diff --git a/README.md b/README.md new file mode 100644 index 0000000..1c77b69 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +https://fastapi.tiangolo.com/advanced/websockets/#await-for-messages-and-send-messages + +```python +fastapi dev server.py +``` diff --git a/app.py b/app.py deleted file mode 100644 index 9030173..0000000 --- a/app.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python - -import asyncio -import itertools -import json - -from websockets.asyncio.server import serve - -from connect4 import PLAYER1, PLAYER2, Connect4 - - - -async def handler(websocket): - # Initialize a Connect Four game. - game = Connect4() - - # Players take alternate turns, using the same browser. - turns = itertools.cycle([PLAYER1, PLAYER2]) - player = next(turns) - - async for message in websocket: - # Parse a "play" event from the UI. - event = json.loads(message) - assert event["type"] == "play" - column = event["column"] - - try: - # Play the move. - row = game.play(player, column) - except ValueError as exc: - # Send an "error" event if the move was illegal. - event = { - "type": "error", - "message": str(exc), - } - await websocket.send(json.dumps(event)) - continue - - # Send a "play" event to update the UI. - event = { - "type": "play", - "player": player, - "column": column, - "row": row, - } - await websocket.send(json.dumps(event)) - - # If move is winning, send a "win" event. - if game.winner is not None: - event = { - "type": "win", - "player": game.winner, - } - await websocket.send(json.dumps(event)) - - # Alternate turns. - player = next(turns) - - -async def main(): - async with serve(handler, "", 8001) as server: - await server.serve_forever() - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/connect4.css b/connect4.css deleted file mode 100644 index 27f0baf..0000000 --- a/connect4.css +++ /dev/null @@ -1,105 +0,0 @@ -/* General layout */ - -body { - background-color: white; - display: flex; - flex-direction: column-reverse; - justify-content: center; - align-items: center; - margin: 0; - min-height: 100vh; -} - -/* Action buttons */ - -.actions { - display: flex; - flex-direction: row; - justify-content: space-evenly; - align-items: flex-end; - width: 720px; - height: 100px; -} - -.action { - color: darkgray; - font-family: "Helvetica Neue", sans-serif; - font-size: 20px; - line-height: 20px; - font-weight: 300; - text-align: center; - text-decoration: none; - text-transform: uppercase; - padding: 20px; - width: 120px; -} - -.action:hover { - background-color: darkgray; - color: white; - font-weight: 700; -} - -.action[href=""] { - display: none; -} - -/* Connect Four board */ - -.board { - background-color: blue; - display: flex; - flex-direction: row; - padding: 0 10px; - position: relative; -} - -.board::before, -.board::after { - background-color: blue; - content: ""; - height: 720px; - width: 20px; - position: absolute; -} - -.board::before { - left: -20px; -} - -.board::after { - right: -20px; -} - -.column { - display: flex; - flex-direction: column-reverse; - padding: 10px; -} - -.cell { - border-radius: 50%; - width: 80px; - height: 80px; - margin: 10px 0; -} - -.empty { - background-color: white; -} - -.column:hover .empty { - background-color: lightgray; -} - -.column:hover .empty ~ .empty { - background-color: white; -} - -.red { - background-color: red; -} - -.yellow { - background-color: yellow; -} diff --git a/connect4.js b/connect4.js deleted file mode 100644 index cb5eb9f..0000000 --- a/connect4.js +++ /dev/null @@ -1,45 +0,0 @@ -const PLAYER1 = "red"; - -const PLAYER2 = "yellow"; - -function createBoard(board) { - // Inject stylesheet. - const linkElement = document.createElement("link"); - linkElement.href = import.meta.url.replace(".js", ".css"); - linkElement.rel = "stylesheet"; - document.head.append(linkElement); - // Generate board. - for (let column = 0; column < 7; column++) { - const columnElement = document.createElement("div"); - columnElement.className = "column"; - columnElement.dataset.column = column; - for (let row = 0; row < 6; row++) { - const cellElement = document.createElement("div"); - cellElement.className = "cell empty"; - cellElement.dataset.column = column; - columnElement.append(cellElement); - } - board.append(columnElement); - } -} - -function playMove(board, player, column, row) { - // Check values of arguments. - if (player !== PLAYER1 && player !== PLAYER2) { - throw new Error(`player must be ${PLAYER1} or ${PLAYER2}.`); - } - const columnElement = board.querySelectorAll(".column")[column]; - if (columnElement === undefined) { - throw new RangeError("column must be between 0 and 6."); - } - const cellElement = columnElement.querySelectorAll(".cell")[row]; - if (cellElement === undefined) { - throw new RangeError("row must be between 0 and 5."); - } - // Place checker in cell. - if (!cellElement.classList.replace("empty", player)) { - throw new Error("cell must be empty."); - } -} - -export { PLAYER1, PLAYER2, createBoard, playMove }; diff --git a/connect4.py b/connect4.py deleted file mode 100644 index 1044769..0000000 --- a/connect4.py +++ /dev/null @@ -1,62 +0,0 @@ -__all__ = ["PLAYER1", "PLAYER2", "Connect4"] - -PLAYER1, PLAYER2 = "red", "yellow" - - -class Connect4: - """ - A Connect Four game. - - Play moves with :meth:`play`. - - Get past moves with :attr:`moves`. - - Check for a victory with :attr:`winner`. - - """ - - def __init__(self): - self.moves = [] - self.top = [0 for _ in range(7)] - self.winner = None - - @property - def last_player(self): - """ - Player who played the last move. - - """ - return PLAYER1 if len(self.moves) % 2 else PLAYER2 - - @property - def last_player_won(self): - """ - Whether the last move is winning. - - """ - b = sum(1 << (8 * column + row) for _, column, row in self.moves[::-2]) - return any(b & b >> v & b >> 2 * v & b >> 3 * v for v in [1, 7, 8, 9]) - - def play(self, player, column): - """ - Play a move in a column. - - Returns the row where the checker lands. - - Raises :exc:`ValueError` if the move is illegal. - - """ - if player == self.last_player: - raise ValueError("It isn't your turn.") - - row = self.top[column] - if row == 6: - raise ValueError("This slot is full.") - - self.moves.append((player, column, row)) - self.top[column] += 1 - - if self.winner is None and self.last_player_won: - self.winner = self.last_player - - return row diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..c909c40 --- /dev/null +++ b/html/index.html @@ -0,0 +1,41 @@ + + +
+