aboutsummaryrefslogtreecommitdiff
path: root/api/game
diff options
context:
space:
mode:
authorlonkaars <l.leblansch@gmail.com>2021-04-11 17:50:58 +0200
committerlonkaars <l.leblansch@gmail.com>2021-04-11 17:50:58 +0200
commit28f104de9ae9abe4b42abafbf3865ede5687996c (patch)
tree65e651f09d8fbf81380384692e45803cb4f9d61c /api/game
parent7b4859059b3bbabf4139ccdf3270a82c094f5d8e (diff)
dprint yapf python formatting
Diffstat (limited to 'api/game')
-rw-r--r--api/game/accept.py17
-rw-r--r--api/game/cleanup.py34
-rw-r--r--api/game/info.py120
-rw-r--r--api/game/new.py61
-rw-r--r--api/game/random.py36
-rw-r--r--api/game/socket.py170
-rw-r--r--api/game/voerbak_connector.py137
7 files changed, 319 insertions, 256 deletions
diff --git a/api/game/accept.py b/api/game/accept.py
index 073f422..a231d3a 100644
--- a/api/game/accept.py
+++ b/api/game/accept.py
@@ -12,19 +12,18 @@ from game.new import start_game
join_game = Blueprint('game_accept', __name__)
+
# join a game by game_id (public or private)
-@join_game.route('/accept', methods = ['POST'])
+@join_game.route('/accept', methods=['POST'])
@auth_required("user")
def index(game_id):
- if cursor.execute("select status from games where game_id = ?", [game_id]).fetchone()[0] != "wait_for_opponent":
- return "", 403
+ if cursor.execute("select status from games where game_id = ?",
+ [game_id]).fetchone()[0] != "wait_for_opponent":
+ return "", 403
+
+ start_game(game_id, user_id)
- start_game(game_id, user_id)
+ return {"id": game_id, "player_1": False, "game_started": True}, 200
- return {
- "id": game_id,
- "player_1": False,
- "game_started": True
- }, 200
dynamic_route = ["/game", join_game]
diff --git a/api/game/cleanup.py b/api/game/cleanup.py
index 0a9aa46..5b705d2 100644
--- a/api/game/cleanup.py
+++ b/api/game/cleanup.py
@@ -2,22 +2,30 @@ from db import cursor, connection
import threading
import time
+
# cleanup function that's ran every five minutes
def cleanup():
- now = int( time.time() * 1000 )
- old_games = cursor.execute("select game_id from games where (status = \"wait_for_opponent\" or status = \"in_progress\") and last_activity < ?", [now - 5 * 60 * 1e3]).fetchall()
- for game_id in old_games:
- cursor.execute("delete from games where game_id = ?", [game_id[0]])
- connection.commit()
+ now = int(time.time() * 1000)
+ old_games = cursor.execute(
+ "select game_id from games where (status = \"wait_for_opponent\" or status = \"in_progress\") and last_activity < ?",
+ [now - 5 * 60 * 1e3]
+ ).fetchall()
+ for game_id in old_games:
+ cursor.execute("delete from games where game_id = ?", [game_id[0]])
+ connection.commit()
+
+
+def set_interval(
+ func, sec
+): # https://stackoverflow.com/questions/2697039/python-equivalent-of-setinterval
+ def func_wrapper():
+ set_interval(func, sec)
+ func()
+
+ t = threading.Timer(sec, func_wrapper)
+ t.start()
+ return t
-def set_interval(func, sec): # https://stackoverflow.com/questions/2697039/python-equivalent-of-setinterval
- def func_wrapper():
- set_interval(func, sec)
- func()
- t = threading.Timer(sec, func_wrapper)
- t.start()
- return t
# run every five minutes
set_interval(cleanup, 5 * 60)
-
diff --git a/api/game/info.py b/api/game/info.py
index 76a3ef8..869a19c 100644
--- a/api/game/info.py
+++ b/api/game/info.py
@@ -5,69 +5,83 @@ from user.info import format_user
from rating import outcome
from ruleset import resolve_ruleset
-def format_game(game_id, user_id = None):
- game = cursor.execute("select " + ", ".join([
- "game_id", # 0
- "parent_game", # 1
- "moves", # 2
- "player_1_id", # 3
- "player_2_id", # 4
- "outcome", # 5
- "created", # 6
- "started", # 7
- "duration", # 8
- "rating_delta_player_1", # 9
- "rating_delta_player_2", # 10
- "ruleset", # 11
- "status", # 12
- "private", # 13
- ]) + " from games where game_id = ?", [game_id]).fetchone()
-
- is_player_1 = game[4] != user_id
-
- # get opponent from perspective of `user_id`
- opponent = game[4] if is_player_1 else game[3]
-
- # parse moves into list and return empty list if moves string is empty
- moves = [] if len(game[2]) == 0 else [int(move) for move in str(game[2] + "0").split(",")]
-
- return {
- "id": game[0],
- "parent": game[1],
- "moves": moves,
- "opponent": None if not opponent else format_user(opponent),
- "outcome": None if not game[5] else outcome(game[5], is_player_1),
- "created": game[6],
- "started": game[7],
- "duration": game[8],
- "rating": game[9] if is_player_1 else game[10],
- "rating_opponent": game[10] if is_player_1 else game[9],
- "ruleset": resolve_ruleset(game[11]),
- "status": game[12],
- "private": bool(game[13]),
- }
+
+def format_game(game_id, user_id=None):
+ game = cursor.execute(
+ "select " + ", ".join(
+ [
+ "game_id", # 0
+ "parent_game", # 1
+ "moves", # 2
+ "player_1_id", # 3
+ "player_2_id", # 4
+ "outcome", # 5
+ "created", # 6
+ "started", # 7
+ "duration", # 8
+ "rating_delta_player_1", # 9
+ "rating_delta_player_2", # 10
+ "ruleset", # 11
+ "status", # 12
+ "private", # 13
+ ]
+ ) + " from games where game_id = ?",
+ [game_id]
+ ).fetchone()
+
+ is_player_1 = game[4] != user_id
+
+ # get opponent from perspective of `user_id`
+ opponent = game[4] if is_player_1 else game[3]
+
+ # parse moves into list and return empty list if moves string is empty
+ moves = [] if len(game[2]) == 0 else [
+ int(move) for move in str(game[2] + "0").split(",")
+ ]
+
+ return {
+ "id": game[0],
+ "parent": game[1],
+ "moves": moves,
+ "opponent": None if not opponent else format_user(opponent),
+ "outcome": None if not game[5] else outcome(game[5], is_player_1),
+ "created": game[6],
+ "started": game[7],
+ "duration": game[8],
+ "rating": game[9] if is_player_1 else game[10],
+ "rating_opponent": game[10] if is_player_1 else game[9],
+ "ruleset": resolve_ruleset(game[11]),
+ "status": game[12],
+ "private": bool(game[13]),
+ }
+
# check if game_id exists in database
def valid_game_id(game_id):
- query = cursor.execute("select game_id from games where game_id = ?", [game_id]).fetchone()
- return bool(query)
+ query = cursor.execute(
+ "select game_id from games where game_id = ?", [game_id]
+ ).fetchone()
+ return bool(query)
+
game_info = Blueprint('game_info', __name__)
-@game_info.route('/info', methods = ['POST'])
+
+@game_info.route('/info', methods=['POST'])
def index():
- data = request.get_json()
- if not data: return "", 400
+ data = request.get_json()
+ if not data: return "", 400
+
+ game_id = data.get("id") or ""
+ if not game_id: return "", 400
- game_id = data.get("id") or ""
- if not game_id: return "", 400
+ user_id = None
+ token = request.cookies.get("token") or ""
+ if token: user_id = token_login(token)
- user_id = None
- token = request.cookies.get("token") or ""
- if token: user_id = token_login(token)
+ if not valid_game_id(game_id): return "", 403
- if not valid_game_id(game_id): return "", 403
+ return format_game(game_id, user_id), 200
- return format_game(game_id, user_id), 200
dynamic_route = ["/game", game_info]
diff --git a/api/game/new.py b/api/game/new.py
index 9868099..7f0862b 100644
--- a/api/game/new.py
+++ b/api/game/new.py
@@ -6,39 +6,60 @@ from randid import new_uuid
from game.socket import games, game
from hierarchy import auth_required
-def create_game(user_1_id, private = False, user_2_id = None):
- timestamp = int( time.time() * 1000 )
- game_id = new_uuid("games")
+def create_game(user_1_id, private=False, user_2_id=None):
+ timestamp = int(time.time() * 1000)
- cursor.execute("insert into games values (?, NULL, \"\", ?, ?, NULL, ?, NULL, ?, NULL, NULL, NULL, \"wait_for_opponent\", \"default\", ?, FALSE) ", (game_id, user_1_id, user_2_id, timestamp, timestamp, private))
- connection.commit()
+ game_id = new_uuid("games")
+
+ cursor.execute(
+ "insert into games values (?, NULL, \"\", ?, ?, NULL, ?, NULL, ?, NULL, NULL, NULL, \"wait_for_opponent\", \"default\", ?, FALSE) ",
+ (game_id, user_1_id, user_2_id, timestamp, timestamp, private)
+ )
+ connection.commit()
+
+ return game_id
- return game_id
def start_game(game_id, user_2_id):
- timestamp = int( time.time() * 1000 )
+ timestamp = int(time.time() * 1000)
+
+ db_game = cursor.execute(
+ "select player_2_id, status, private from games where game_id = ?",
+ [game_id]
+ ).fetchone()
+ if db_game[1] != "wait_for_opponent": return False
- db_game = cursor.execute("select player_2_id, status, private from games where game_id = ?", [game_id]).fetchone()
- if db_game[1] != "wait_for_opponent": return False
+ if db_game[0] == None:
+ cursor.execute(
+ "update games set player_2_id = ? where game_id = ?",
+ (user_2_id, game_id)
+ )
+ cursor.execute(
+ "update games set status = \"in_progress\", started = ?, last_activity = ? where game_id = ?",
+ (timestamp, timestamp, game_id)
+ )
+ connection.commit()
- if db_game[0] == None: cursor.execute("update games set player_2_id = ? where game_id = ?", (user_2_id, game_id))
- cursor.execute("update games set status = \"in_progress\", started = ?, last_activity = ? where game_id = ?", (timestamp, timestamp, game_id))
- connection.commit()
+ players = cursor.execute(
+ "select player_1_id, player_2_id from games where game_id = ?",
+ [game_id]
+ ).fetchone()
+ games[game_id] = game(game_id, io, players[0], players[1])
- players = cursor.execute("select player_1_id, player_2_id from games where game_id = ?", [game_id]).fetchone()
- games[game_id] = game(game_id, io, players[0], players[1])
+ io.emit("gameStart", room=games[game_id].room)
- io.emit("gameStart", room=games[game_id].room)
new_game = Blueprint('new_game', __name__)
-@new_game.route('/new', methods = ["GET", "POST"])
+
+@new_game.route('/new', methods=["GET", "POST"])
@auth_required("user")
def index(user_id):
- # create a new private game (join by link)
- #TODO: friend invites + notifications
- game_id = create_game(user_id, True)
- return { "id": game_id }, 200
+ # create a new private game (join by link)
+ #TODO: friend invites + notifications
+ game_id = create_game(user_id, True)
+ return {"id": game_id}, 200
+
dynamic_route = ["/game", new_game]
diff --git a/api/game/random.py b/api/game/random.py
index 4d70b56..2dfbe0b 100644
--- a/api/game/random.py
+++ b/api/game/random.py
@@ -11,27 +11,35 @@ from socket_io import io
random_game = Blueprint('random', __name__)
+
@random_game.route('/random')
@auth_required("user")
def index(user_id):
- # get public_games (random opponent queue)
- public_games = cursor.execute("select game_id from games where private = FALSE and status = \"wait_for_opponent\"").fetchall()
+ # get public_games (random opponent queue)
+ public_games = cursor.execute(
+ "select game_id from games where private = FALSE and status = \"wait_for_opponent\""
+ ).fetchall()
+
+ game_started = False
- game_started = False
+ # create a new public game if the queue is empty
+ if len(public_games) == 0:
+ game_id = create_game(user_id)
+ player_1 = True
+ # otherwise join a random public game
+ else:
+ game_id = random.choice(public_games)[0]
- # create a new public game if the queue is empty
- if len(public_games) == 0:
- game_id = create_game(user_id)
- player_1 = True
- # otherwise join a random public game
- else:
- game_id = random.choice(public_games)[0]
+ start_game(game_id, user_id)
- start_game(game_id, user_id)
+ player_1 = False
+ game_started = True
- player_1 = False
- game_started = True
+ return {
+ "id": game_id,
+ "player_1": player_1,
+ "game_started": game_started
+ }, 200
- return { "id": game_id, "player_1": player_1, "game_started": game_started }, 200
dynamic_route = ["/game", random_game]
diff --git a/api/game/socket.py b/api/game/socket.py
index cf45eb6..63f680c 100644
--- a/api/game/socket.py
+++ b/api/game/socket.py
@@ -9,97 +9,109 @@ from socket_io import io
games = {}
+
class game:
- def __init__(self, game_id, io, player_1_id, player_2_id):
- self.game_id = game_id
- self.room = "game-" + game_id
- self.board = bord(7, 6)
- self.io = io
- self.player_1_id = player_1_id
- self.player_2_id = player_2_id
-
- # drop a disc in `column`
- def move(self, user_id, column):
- if user_id != self.player_1_id and user_id != self.player_2_id: return
- move = self.player_1_id if self.board.player_1 else self.player_2_id
- if user_id != move: return
-
- self.board.drop_fisje(column)
-
- io.emit("fieldUpdate", { "field": self.board.board }, room=self.room)
-
- now = int( time.time() * 1000 )
- cursor.execute("update games set last_activity = ?, moves = moves || ? || ',' where game_id = ?", [now, column, self.game_id])
- connection.commit()
-
- if len(self.board.win_positions) > 0 or self.board.board_full:
- outcome = "d"
- if not self.board.board_full:
- winner = self.board.board[int(self.board.win_positions[0][0])]
- outcome = "w" if winner == "2" else "l"
- io.emit("finish", {
- "winPositions": self.board.win_positions,
- "boardFull": self.board.board_full
- }, room=self.room)
- self.close("finished", outcome)
- return
-
- io.emit("turnUpdate", { "player1": self.board.player_1 }, room=self.room)
-
- def resign(self):
- self.board.kill_voerbak()
- io.emit("resign", room=self.room)
- self.close("resign", "d")
-
- def close(self, new_status, outcome):
- cursor.execute(" ".join([
- "update games set",
- "moves = moves || '0',",
- "duration = ?,",
- "status = ?,",
- "outcome = ?",
- "where game_id = ?"
- ]), [
- int( time.time() * 1000 ) - cursor.execute("select started from games where game_id = ?", [self.game_id]).fetchone()[0],
- new_status,
- outcome,
- self.game_id
- ])
- connection.commit()
-
- games.pop(self.game_id)
+ def __init__(self, game_id, io, player_1_id, player_2_id):
+ self.game_id = game_id
+ self.room = "game-" + game_id
+ self.board = bord(7, 6)
+ self.io = io
+ self.player_1_id = player_1_id
+ self.player_2_id = player_2_id
+
+ # drop a disc in `column`
+ def move(self, user_id, column):
+ if user_id != self.player_1_id and user_id != self.player_2_id: return
+ move = self.player_1_id if self.board.player_1 else self.player_2_id
+ if user_id != move: return
+
+ self.board.drop_fisje(column)
+
+ io.emit("fieldUpdate", {"field": self.board.board}, room=self.room)
+
+ now = int(time.time() * 1000)
+ cursor.execute(
+ "update games set last_activity = ?, moves = moves || ? || ',' where game_id = ?",
+ [now, column, self.game_id]
+ )
+ connection.commit()
+
+ if len(self.board.win_positions) > 0 or self.board.board_full:
+ outcome = "d"
+ if not self.board.board_full:
+ winner = self.board.board[int(self.board.win_positions[0][0])]
+ outcome = "w" if winner == "2" else "l"
+ io.emit(
+ "finish", {
+ "winPositions": self.board.win_positions,
+ "boardFull": self.board.board_full
+ },
+ room=self.room
+ )
+ self.close("finished", outcome)
+ return
+
+ io.emit("turnUpdate", {"player1": self.board.player_1}, room=self.room)
+
+ def resign(self):
+ self.board.kill_voerbak()
+ io.emit("resign", room=self.room)
+ self.close("resign", "d")
+
+ def close(self, new_status, outcome):
+ cursor.execute(
+ " ".join(
+ [
+ "update games set", "moves = moves || '0',",
+ "duration = ?,", "status = ?,", "outcome = ?",
+ "where game_id = ?"
+ ]
+ ), [
+ int(time.time() * 1000) - cursor.execute(
+ "select started from games where game_id = ?",
+ [self.game_id]
+ ).fetchone()[0], new_status, outcome, self.game_id
+ ]
+ )
+ connection.commit()
+
+ games.pop(self.game_id)
+
@io.on("newMove")
def new_move(data):
- if not data["game_id"] or \
- not data["move"] or \
- not data["token"]: return
- if not data["game_id"] in games: return
+ if not data["game_id"] or \
+ not data["move"] or \
+ not data["token"]:
+ return
+ if not data["game_id"] in games: return
+
+ game = games[data["game_id"]]
+ if (len(game.board.win_positions) > 0 or game.board.board_full): return
+ user_id = token_login(data["token"])
+ game.move(user_id, data["move"])
- game = games[data["game_id"]]
- if(len(game.board.win_positions) > 0 or game.board.board_full): return
- user_id = token_login(data["token"])
- game.move(user_id, data["move"])
@io.on("resign")
def resign(data):
- if not data["game_id"] or \
- not request.cookies.get("token"): return
- if not data["game_id"] in games: return
+ if not data["game_id"] or \
+ not request.cookies.get("token"):
+ return
+ if not data["game_id"] in games: return
- user_id = token_login(request.cookies.get("token"))
- if not user_id: return
+ user_id = token_login(request.cookies.get("token"))
+ if not user_id: return
- if games[data["game_id"]].player_1_id != user_id and \
- games[data["game_id"]].player_2_id != user_id:
- return
+ if games[data["game_id"]].player_1_id != user_id and \
+ games[data["game_id"]].player_2_id != user_id:
+ return
+
+ games[data["game_id"]].resign()
- games[data["game_id"]].resign()
@io.on("registerGameListener")
def register_game_listener(data):
- game_id = data.get("game_id")
- if not game_id: return
-
- join_room("game-" + game_id)
+ game_id = data.get("game_id")
+ if not game_id: return
+ join_room("game-" + game_id)
diff --git a/api/game/voerbak_connector.py b/api/game/voerbak_connector.py
index 048a5d1..412a512 100644
--- a/api/game/voerbak_connector.py
+++ b/api/game/voerbak_connector.py
@@ -11,82 +11,83 @@ EMPTY = Fore.LIGHTBLACK_EX + "_" + Fore.RESET
VOERBAK_LOCATION = os.path.join(os.getcwd(), "voerbak/", "voerbak")
if os.name == "nt": VOERBAK_LOCATION += ".exe"
+
class bord:
- def __init__(self, w, h):
- self.width = w
- self.height = h
- self.player_1 = True
- self.board = "0" * (w * h)
- self.board_full = False
- self.win_positions = []
- self.process = subprocess.Popen([VOERBAK_LOCATION, f"-w {w}", f"-h {h}"],
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=None)
- self.process.stdin.flush()
+ def __init__(self, w, h):
+ self.width = w
+ self.height = h
+ self.player_1 = True
+ self.board = "0" * (w * h)
+ self.board_full = False
+ self.win_positions = []
+ self.process = subprocess.Popen(
+ [VOERBAK_LOCATION, f"-w {w}", f"-h {h}"],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=None
+ )
+ self.process.stdin.flush()
+
+ # get output from voerbak without trailing newline character (this might break on windows because crlf)
+ def get_output(self):
+ return self.process.stdout.readline().decode()[:-1]
- # get output from voerbak without trailing newline character (this might break on windows because crlf)
- def get_output(self):
- return self.process.stdout.readline().decode()[:-1]
+ def kill_voerbak(self):
+ self.process.stdin.write(bytearray("0", "utf-8"))
+ self.process.stdin.flush()
- def kill_voerbak(self):
- self.process.stdin.write(bytearray("0", "utf-8"))
- self.process.stdin.flush()
+ # read messages from voerbak
+ def update_board(self):
+ buffer = self.get_output()
+ while not buffer.isdigit():
+ # win message
+ if buffer.startswith("w:"):
+ self.win_positions.append(buffer[2:].split("-"))
+ log.info(f"won: {buffer[2:].split('-')}")
+ self.kill_voerbak()
+ # error message
+ elif buffer.startswith("e:"):
+ log.warning(buffer[2:])
+ # turn update message
+ elif buffer.startswith("m:"):
+ substr = buffer[2:]
+ self.player_1 = True if substr == "true" else False
+ # draw game message
+ elif buffer.startswith("d:"):
+ self.board_full = True
+ self.kill_voerbak()
+ buffer = self.get_output()
+ self.board = buffer
- # read messages from voerbak
- def update_board(self):
- buffer = self.get_output()
- while not buffer.isdigit():
- # win message
- if buffer.startswith("w:"):
- self.win_positions.append(buffer[2:].split("-"))
- log.info(f"won: {buffer[2:].split('-')}")
- self.kill_voerbak()
- # error message
- elif buffer.startswith("e:"):
- log.warning(buffer[2:])
- # turn update message
- elif buffer.startswith("m:"):
- substr = buffer[2:]
- self.player_1 = True if substr == "true" else False
- # draw game message
- elif buffer.startswith("d:"):
- self.board_full = True
- self.kill_voerbak()
- buffer = self.get_output()
- self.board = buffer
+ # debug board print function
+ def print(self):
+ for y in range(self.height - 1, -1, -1):
+ for x in range(self.width):
+ state = self.board[x + y * self.width]
+ char = [EMPTY, DISC_A, DISC_B]
+ print(char[int(state)], end=" ")
+ print("\n", end="")
- # debug board print function
- def print(self):
- for y in range(self.height -1, -1, -1):
- for x in range(self.width):
- state = self.board[x + y * self.width]
- char = [EMPTY,
- DISC_A,
- DISC_B
- ]
- print(char[int(state)], end=" ")
- print("\n", end="")
+ def drop_fisje(self, column):
+ self.process.stdin.write(bytearray(f"{column}\n", "utf-8"))
+ self.process.stdin.flush()
+ self.update_board()
- def drop_fisje(self, column):
- self.process.stdin.write(bytearray(f"{column}\n", "utf-8"))
- self.process.stdin.flush()
- self.update_board()
# debug game
def main():
- gert = bord(7, 6)
- while True:
- print(gert.player_1)
- if len(gert.win_positions) > 0:
- print(f"won: {gert.win_positions}")
- exit(0)
- gert.print()
- column = int(input("column?: ")) - 1
- if column not in range(gert.width):
- continue
- gert.drop_fisje(column + 1)
+ gert = bord(7, 6)
+ while True:
+ print(gert.player_1)
+ if len(gert.win_positions) > 0:
+ print(f"won: {gert.win_positions}")
+ exit(0)
+ gert.print()
+ column = int(input("column?: ")) - 1
+ if column not in range(gert.width):
+ continue
+ gert.drop_fisje(column + 1)
-if __name__ == "__main__":
- main()
+if __name__ == "__main__":
+ main()