Compare commits
8 Commits
bfe6c47d8e
...
83b570214a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83b570214a | ||
|
|
460255db9e | ||
|
|
56a01ac5a7 | ||
|
|
4f6a45cbc7 | ||
|
|
4a998bca84 | ||
|
|
cc9cf0f1c2 | ||
|
|
3d7508fc29 | ||
|
|
d2ed2b2842 |
62
app.py
62
app.py
@@ -2,7 +2,7 @@ import asyncio
|
|||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from itertools import product
|
from itertools import product
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -166,13 +166,13 @@ class Room:
|
|||||||
self.turn = Color.WHITE
|
self.turn = Color.WHITE
|
||||||
self.board = Board()
|
self.board = Board()
|
||||||
self.players = []
|
self.players = []
|
||||||
self.last_move = datetime.now()
|
self.last_move = datetime.now(timezone.utc)
|
||||||
self.game_start = None
|
self.game_start = None
|
||||||
self.state = State.NOT_FINISHED
|
self.state = State.NOT_FINISHED
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.last_move = datetime.now()
|
self.last_move = datetime.now(timezone.utc)
|
||||||
self.game_start = datetime.now()
|
self.game_start = datetime.now(timezone.utc)
|
||||||
|
|
||||||
def add_player(self) -> None | tuple[str, Color]:
|
def add_player(self) -> None | tuple[str, Color]:
|
||||||
np: int = len(self.players)
|
np: int = len(self.players)
|
||||||
@@ -192,6 +192,20 @@ class Room:
|
|||||||
rooms: dict[str, Room] = {}
|
rooms: dict[str, Room] = {}
|
||||||
|
|
||||||
|
|
||||||
|
favicon = Path("favicon.ico").read_bytes()
|
||||||
|
favicon_update = Path("favicon_update.ico").read_bytes()
|
||||||
|
|
||||||
|
|
||||||
|
@app.GET("/favicon.ico")
|
||||||
|
async def favicon_s(request):
|
||||||
|
return HTTPResponse(request, favicon, content_type="image/x-icon")
|
||||||
|
|
||||||
|
|
||||||
|
@app.GET("/favicon_update.ico")
|
||||||
|
async def favicon_update_s(request):
|
||||||
|
return HTTPResponse(request, favicon_update, content_type="image/x-icon")
|
||||||
|
|
||||||
|
|
||||||
@app.GET("/")
|
@app.GET("/")
|
||||||
async def home(request):
|
async def home(request):
|
||||||
return render(request, "index.html")
|
return render(request, "index.html")
|
||||||
@@ -218,12 +232,25 @@ async def join(request, room_id):
|
|||||||
"turn": room.turn.value,
|
"turn": room.turn.value,
|
||||||
"board": room.board.serialize(),
|
"board": room.board.serialize(),
|
||||||
"ready": len(room.players) == 2,
|
"ready": len(room.players) == 2,
|
||||||
|
"state": room.state.value,
|
||||||
|
"start_time": (
|
||||||
|
room.game_start or datetime.now(timezone.utc)
|
||||||
|
).isoformat(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
request,
|
request,
|
||||||
{"code": "FULL", "error": "Room Full", "board": room.board.serialize()},
|
{
|
||||||
|
"code": "FULL",
|
||||||
|
"error": "Room Full",
|
||||||
|
"board": room.board.serialize(),
|
||||||
|
"turn": room.turn.value,
|
||||||
|
"state": room.state.value,
|
||||||
|
"start_time": (
|
||||||
|
room.game_start or datetime.now(timezone.utc)
|
||||||
|
).isoformat(),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -392,6 +419,8 @@ async def move(request: Request, room_id):
|
|||||||
room = rooms[room_id]
|
room = rooms[room_id]
|
||||||
if len(room.players) != 2:
|
if len(room.players) != 2:
|
||||||
return 400, {"code": "PRES", "error": "Game has not been started"}
|
return 400, {"code": "PRES", "error": "Game has not been started"}
|
||||||
|
if room.state != State.NOT_FINISHED:
|
||||||
|
return 400, {"code": "FINI", "error": "Game has not finished."}
|
||||||
board = room.board
|
board = room.board
|
||||||
uid = req["uid"]
|
uid = req["uid"]
|
||||||
src = req["from"].lower()
|
src = req["from"].lower()
|
||||||
@@ -429,11 +458,27 @@ async def move(request: Request, room_id):
|
|||||||
if c.y == 0 or c.y == 7 and piece_kind == "p":
|
if c.y == 0 or c.y == 7 and piece_kind == "p":
|
||||||
board.grid[c.y][c.x] = Piece.WHITE_QUEEN if is_white else Piece.BLACK_QUEEN
|
board.grid[c.y][c.x] = Piece.WHITE_QUEEN if is_white else Piece.BLACK_QUEEN
|
||||||
room.turn = Color.BLACK if color == Color.WHITE else Color.WHITE
|
room.turn = Color.BLACK if color == Color.WHITE else Color.WHITE
|
||||||
room.last_move = datetime.now()
|
room.last_move = datetime.now(timezone.utc)
|
||||||
|
opp_checkmate = True
|
||||||
|
for i in range(8):
|
||||||
|
for j in range(8):
|
||||||
|
P = board.index_xy(i, j)
|
||||||
|
if P != "E" and P.value.isupper() != is_white:
|
||||||
|
moves = generate_valid_moves(
|
||||||
|
P.value.lower(), board, not is_white, xy_to_pos_safe(i, j)
|
||||||
|
)
|
||||||
|
if len(moves) > 0:
|
||||||
|
opp_checkmate = False
|
||||||
|
break
|
||||||
|
if not opp_checkmate:
|
||||||
|
break
|
||||||
|
if opp_checkmate:
|
||||||
|
room.state = State.BLACK_WIN if is_white else State.WHITE_WIN
|
||||||
return {
|
return {
|
||||||
"code": "MOVD",
|
"code": "MOVD",
|
||||||
"color": color.value,
|
"color": color.value,
|
||||||
"turn": room.turn.value,
|
"turn": room.turn.value,
|
||||||
|
"state": room.state.value,
|
||||||
"board": board.serialize(),
|
"board": board.serialize(),
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
@@ -445,11 +490,11 @@ async def move(request: Request, room_id):
|
|||||||
async def poll(request, room_id):
|
async def poll(request, room_id):
|
||||||
for key in rooms:
|
for key in rooms:
|
||||||
room = rooms[key]
|
room = rooms[key]
|
||||||
if (datetime.now() - room.last_move) >= timedelta(hours=24):
|
if (datetime.now(timezone.utc) - room.last_move) >= timedelta(hours=24):
|
||||||
del rooms[key]
|
del rooms[key]
|
||||||
if (
|
if (
|
||||||
room.game_start
|
room.game_start
|
||||||
and (datetime.now() - room.game_start) >= timedelta(minutes=30)
|
and (datetime.now(timezone.utc) - room.game_start) >= timedelta(minutes=30)
|
||||||
and room.state == State.NOT_FINISHED
|
and room.state == State.NOT_FINISHED
|
||||||
):
|
):
|
||||||
room.state = State.TIE
|
room.state = State.TIE
|
||||||
@@ -462,6 +507,7 @@ async def poll(request, room_id):
|
|||||||
"board": room.board.serialize(),
|
"board": room.board.serialize(),
|
||||||
"ready": len(room.players) == 2,
|
"ready": len(room.players) == 2,
|
||||||
"state": room.state.value,
|
"state": room.state.value,
|
||||||
|
"start_time": (room.game_start or datetime.now(timezone.utc)).isoformat(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
favicon_update.ico
Normal file
BIN
favicon_update.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
icon_default.png
Normal file
BIN
icon_default.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
BIN
icon_update.png
Normal file
BIN
icon_update.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
125
index.html
125
index.html
@@ -28,8 +28,32 @@
|
|||||||
|
|
||||||
.center {
|
.center {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
margin: 24px 0;
|
margin: 24px 0;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stats {
|
||||||
|
font-family: 'Times New Roman', Times, serif;
|
||||||
|
font-weight: 100;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clock {
|
||||||
|
font-family: 'Times New Roman', Times, serif;
|
||||||
|
font-weight: 100;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #000;
|
||||||
|
letter-spacing: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#turn {
|
||||||
|
border-top: 1px solid #68687aaa;
|
||||||
|
padding-top: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #68687aaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.join-menu {
|
.join-menu {
|
||||||
@@ -80,6 +104,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<link rel="icon" href="/favicon.ico" type="image/x-icon">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -89,6 +114,9 @@
|
|||||||
<div class="center">
|
<div class="center">
|
||||||
<div id="join-menu" class="join-menu"><input type="text" id="room-id" placeholder="Room ID"><button
|
<div id="join-menu" class="join-menu"><input type="text" id="room-id" placeholder="Room ID"><button
|
||||||
id="join-submit">Join</button></div>
|
id="join-submit">Join</button></div>
|
||||||
|
<h2 id="stats"></h2>
|
||||||
|
<h2 id="clock"></h2>
|
||||||
|
<h4 id="turn"></h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<canvas class="game" id="canvas"></canvas>
|
<canvas class="game" id="canvas"></canvas>
|
||||||
@@ -112,11 +140,17 @@
|
|||||||
let mHouse = [0, 0];
|
let mHouse = [0, 0];
|
||||||
const textures = {};
|
const textures = {};
|
||||||
|
|
||||||
|
const piecemoveSound = new Audio("/static/piecemove.mp3");
|
||||||
|
//const endgameSound = new Audio("/static/endgame.mp3");
|
||||||
|
|
||||||
let color = 0;
|
let color = 0;
|
||||||
let turn = 0;
|
let turn = 0;
|
||||||
|
let state = -1;
|
||||||
|
let start_time = undefined;
|
||||||
let ready = false;
|
let ready = false;
|
||||||
let letterMap = ' ';
|
let letterMap = ' ';
|
||||||
let numberMap = ' ';
|
let numberMap = ' ';
|
||||||
|
let oldBoard = undefined;
|
||||||
let board = undefined;
|
let board = undefined;
|
||||||
let UID = undefined;
|
let UID = undefined;
|
||||||
let ROOM_ID = undefined;
|
let ROOM_ID = undefined;
|
||||||
@@ -124,6 +158,82 @@
|
|||||||
let selected = undefined;
|
let selected = undefined;
|
||||||
let moves = [];
|
let moves = [];
|
||||||
|
|
||||||
|
function handleVisibilityChange() {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
let link = document.querySelector("link[rel~='icon']");
|
||||||
|
|
||||||
|
link.href = "/favicon.ico";
|
||||||
|
link.type = "image/x-icon";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||||
|
|
||||||
|
function boardOnChange() {
|
||||||
|
if (JSON.stringify(board) !== JSON.stringify(oldBoard)) {
|
||||||
|
if (document.visibilityState !== 'visible') {
|
||||||
|
let link = document.querySelector("link[rel~='icon']");
|
||||||
|
|
||||||
|
link.href = "/favicon_update.ico";
|
||||||
|
link.type = "image/x-icon";
|
||||||
|
}
|
||||||
|
console.log("PLAYING SOUND!!");
|
||||||
|
piecemoveSound.currentTime = Math.round(Math.random() * 4) * 0.5;
|
||||||
|
piecemoveSound.play()
|
||||||
|
.then(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
piecemoveSound.pause();
|
||||||
|
}, 400);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("Playback failed:", error);
|
||||||
|
});
|
||||||
|
oldBoard = structuredClone(board);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setUI() {
|
||||||
|
if (ready) {
|
||||||
|
if (state == -1) {
|
||||||
|
document.getElementById('stats').textCotnent = "";
|
||||||
|
const now = new Date();
|
||||||
|
const differenceMs = Math.abs(start_time.getTime() - now.getTime());
|
||||||
|
const totalSeconds = Math.floor(differenceMs / 1000);
|
||||||
|
|
||||||
|
const minutes = Math.floor(totalSeconds / 60);
|
||||||
|
const seconds = totalSeconds % 60;
|
||||||
|
|
||||||
|
const paddedMinutes = String(minutes).padStart(2, '0');
|
||||||
|
const paddedSeconds = String(seconds).padStart(2, '0');
|
||||||
|
|
||||||
|
document.getElementById('clock').textCotnent = `${paddedMinutes}:${paddedSeconds}`;
|
||||||
|
|
||||||
|
if (UID === undefined || turn != color) {
|
||||||
|
if (turn == 0) {
|
||||||
|
document.getElementById('turn').textCotnent = "White's Turn";
|
||||||
|
} else if (turn == 0) {
|
||||||
|
document.getElementById('turn').textCotnent = "Black's Turn";
|
||||||
|
}
|
||||||
|
} else if (turn == color) {
|
||||||
|
document.getElementById('turn').textCotnent = "Your Turn";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
document.getElementById('turn').textCotnent = "";
|
||||||
|
if (state == 0) {
|
||||||
|
document.getElementById('stats').textCotnent = "Tie";
|
||||||
|
} else if (state == 1) {
|
||||||
|
document.getElementById('stats').textCotnent = "White Won";
|
||||||
|
} else if (state == 2) {
|
||||||
|
document.getElementById('stats').textCotnent = "Black Won";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
document.getElementById('turn').textCotnent = "";
|
||||||
|
document.getElementById('clock').textCotnent = "";
|
||||||
|
document.getElementById('stats').textCotnent = "Waiting for Opponent";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
canvas.addEventListener('mousemove', (e) => {
|
canvas.addEventListener('mousemove', (e) => {
|
||||||
const rect = canvas.getBoundingClientRect();
|
const rect = canvas.getBoundingClientRect();
|
||||||
const x = ((e.clientX - rect.left) / rect.width) * width;
|
const x = ((e.clientX - rect.left) / rect.width) * width;
|
||||||
@@ -131,7 +241,7 @@
|
|||||||
mouse = [x, y];
|
mouse = [x, y];
|
||||||
i = Math.floor((x / width) * 8);
|
i = Math.floor((x / width) * 8);
|
||||||
j = Math.floor((y / height) * 8);
|
j = Math.floor((y / height) * 8);
|
||||||
if (ready) {
|
if (ready && state == -1) {
|
||||||
canvas.style.cursor = 'default';
|
canvas.style.cursor = 'default';
|
||||||
if (board) {
|
if (board) {
|
||||||
if (color == 0) {
|
if (color == 0) {
|
||||||
@@ -157,7 +267,7 @@
|
|||||||
canvas.addEventListener('mouseup', (e) => {
|
canvas.addEventListener('mouseup', (e) => {
|
||||||
i = mHouse[0];
|
i = mHouse[0];
|
||||||
j = mHouse[1];
|
j = mHouse[1];
|
||||||
if (ready && board) {
|
if (state == -1 && ready && board) {
|
||||||
if (board[j][i] !== "E" && (board[j][i] === board[j][i].toUpperCase() ? 0 : 1) === color && turn == color) {
|
if (board[j][i] !== "E" && (board[j][i] === board[j][i].toUpperCase() ? 0 : 1) === color && turn == color) {
|
||||||
|
|
||||||
selected = [i, j];
|
selected = [i, j];
|
||||||
@@ -192,6 +302,7 @@
|
|||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
board = data.board.grid;
|
board = data.board.grid;
|
||||||
|
boardOnChange();
|
||||||
console.log(data);
|
console.log(data);
|
||||||
selected = undefined;
|
selected = undefined;
|
||||||
moves = [];
|
moves = [];
|
||||||
@@ -216,6 +327,11 @@
|
|||||||
ready = data.ready;
|
ready = data.ready;
|
||||||
turn = data.turn;
|
turn = data.turn;
|
||||||
board = data.board.grid;
|
board = data.board.grid;
|
||||||
|
boardOnChange();
|
||||||
|
state = data.state;
|
||||||
|
start_time = new Date(data.start_time);
|
||||||
|
|
||||||
|
setUI();
|
||||||
})
|
})
|
||||||
.catch((error) => console.error('Error:', error));
|
.catch((error) => console.error('Error:', error));
|
||||||
}
|
}
|
||||||
@@ -252,11 +368,14 @@
|
|||||||
color = data.color;
|
color = data.color;
|
||||||
ready = data.ready;
|
ready = data.ready;
|
||||||
turn = data.turn;
|
turn = data.turn;
|
||||||
|
state = data.state;
|
||||||
|
start_time = new Date(data.start_time);
|
||||||
}
|
}
|
||||||
ROOM_ID = rid;
|
ROOM_ID = rid;
|
||||||
board = data.board.grid;
|
board = data.board.grid;
|
||||||
numberMap = "12345678";
|
numberMap = "12345678";
|
||||||
letterMap = "abcdefgh";
|
letterMap = "abcdefgh";
|
||||||
|
setUI();
|
||||||
document.getElementById('join-menu').outerHTML = '';
|
document.getElementById('join-menu').outerHTML = '';
|
||||||
const urlID = extractIdFromPath();
|
const urlID = extractIdFromPath();
|
||||||
if (urlID == null) {
|
if (urlID == null) {
|
||||||
|
|||||||
BIN
static/endgame.mp3
Normal file
BIN
static/endgame.mp3
Normal file
Binary file not shown.
BIN
static/piecemove.mp3
Normal file
BIN
static/piecemove.mp3
Normal file
Binary file not shown.
Reference in New Issue
Block a user