Compare commits

..

6 Commits

Author SHA1 Message Date
0880
c367ebad1f Quick matchmaking API 2026-01-20 19:57:38 +03:30
0880
17d66398b4 imports 2026-01-20 19:57:19 +03:30
0880
4240312603 Don't allow automatic room creation 2026-01-20 19:57:01 +03:30
0880
5c268911b4 Add home page 2026-01-20 19:55:31 +03:30
0880
2599f50484 redirect 2026-01-20 19:55:17 +03:30
0880
e220431041 Move index.html to game.html 2026-01-20 19:54:57 +03:30
5 changed files with 278 additions and 8 deletions

98
app.py
View File

@@ -1,6 +1,10 @@
import asyncio import asyncio
import json import json
import random
import re
import string
import uuid import uuid
from collections import deque
from collections.abc import Iterator from collections.abc import Iterator
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from enum import Enum from enum import Enum
@@ -8,7 +12,15 @@ from itertools import product
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
from libs.slow import JSONAPI, App, HTTPResponse, JSONResponse, Request, render from libs.slow import (
JSONAPI,
App,
HTTPResponse,
JSONResponse,
Request,
redirect,
render,
)
class Coord: class Coord:
@@ -206,20 +218,96 @@ async def favicon_update_s(request):
return HTTPResponse(request, favicon_update, content_type="image/x-icon") return HTTPResponse(request, favicon_update, content_type="image/x-icon")
letters = string.ascii_lowercase + string.digits
def room_key():
key: str = "".join(random.choices(letters, k=4))
while key in rooms.keys():
key: str = "".join(random.choices(letters, k=4))
return key
@app.POST("/create_room")
async def new_room(request):
if len(rooms) == len(letters) ** 4:
return HTTPResponse(request, "Out of service", status=501)
key = room_key()
rooms[key] = Room()
return JSONResponse(
request,
{
"id": key,
},
)
quick_queue: deque[str]
quick_map: dict[str, Room]
lock = asyncio.Lock()
@app.POST("/quick")
async def quick_match(request: Request):
global quick_queue, quick_map
data = parse(request.body)
async with lock:
if (
data
and len(data) == 1
and "queue_id" in data
and data["queue_id"] in quick_queue
):
first = None
second = None
position: int = 0
# UPDATE LOGIC
while not second and position < len(quick_queue):
if quick_queue[position] not in quick_map:
if not first:
first = quick_queue[position]
else:
second = quick_queue[position]
position += 1
if first and second:
room = Room()
k = room_key()
rooms[k] = room
quick_map[first] = room
quick_map[second] = room
qid = data["queue_id"]
if qid in quick_map:
return JSONResponse(request, {"room_id": quick_map[qid]})
else:
return JSONResponse(
request, {}
) # Client handles empty as continue to wait
else:
qid = str(uuid.uuid4())
quick_queue.append(qid)
return JSONResponse(request, {"queue_id": qid})
@app.GET("/") @app.GET("/")
async def home(request): async def home(request):
return render(request, "index.html") return render(request, "home.html")
@app.GET("/<id>") @app.GET("/<id>")
async def home_with_id(request, id): async def game(request, id):
return render(request, "index.html") if not re.match(r"[a-z0-9]{4}", id):
return redirect("/")
if id not in rooms:
return redirect("/")
return render(request, "game.html")
@app.POST("/join/<room_id>") @app.POST("/join/<room_id>")
async def join(request, room_id): async def join(request, room_id):
if room_id not in rooms: if room_id not in rooms:
rooms[room_id] = Room() return JSONResponse(request, {"code": "NOGO", "error": "Room not found."}, 404)
room: Room = rooms[room_id] room: Room = rooms[room_id]
player = room.add_player() player = room.add_player()
if player: if player:

View File

@@ -112,8 +112,8 @@
<img src="/static/logo.webp" alt=""> <img src="/static/logo.webp" alt="">
</header> </header>
<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" style="display: none;"><input type="text" id="room-id"
id="join-submit">Join</button></div> placeholder="Room ID"><button id="join-submit">Join</button></div>
<h2 id="stats"></h2> <h2 id="stats"></h2>
<h2 id="clock"></h2> <h2 id="clock"></h2>
<h4 id="turn"></h4> <h4 id="turn"></h4>

182
home.html Normal file
View File

@@ -0,0 +1,182 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>شرطنج</title>
<style>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
font-family: Arial, Helvetica, sans-serif;
color: 262942;
}
body {
background-color: #f8e8d3;
}
header {
display: flex;
justify-content: center;
width: 100vw;
user-select: none;
height: 230px;
}
.center {
display: flex;
flex-direction: column;
align-items: center;
margin: 24px auto;
gap: 8px;
max-width: 70%;
}
.or {
width: 100%;
position: relative;
margin: 10px 0;
}
.or::before {
content: '';
position: absolute;
top: 50%;
left: 0;
width: 100%;
height: 2px;
transform: translateY(-50%);
background: rgba(92, 92, 92, 0.664);
}
.or::after {
content: 'OR';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
font-size: 1rem;
padding: 8px;
font-weight: bolder;
background: #f8e8d3;
color: rgba(92, 92, 92, 0.664);
}
.code {
padding: 16px;
border: none;
border-bottom: 2px solid rgba(92, 92, 92, 0.664);
font-size: 1.5rem;
text-align: center;
letter-spacing: 8px;
text-transform: lowercase;
width: 200px;
outline: none;
}
.button {
background: rgb(85, 122, 75);
color: rgb(255, 255, 255);
font-family: 'Times New Roman', Times, serif;
font-weight: lighter;
text-transform: uppercase;
font-size: 1.3rem;
padding: 12px 30px;
cursor: pointer;
border: 0;
outline: none;
}
.group {
display: flex;
height: 60px;
gap: 4px;
}
.group * {
height: 60px;
margin-bottom: 4px;
}
.vs {
height: calc(100% - 8px);
width: 2px;
margin: 4px;
background: rgba(92, 92, 92, 0.664);
}
.code::placeholder {
letter-spacing: normal;
}
.background {
width: 100%;
aspect-ratio: 16/9;
z-index: -1000;
bottom: 0;
position: fixed;
image-rendering: pixelated;
background-image: url("/static/background.png");
background-size: contain;
background-repeat: no-repeat;
}
</style>
<link rel="icon" href="/favicon.ico" type="image/x-icon">
</head>
<body>
<header>
<img src="/static/logo.webp" alt="">
</header>
<div class="background"></div>
<div class="center">
<div class="group">
<button id="create" class="button">create</button>
<div class="vs"></div>
<input type="text" id="code" class="code" placeholder="invite code" pattern="[a-z0-9A-Z]{4}" maxlength="4">
<button id="join" class="button">join</button>
</div>
<div class="or"></div>
Public games not added yet.
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const code = document.getElementById("code");
const pat = new RegExp("^[a-z0-9]{4}$")
document.getElementById("join").addEventListener("click", () => {
if (!reg.test(code.value)) {
alert("Invalid ID");
} else {
window.location.href = window.location.origin + "/" + code.value;
}
});
document.getElementById("create").addEventListener("click", () => {
fetch('/create_room', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
})
.then((response) => response.json())
.then((data) => {
window.location.href = window.location.origin + "/" + data.id;
}).catch((error) => {
console.log(error);
})
});
});
</script>
</body>
</html>

2
libs

Submodule libs updated: 3164c446c6...24334d1def

BIN
static/background.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 KiB