diff --git a/README.md b/README.md
index c809474..8d5622d 100644
--- a/README.md
+++ b/README.md
@@ -12,18 +12,20 @@ python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python server.py # http://127.0.0.1:8080
.venv/bin/python server.py --host 0.0.0.0 --port 8080 # reachable on your LAN
-.venv/bin/python practice.py # vs the computer: http://127.0.0.1:8081
```
+Practice mode (vs the computer) is at `/practice` on the same server. The switch in the
+bottom-right corner of each page goes between the two modes.
+
Tests: `.venv/bin/python -m unittest discover -s tests -t .`
## How it works
- `game.py`: the rules, no networking.
-- `server.py`: one aiohttp process. `/ws?game=CODE` is the live connection, and
- everything else is static files.
-- `practice.py`: a separate server for playing against the computer. It holds no state:
- the page sends the position to `POST /api/play` and gets the next one back.
+- `server.py`: one aiohttp process. `/ws?game=CODE` is the live connection, `/practice`
+ and `POST /api/play` are practice mode, and everything else is static files.
+- `practice.py`: practice mode's move endpoint, mounted by `server.py`. It holds no
+ state: the page sends the position to `POST /api/play` and gets the next one back.
- `solver.py`: solves every reachable position (~67k, about a second at startup) so the
computer knows the perfect move everywhere.
- `static/`: the pages. Plain HTML/CSS/JS, no build step. `board.js` is the board
@@ -66,7 +68,6 @@ your home IP. WebSockets work through it without any extra configuration.
cloudflared tunnel login
cloudflared tunnel create moonchess
cloudflared tunnel route dns moonchess moonchess.example.com
- cloudflared tunnel route dns moonchess practice.moonchess.example.com # if running practice.py
```
3. `~/.cloudflared/config.yml`:
```yaml
@@ -75,8 +76,6 @@ your home IP. WebSockets work through it without any extra configuration.
ingress:
- hostname: moonchess.example.com
service: http://localhost:8080
- - hostname: practice.moonchess.example.com # optional, if you run practice.py
- service: http://localhost:8081
- service: http_status:404
```
4. `cloudflared tunnel run moonchess` (or `sudo cloudflared service install` to run it at boot).
diff --git a/practice.py b/practice.py
index acfbf7d..6db9c62 100644
--- a/practice.py
+++ b/practice.py
@@ -1,26 +1,18 @@
-"""Moonchess practice server: play against the computer.
+"""Moonchess practice mode: play against the computer. server.py serves it at /practice.
Stateless: the page sends the current position with every request and gets the new
position back, so there's no session to lose. The page decides whose turn belongs to
-the computer; this server only applies moves and picks them.
+the computer; this module only applies moves and picks them.
"""
-import argparse
-import logging
-
from aiohttp import web
from game import Game
-from server import STATIC_DIR, no_cache
from solver import Solver
solver = Solver() # solves every position up front; takes about a second
-async def index(request):
- return web.FileResponse(STATIC_DIR / "practice.html")
-
-
async def play(request):
"""Body: {history, turn, cell} to play `cell`, or {history, turn, accuracy} for a
computer move, where accuracy (0-1) is the chance it plays a perfect move. A computer
@@ -41,20 +33,3 @@ async def play(request):
if error:
return web.json_response({"error": error}, status=400)
return web.json_response({**game.to_dict(), "computerMove": computer_move})
-
-
-def make_app():
- app = web.Application(middlewares=[no_cache])
- app.router.add_get("/", index)
- app.router.add_post("/api/play", play)
- app.router.add_static("/static", STATIC_DIR)
- return app
-
-
-if __name__ == "__main__":
- parser = argparse.ArgumentParser(description="Moonchess practice server")
- parser.add_argument("--host", default="127.0.0.1")
- parser.add_argument("--port", type=int, default=8081)
- args = parser.parse_args()
- logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
- web.run_app(make_app(), host=args.host, port=args.port)
diff --git a/server.py b/server.py
index 25c84e7..8337757 100644
--- a/server.py
+++ b/server.py
@@ -17,6 +17,7 @@ from pathlib import Path
from aiohttp import WSMsgType, web
+import practice
from game import Game
STATIC_DIR = Path(__file__).parent / "static"
@@ -122,6 +123,10 @@ async def index(request):
return web.FileResponse(STATIC_DIR / "index.html")
+async def practice_page(request):
+ return web.FileResponse(STATIC_DIR / "practice.html")
+
+
async def game_socket(request):
code = normalize_code(request.query.get("game", ""))
ws = web.WebSocketResponse(max_msg_size=MAX_MESSAGE_SIZE)
@@ -229,6 +234,8 @@ def make_app():
app = web.Application(middlewares=[no_cache])
app.router.add_get("/", index)
app.router.add_get("/ws", game_socket)
+ app.router.add_get("/practice", practice_page)
+ app.router.add_post("/api/play", practice.play)
app.router.add_static("/static", STATIC_DIR)
app.cleanup_ctx.append(start_reaper)
return app
diff --git a/static/index.html b/static/index.html
index 2bc15a7..a8dd4da 100644
--- a/static/index.html
+++ b/static/index.html
@@ -46,6 +46,7 @@
+ Practice