Compare commits

...

8 Commits

Author SHA1 Message Date
0880
83b570214a Finishing game 2026-01-18 19:07:25 +03:30
0880
460255db9e Send UTC time 2026-01-18 19:06:57 +03:30
0880
56a01ac5a7 Handle finishing game 2026-01-18 19:06:03 +03:30
0880
4f6a45cbc7 UI Update 2026-01-18 19:05:38 +03:30
0880
4a998bca84 UI & SFX 2026-01-18 19:05:26 +03:30
0880
cc9cf0f1c2 Favicon 2026-01-18 19:04:57 +03:30
0880
3d7508fc29 SFX 2026-01-18 19:04:45 +03:30
0880
d2ed2b2842 Favicon 2026-01-18 19:03:06 +03:30
8 changed files with 176 additions and 11 deletions

62
app.py
View File

@@ -2,7 +2,7 @@ import asyncio
import json
import uuid
from collections.abc import Iterator
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from enum import Enum
from itertools import product
from pathlib import Path
@@ -166,13 +166,13 @@ class Room:
self.turn = Color.WHITE
self.board = Board()
self.players = []
self.last_move = datetime.now()
self.last_move = datetime.now(timezone.utc)
self.game_start = None
self.state = State.NOT_FINISHED
def start(self):
self.last_move = datetime.now()
self.game_start = datetime.now()
self.last_move = datetime.now(timezone.utc)
self.game_start = datetime.now(timezone.utc)
def add_player(self) -> None | tuple[str, Color]:
np: int = len(self.players)
@@ -192,6 +192,20 @@ class 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("/")
async def home(request):
return render(request, "index.html")
@@ -218,12 +232,25 @@ async def join(request, room_id):
"turn": room.turn.value,
"board": room.board.serialize(),
"ready": len(room.players) == 2,
"state": room.state.value,
"start_time": (
room.game_start or datetime.now(timezone.utc)
).isoformat(),
},
)
else:
return JSONResponse(
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]
if len(room.players) != 2:
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
uid = req["uid"]
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":
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.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 {
"code": "MOVD",
"color": color.value,
"turn": room.turn.value,
"state": room.state.value,
"board": board.serialize(),
}
else:
@@ -445,11 +490,11 @@ async def move(request: Request, room_id):
async def poll(request, room_id):
for key in rooms:
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]
if (
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
):
room.state = State.TIE
@@ -462,6 +507,7 @@ async def poll(request, room_id):
"board": room.board.serialize(),
"ready": len(room.players) == 2,
"state": room.state.value,
"start_time": (room.game_start or datetime.now(timezone.utc)).isoformat(),
}

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
favicon_update.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
icon_default.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icon_update.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -28,8 +28,32 @@
.center {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
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 {
@@ -80,6 +104,7 @@
}
}
</style>
<link rel="icon" href="/favicon.ico" type="image/x-icon">
</head>
<body>
@@ -89,6 +114,9 @@
<div class="center">
<div id="join-menu" class="join-menu"><input type="text" id="room-id" placeholder="Room ID"><button
id="join-submit">Join</button></div>
<h2 id="stats"></h2>
<h2 id="clock"></h2>
<h4 id="turn"></h4>
</div>
<div class="center">
<canvas class="game" id="canvas"></canvas>
@@ -112,11 +140,17 @@
let mHouse = [0, 0];
const textures = {};
const piecemoveSound = new Audio("/static/piecemove.mp3");
//const endgameSound = new Audio("/static/endgame.mp3");
let color = 0;
let turn = 0;
let state = -1;
let start_time = undefined;
let ready = false;
let letterMap = ' ';
let numberMap = ' ';
let oldBoard = undefined;
let board = undefined;
let UID = undefined;
let ROOM_ID = undefined;
@@ -124,6 +158,82 @@
let selected = undefined;
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) => {
const rect = canvas.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * width;
@@ -131,7 +241,7 @@
mouse = [x, y];
i = Math.floor((x / width) * 8);
j = Math.floor((y / height) * 8);
if (ready) {
if (ready && state == -1) {
canvas.style.cursor = 'default';
if (board) {
if (color == 0) {
@@ -157,7 +267,7 @@
canvas.addEventListener('mouseup', (e) => {
i = mHouse[0];
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) {
selected = [i, j];
@@ -192,6 +302,7 @@
.then((response) => response.json())
.then((data) => {
board = data.board.grid;
boardOnChange();
console.log(data);
selected = undefined;
moves = [];
@@ -216,6 +327,11 @@
ready = data.ready;
turn = data.turn;
board = data.board.grid;
boardOnChange();
state = data.state;
start_time = new Date(data.start_time);
setUI();
})
.catch((error) => console.error('Error:', error));
}
@@ -252,11 +368,14 @@
color = data.color;
ready = data.ready;
turn = data.turn;
state = data.state;
start_time = new Date(data.start_time);
}
ROOM_ID = rid;
board = data.board.grid;
numberMap = "12345678";
letterMap = "abcdefgh";
setUI();
document.getElementById('join-menu').outerHTML = '';
const urlID = extractIdFromPath();
if (urlID == null) {

BIN
static/endgame.mp3 Normal file

Binary file not shown.

BIN
static/piecemove.mp3 Normal file

Binary file not shown.