Initial Commit
This commit is contained in:
280
app.py
Normal file
280
app.py
Normal file
@@ -0,0 +1,280 @@
|
||||
import asyncio
|
||||
import json
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from itertools import combinations
|
||||
from typing import TypedDict
|
||||
|
||||
from slow import JSONAPI, App, JSONResponse, render
|
||||
|
||||
|
||||
class Coord(TypedDict):
|
||||
x: int
|
||||
y: int
|
||||
|
||||
|
||||
app = App()
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
WHITE = 0
|
||||
BLACK = 1
|
||||
|
||||
|
||||
class Piece(Enum):
|
||||
NONE = ""
|
||||
EMPTY = "E"
|
||||
|
||||
# WHITE
|
||||
WHITE_PAWN = "P"
|
||||
WHITE_ROOK = "R"
|
||||
WHITE_BISHOP = "B"
|
||||
WHITE_KING = "K"
|
||||
WHITE_QUEEN = "Q"
|
||||
WHITE_CASTLE = "C"
|
||||
|
||||
# BLACK
|
||||
BLACK_PAWN = "p"
|
||||
BLACK_ROOK = "r"
|
||||
BLACK_BISHOP = "b"
|
||||
BLACK_KING = "k"
|
||||
BLACK_QUEEN = "q"
|
||||
BLACK_CASTLE = "c"
|
||||
|
||||
|
||||
def xy_to_coord(xy: Coord) -> None | str:
|
||||
if xy.x < 0 or xy.x > 7 or xy.y < 0 or xy.y > 7:
|
||||
return None
|
||||
return "abcdefgh"[xy.x] + "12345678"[xy.y]
|
||||
|
||||
|
||||
def coord_to_xy(pos: str) -> tuple[int, int]:
|
||||
return ("abcdefgh".inedx(pos[0]), "12345678".inedx(pos[1]))
|
||||
|
||||
|
||||
class Board:
|
||||
grid: list[list[Piece]]
|
||||
|
||||
def __init__(self):
|
||||
self.grid = [[Piece.EMPTY] * 8] * 8
|
||||
self.grid[0] = [
|
||||
Piece.WHITE_CASTLE,
|
||||
Piece.WHITE_ROOK,
|
||||
Piece.WHITE_BISHOP,
|
||||
Piece.WHITE_KING,
|
||||
Piece.WHITE_QUEEN,
|
||||
Piece.WHITE_BISHOP,
|
||||
Piece.WHITE_ROOK,
|
||||
Piece.WHITE_CASTLE,
|
||||
]
|
||||
self.grid[1] = [Piece.WHITE_PAWN] * 8
|
||||
|
||||
self.grid[6] = [Piece.BLACK_PAWN] * 8
|
||||
self.grid[7] = [
|
||||
Piece.BLACK_CASTLE,
|
||||
Piece.BLACK_ROOK,
|
||||
Piece.BLACK_BISHOP,
|
||||
Piece.BLACK_KING,
|
||||
Piece.BLACK_QUEEN,
|
||||
Piece.BLACK_BISHOP,
|
||||
Piece.BLACK_ROOK,
|
||||
Piece.BLACK_CASTLE,
|
||||
]
|
||||
|
||||
def index(self, pos: str) -> Piece:
|
||||
let = "abcdefgh".index(pos[0])
|
||||
num = "12345678".index(pos[1])
|
||||
return self.grid[num][let]
|
||||
|
||||
def index_xy(self, xy: Coord) -> Piece:
|
||||
if xy.x < 0 or xy.x > 7 or xy.y < 0 or xy.y > 7:
|
||||
return Piece.NONE
|
||||
return self.grid[xy.y][xy.x]
|
||||
|
||||
def serialize(self) -> dict:
|
||||
return {"grid": self.grid}
|
||||
|
||||
|
||||
class Room:
|
||||
board: Board = Board()
|
||||
turn: Color = Color.WHITE
|
||||
players: list[uuid.UUID] = []
|
||||
last_move: datetime
|
||||
|
||||
def start(self):
|
||||
self.last_move = datetime.now()
|
||||
|
||||
def add_player(self) -> None | tuple[uuid.UUID, Color]:
|
||||
np: int = len(self.players)
|
||||
if np >= 2:
|
||||
return None
|
||||
elif np == 1:
|
||||
uid = uuid.uuid4()
|
||||
self.players.append(uid)
|
||||
return uid, Color.BLACK
|
||||
else:
|
||||
uid = uuid.uuid4()
|
||||
self.players.append(uid)
|
||||
return uid, Color.WHITE
|
||||
|
||||
|
||||
rooms: dict[str, Room] = {}
|
||||
|
||||
|
||||
@app.GET("/")
|
||||
async def home(request):
|
||||
return render("index.html")
|
||||
|
||||
|
||||
@app.POST("/join/<id>")
|
||||
async def join(request, room_id):
|
||||
room: Room = rooms.get(room_id, Room())
|
||||
player = room.add_player()
|
||||
if player:
|
||||
return JSONResponse(
|
||||
{
|
||||
"code": "JOIN",
|
||||
"id": player[0],
|
||||
"color": player[1],
|
||||
"board": room.board.serialize(),
|
||||
}
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
{"code": "FULL", "error": "Room Full", "board": room.board.serialize()}
|
||||
)
|
||||
|
||||
|
||||
def parse(body: str) -> dict | None:
|
||||
try:
|
||||
return json.loads(body)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
@app.POST("/move/<id>")
|
||||
@JSONAPI
|
||||
async def move(request, room_id):
|
||||
req = parse(request.body)
|
||||
if not req:
|
||||
return 400, {}
|
||||
if "uid" not in req or not isinstance(req["uid"], str):
|
||||
return 400, {}
|
||||
if "from" not in req or not isinstance(req["from"], str):
|
||||
return 400, {}
|
||||
if "to" not in req or not isinstance(req["to"], str):
|
||||
return 400, {}
|
||||
if room_id not in rooms:
|
||||
return 400, {"code": "NOEX", "error": "Room does not exist"}
|
||||
room = rooms[room_id]
|
||||
if len(room.players) != 2:
|
||||
return 400, {"code": "PRES", "error": "Game has not been started"}
|
||||
board = room.board
|
||||
uid = req["uid"]
|
||||
src = req["from"].lower()
|
||||
dst = req["to"].lower()
|
||||
color = Color.WHITE if room.players[0] == uid else Color.BLACK
|
||||
turn = room.turn == color
|
||||
if not turn:
|
||||
return 400, {"code": "OTHR", "error": "Not your turn."}
|
||||
if (
|
||||
len(src) != 2
|
||||
or len(dst) != 2
|
||||
or src[0] not in "abcdefgh"
|
||||
or src[1] not in "12345678"
|
||||
or dst[0] not in "abcdefgh"
|
||||
or dst[1] not in "12345678"
|
||||
or board.index(src) != Piece.EMPTY
|
||||
):
|
||||
return 400, {"code": "LMOV", "error": "Bad Move"}
|
||||
srcp = board.index(src)
|
||||
dstp = board.index(dst)
|
||||
piece_kind = srcp.value.lower()
|
||||
is_white = srcp.value.isupper()
|
||||
if (is_white and color == Color.BLACK) or (not is_white and color == Color.WHITE):
|
||||
return 400, {"code": "NOTU", "error": "Not your piece"}
|
||||
if dstp != Piece.EMPTY and (
|
||||
(dstp.value.isupper() and color == Color.WHITE)
|
||||
or (dstp.value.islower() and color == Color.BLACK)
|
||||
):
|
||||
return 400, {"code": "HTME", "error": "Cannot move to your own piece"}
|
||||
valids: list[Coord] = []
|
||||
if piece_kind == "p":
|
||||
dir = 1 if is_white else -1
|
||||
x, y = coord_to_xy(src)
|
||||
if board.index_xy(x, y + dir) == Piece.EMPTY:
|
||||
valids.append({"x": x, "y": y + dir})
|
||||
if (y == 1 or y == 6) and board.index_xy(x, y + 2 * dir) == Piece.EMPTY:
|
||||
valids.append({"x": x, "y": y + 2 * dir})
|
||||
if (
|
||||
board.index_xy(x + 1, y + dir)
|
||||
not in [Piece.EMPTY, Piece.NONE, Piece.BLACK_KING, Piece.WHITE_KING]
|
||||
and board.index_xy(x + 1, y + dir).value.upper() != is_white
|
||||
):
|
||||
valids.append({"x": x + 1, "y": y + 1})
|
||||
if (
|
||||
board.index_xy(x - 1, y + dir)
|
||||
not in [Piece.EMPTY, Piece.NONE, Piece.BLACK_KING, Piece.WHITE_KING]
|
||||
and board.index_xy(x - 1, y + dir).value.upper() != is_white
|
||||
):
|
||||
valids.append({"x": x - 1, "y": y + 1})
|
||||
if piece_kind == "b":
|
||||
x, y = coord_to_xy(src)
|
||||
comb = combinations([1, -1], 2)
|
||||
for dir in comb:
|
||||
scl = 1
|
||||
target: Coord = {"x": x + dir[0] * scl, "y": y + dir[1] * scl}
|
||||
while board.index_xy(target) not in [Piece.EMPTY, Piece.NONE]:
|
||||
valids.append(target.copy())
|
||||
scl += 1
|
||||
target: Coord = {"x": x + dir[0] * scl, "y": y + dir[1] * scl}
|
||||
if (
|
||||
p := board.index_xy(target)
|
||||
) != Piece.NONE and p.value.isupper() != is_white:
|
||||
valids.append(target.copy())
|
||||
if piece_kind == "q":
|
||||
x, y = coord_to_xy(src)
|
||||
comb = combinations([1, -1, 0], 2)
|
||||
for dir in comb:
|
||||
if dir[0] ** 2 + dir[1] ** 2 == 0:
|
||||
continue # x=0 y=0 cannot move => invalid
|
||||
scl = 1
|
||||
target: Coord = {"x": x + dir[0] * scl, "y": y + dir[1] * scl}
|
||||
while board.index_xy(target) not in [Piece.EMPTY, Piece.NONE]:
|
||||
valids.append(target.copy())
|
||||
scl += 1
|
||||
target: Coord = {"x": x + dir[0] * scl, "y": y + dir[1] * scl}
|
||||
if (
|
||||
p := board.index_xy(target)
|
||||
) != Piece.NONE and p.value.isupper() != is_white:
|
||||
valids.append(target.copy())
|
||||
if piece_kind == "c":
|
||||
x, y = coord_to_xy(src)
|
||||
comb = [(-1, 0), (1, 0), (0, -1), (0, 1)]
|
||||
for dir in comb:
|
||||
scl = 1
|
||||
target: Coord = {"x": x + dir[0] * scl, "y": y + dir[1] * scl}
|
||||
while board.index_xy(target) not in [Piece.EMPTY, Piece.NONE]:
|
||||
valids.append(target.copy())
|
||||
scl += 1
|
||||
target: Coord = {"x": x + dir[0] * scl, "y": y + dir[1] * scl}
|
||||
if (
|
||||
p := board.index_xy(target)
|
||||
) != Piece.NONE and p.value.isupper() != is_white:
|
||||
valids.append(target.copy())
|
||||
if piece_kind == "k":
|
||||
x, y = coord_to_xy(src)
|
||||
comb = combinations([-1, 0, 1], 2)
|
||||
for dir in comb:
|
||||
if dir[0] ** 2 + dir[1] ** 2 == 0:
|
||||
continue # x=0 y=0 cannot move => invalid
|
||||
target: Coord = {"x": x + dir[0], "y": y + dir[1]}
|
||||
if (
|
||||
p := board.index_xy(target)
|
||||
) != Piece.NONE and p.value.isupper() != is_white:
|
||||
valids.append(target.copy())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(app.run(port=8080))
|
||||
Reference in New Issue
Block a user