You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
346 lines
11 KiB
Python
346 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import sys
|
|
import sqlite3 as sql
|
|
import os
|
|
from os.path import isfile as fileexists
|
|
import subprocess
|
|
import json
|
|
from gomoku import Gomoku
|
|
from isola import Isola
|
|
from brandubh import Brandubh
|
|
|
|
version = '0.8.1'
|
|
home_folder = os.path.expanduser('~')
|
|
username = os.path.split(home_folder)[-1]
|
|
database = '/var/lib/oberon/oberon.sqlite'
|
|
basefile = '/var/lib/oberon/'
|
|
|
|
|
|
def print_header():
|
|
print('\nO B E R O N v{}\n'.format(version))
|
|
|
|
|
|
##############################
|
|
######------------------#######
|
|
###### Top Lvl Cmds ########
|
|
######------------------#######
|
|
##############################
|
|
|
|
# Binds to the HISTORY command
|
|
def show_game_record():
|
|
q = "SELECT rowid, p1, p2, winner, game_type FROM games WHERE winner is not NULL and (p1 = ? or p2 = ?)"
|
|
v = (username, username)
|
|
res = db_do(q, v)
|
|
if res == False:
|
|
print('Database error. Contact the sysadmin or try another time')
|
|
print_header()
|
|
print('{:^8} {:^10} {:^10} {:^10} {:^12}'.format('ID', 'Player 1', 'Player 2', 'Winner', 'Game Type'))
|
|
print('-------- ---------- ---------- ---------- ------------')
|
|
for x in res:
|
|
print('{:^8} {:^10} {:^10} {:^10} {:^12}'.format(x[0], x[1], x[2], x[3], x[4]))
|
|
if not len(res):
|
|
print('You have not completed any games yet...')
|
|
print('\nTo view a historical game, run "oberon play [game_id]"')
|
|
return True
|
|
|
|
|
|
# Binds to the PLAY command
|
|
def play_game(gid):
|
|
q = "SELECT rowid, p1, p2, game_board, turn, game_type, winner FROM games WHERE rowid = ?"
|
|
v = (gid,)
|
|
res = db_do(q,v)
|
|
if not res:
|
|
print('Invalid game id')
|
|
return False
|
|
elif res[0][1] != username and res[0][2] != username:
|
|
print('Invalid selection. You are not playing game {}'.format(gid))
|
|
return False
|
|
|
|
state = {
|
|
'id': res[0][0],
|
|
'p1': res[0][1],
|
|
'p2': res[0][2],
|
|
'board': json.loads(res[0][3]),
|
|
'turn_number': res[0][4],
|
|
'game_type': res[0][5],
|
|
'winner': res[0][6],
|
|
'current_ps_turn': res[0][2] if res[0][4] % 2 == 0 else res[0][1],
|
|
'piece_index': 1 if res[0][4] % 2 == 0 else 0
|
|
}
|
|
|
|
if state['game_type'] == 'gomoku':
|
|
game = Gomoku(state)
|
|
elif state['game_type'] == 'isola':
|
|
game = Isola(state)
|
|
elif state['game_type'] == 'brandubh':
|
|
game = Brandubh(state)
|
|
|
|
print_header()
|
|
game.print_challenge_text()
|
|
game.print_board()
|
|
|
|
if not state['current_ps_turn'] == username:
|
|
print('\nIt is your opponents turn.\n')
|
|
return True
|
|
|
|
if game.winner:
|
|
print('You are viewing a game that has already finished')
|
|
return True
|
|
|
|
play = game.get_input()
|
|
|
|
if not play:
|
|
sys.exit(0)
|
|
else:
|
|
q2 = "UPDATE games SET game_board = ?, turn = ?, winner = ? WHERE rowid = ?"
|
|
v2 = (json.dumps(play['board']), state['turn_number'] + 1, play['winner'], gid)
|
|
res2 = db_do(q2, v2, True)
|
|
if play['winner']:
|
|
enemy = state['p1'] if state['p1'] != username else state['p2']
|
|
msg_path = basefile + '{}'.format(enemy)
|
|
writemode = 'a' if fileexists(msg_path) else 'w'
|
|
with open(msg_path, writemode) as f:
|
|
f.write('{} defeated you in {} game {}\n'.format(username, state['game_type'], gid))
|
|
|
|
if not res2:
|
|
print('Error updating database, sorry.\nTry again later?\n...or contact the sysadmin.')
|
|
return False
|
|
print(play['message'])
|
|
|
|
|
|
|
|
# Binds to the LIST command
|
|
def list_games():
|
|
q = "SELECT rowid, p1, p2, game_type, game_status, turn FROM games WHERE winner is NULL and (p1 = ? or p2 = ?)"
|
|
v = (username, username)
|
|
res = db_do(q, v)
|
|
if res == False:
|
|
print('Database error. Contact the sysadmin or try another time.')
|
|
return False
|
|
print_header()
|
|
print('{:^8} {:^10} {:^10} {:^22} {:^12}'.format('ID', 'Player 1', 'Player 2', 'Game Status', 'Game Type'))
|
|
print('-------- ---------- ---------- ---------------------- ------------')
|
|
for x in res:
|
|
pturn = x[2] if x[5] % 2 == 0 else x[1]
|
|
print("{:^8} {:^10} {:^10} {:^22} {:^12}".format(x[0], x[1], x[2], pturn +"'s turn", x[3]))
|
|
if not len(res):
|
|
print('You are not currently playing any games...')
|
|
print('')
|
|
return True
|
|
|
|
|
|
# Binds to the HELP command
|
|
def display_help():
|
|
print_header()
|
|
print('syntax: oberon [command [option]]\n')
|
|
print('{:20} {}'.format('help','display this message'))
|
|
print('{:20} {}'.format('list', 'display your currently open games'))
|
|
print('{:20} {}'.format('history', 'display your win/loss record'))
|
|
print('{:20} {}'.format('create [user]', 'create a game against the user provided'))
|
|
print('{:20} {}'.format('play [game_id]', 'make a move or view the board for the game id provided'))
|
|
print('')
|
|
print('if you do not want to play games simply add a file named ".killoberon" to your home directory and users will not be able to create games with you')
|
|
print('')
|
|
|
|
|
|
# Binds to the CREATE command
|
|
def create_game(enemy):
|
|
if enemy == username:
|
|
print('This system is designed for multiuser games. You cannot create a game against yourself.')
|
|
return False
|
|
enemies = get_user_list()
|
|
|
|
if not enemy in enemies:
|
|
print('There is no avialable player with the name {}.\nGame not created.'.format(enemy))
|
|
return False
|
|
|
|
if fileexists('/home/{}/.killoberon'.format(enemy)):
|
|
print('{} does not wish to play games in oberon. Please try creating a game with a different user.'.format(enemy))
|
|
return False
|
|
|
|
print_header()
|
|
print('Which game would you like to play?\n\n(1) Gomoku/Five In a Row\n(2) Isola\n(3) Brandubh\n\nor (Q)uit')
|
|
while True:
|
|
game = input('> ')
|
|
if game.lower() == 'q':
|
|
sys.exit(0)
|
|
elif not game in ['1', '2', '3']:
|
|
continue
|
|
|
|
break
|
|
|
|
if game == '1':
|
|
game_board = [[' · ' for y in range(15)] for x in range(15)]
|
|
game_choice = 'gomoku'
|
|
elif game == '2':
|
|
board = [[ ' - ' for y in range(7)] for x in range(7)]
|
|
board[0][3] = ' X '
|
|
board[6][3] = ' O '
|
|
game_board = {'board': board, 'p1': [0,3], 'p2': [6,3]}
|
|
game_choice = 'isola'
|
|
elif game == '3':
|
|
game_board = [[ ' - ' for y in range(7)] for x in range(7)]
|
|
game_board[0][0] = ' * '
|
|
game_board[0][6] = ' * '
|
|
game_board[6][0] = ' * '
|
|
game_board[6][6] = ' * '
|
|
for x in [0,1,5,6]:
|
|
game_board[3][x] = ' a '
|
|
game_board[x][3] = ' a '
|
|
for x in [2,4]:
|
|
game_board[3][x] = ' d '
|
|
game_board[x][3] = ' d '
|
|
game_board[3][3] = ' D '
|
|
game_choice = 'brandubh'
|
|
|
|
player = '0'
|
|
print('')
|
|
while not player in ['1','2']:
|
|
print('Play as player 1 or player 2 (enter the number)')
|
|
player = input('> ')
|
|
|
|
if player == '2':
|
|
temp = enemy
|
|
enemy = username
|
|
player = temp
|
|
else:
|
|
player = username
|
|
|
|
q = "INSERT INTO games VALUES (?, ?, ?, ?, ?, ?, ?)"
|
|
v = (player, enemy, game_choice, 'playing', json.dumps(game_board), 1, None)
|
|
res = db_do(q, v, True)
|
|
if res:
|
|
print('Game between {} and {} created!'.format(username, enemy))
|
|
return True
|
|
return False
|
|
|
|
|
|
# Binds to the AVAILABLE command
|
|
def available_moves():
|
|
q = "SELECT rowid, p1, p2, turn FROM games WHERE winner is null and (p1 = ? or p2 = ?)"
|
|
v = (username, username)
|
|
res = db_do(q, v)
|
|
p1 = [x[0] for x in res if x[1] == username and x[3] % 2 != 0]
|
|
p2 = [x[0] for x in res if x[2] == username and x[3] % 2 == 0]
|
|
print('- - - -')
|
|
if len(p1) + len(p2) > 0:
|
|
p1 = ', '.join(str(x) for x in p1)
|
|
p2 = ', '.join(str(x) for x in p2)
|
|
spacer = ', ' if p1 and p2 else ''
|
|
print('\033[1;32mIt is your turn in the following game(s): {}{}{}\033[0m'.format(p1, spacer, p2))
|
|
else:
|
|
print('It is not your turn in any games...\n')
|
|
|
|
|
|
##############################
|
|
######------------------#######
|
|
###### Sys Utilities ########
|
|
######------------------#######
|
|
##############################
|
|
def parse_args():
|
|
args = sys.argv
|
|
arg_len = len(args)
|
|
if arg_len == 1:
|
|
display_help()
|
|
available_moves()
|
|
sys.exit(0)
|
|
if arg_len == 2:
|
|
if args[1] == 'list':
|
|
l = list_games()
|
|
if l:
|
|
sys.exit(0)
|
|
sys.exit(1)
|
|
elif args[1] == 'history':
|
|
h = show_game_record()
|
|
if h:
|
|
sys.exit(0)
|
|
sys.exit(1)
|
|
elif args[1] == 'available':
|
|
available_moves()
|
|
sys.exit(0)
|
|
elif args[1] == 'create':
|
|
print('Must include an opponent name.\nFind one in chat or cspc!')
|
|
sys.exit(1)
|
|
elif args[1] == 'play':
|
|
print('Must include a game id.\nRun "oberon list" to view your open games.')
|
|
sys.exit(1)
|
|
elif args[1] == 'help':
|
|
display_help()
|
|
sys.exit(0)
|
|
else:
|
|
print('Invalid arguments. Run "oberon help".')
|
|
sys.exit(1)
|
|
elif arg_len == 3:
|
|
if args[1] == 'create':
|
|
if create_game(args[2]):
|
|
sys.exit(0)
|
|
sys.exit(1)
|
|
elif args[1] == 'play':
|
|
try:
|
|
game_id = int(args[2])
|
|
except:
|
|
print('Invalid game id.')
|
|
sys.exit(1)
|
|
play_game(game_id)
|
|
sys.exit(0)
|
|
else:
|
|
print('Invalid arguments. Run "oberon help".')
|
|
sys.exit(1)
|
|
else:
|
|
print('Unknown paramaters: {}'.format(' '.join(args[3:])))
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
def get_user_list():
|
|
res = subprocess.run(['awk', '-F', ':', '{if ($7 == "/usr/local/bin/colorsh") print $1}', '/etc/passwd'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
res_list = res.stdout.decode('utf-8').split('\n')
|
|
if res_list[0] == '':
|
|
return []
|
|
return res_list
|
|
|
|
|
|
##############################
|
|
######------------------#######
|
|
###### DB Utilities ########
|
|
######------------------#######
|
|
##############################
|
|
def check_and_build_db(db_path):
|
|
if not os.path.isfile(db_path):
|
|
conn = sql.connect(db_path)
|
|
c = conn.cursor()
|
|
|
|
c.execute("CREATE TABLE games (p1 text NOT NULL, p2 text DEFAULT NULL, game_type text NOT NULL, game_status text DEFAULT NULL, game_board text, turn INTEGER, winner text DEFAULT NULL)")
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
|
|
def db_do(query, var=False, noresval=False):
|
|
global messages
|
|
db_path = database
|
|
if os.path.isfile(db_path):
|
|
conn = sql.connect(db_path)
|
|
c = conn.cursor()
|
|
if var:
|
|
c.execute(query, var)
|
|
else:
|
|
c.execute(query)
|
|
if noresval:
|
|
out = c.rowcount
|
|
else:
|
|
out = []
|
|
for row in c:
|
|
out.append(row)
|
|
conn.commit()
|
|
conn.close()
|
|
return out
|
|
else:
|
|
return False
|
|
|
|
|
|
if __name__ == '__main__':
|
|
check_and_build_db(database)
|
|
parse_args()
|