Store game in session instead of cookie
Browse files- .gitignore +3 -1
- Dockerfile +2 -2
- pyproject.toml +1 -1
- src/wordle/game.py +1 -1
- src/wordle/tw.py +9 -2
- src/wordle/ui.py +31 -19
- uv.lock +1 -1
.gitignore
CHANGED
@@ -1,4 +1,6 @@
|
|
1 |
.venv
|
2 |
app.css
|
3 |
.sesskey
|
4 |
-
__pycache__
|
|
|
|
|
|
1 |
.venv
|
2 |
app.css
|
3 |
.sesskey
|
4 |
+
__pycache__
|
5 |
+
.vscode/
|
6 |
+
tailwind.config.js
|
Dockerfile
CHANGED
@@ -15,6 +15,6 @@ COPY --chown=user src ./src
|
|
15 |
RUN touch README.md
|
16 |
|
17 |
RUN uv sync --frozen
|
18 |
-
RUN . .venv/bin/activate
|
19 |
|
20 |
-
CMD ["uv", "run", "python", "src/wordle/ui.py"]
|
|
|
|
15 |
RUN touch README.md
|
16 |
|
17 |
RUN uv sync --frozen
|
|
|
18 |
|
19 |
+
# CMD ["uv", "run", "python", "src/wordle/ui.py"]
|
20 |
+
CMD ["uv", "run", "uvicorn", "--port", "5001", "--host", "0.0.0.0", "wordle.ui:app"]
|
pyproject.toml
CHANGED
@@ -5,7 +5,7 @@ description = "A simple wordle clone with fasthtml"
|
|
5 |
readme = "README.md"
|
6 |
requires-python = ">=3.10"
|
7 |
dependencies = [
|
8 |
-
"python-fasthtml>=0.4.
|
9 |
]
|
10 |
|
11 |
# [tool.uv.sources]
|
|
|
5 |
readme = "README.md"
|
6 |
requires-python = ">=3.10"
|
7 |
dependencies = [
|
8 |
+
"python-fasthtml>=0.4.6",
|
9 |
]
|
10 |
|
11 |
# [tool.uv.sources]
|
src/wordle/game.py
CHANGED
@@ -68,7 +68,7 @@ class Game:
|
|
68 |
# Return updated squares and keys
|
69 |
def keypress(self, key: str):
|
70 |
keys = []
|
71 |
-
if key == "
|
72 |
word = self.current
|
73 |
squares = self._enter()
|
74 |
keys = word if squares else []
|
|
|
68 |
# Return updated squares and keys
|
69 |
def keypress(self, key: str):
|
70 |
keys = []
|
71 |
+
if key == "GO":
|
72 |
word = self.current
|
73 |
squares = self._enter()
|
74 |
keys = word if squares else []
|
src/wordle/tw.py
CHANGED
@@ -11,6 +11,7 @@ app, rt = fast_app(hdrs=[
|
|
11 |
)
|
12 |
|
13 |
# Add public/app.css to your .gitignore
|
|
|
14 |
```
|
15 |
|
16 |
Acknowledgement: This code is heavily inspired by the [pytailwindcss](https://github.com/timonweb/pytailwindcss) project.
|
@@ -54,7 +55,13 @@ class Tailwind:
|
|
54 |
`public/app.css` should be in .gitignore
|
55 |
"""
|
56 |
|
57 |
-
def __init__(
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
self.dir = tempfile.TemporaryDirectory()
|
59 |
path = Path(self.dir.name)
|
60 |
if static_path is None:
|
@@ -81,7 +88,7 @@ class Tailwind:
|
|
81 |
)
|
82 |
subprocess.run([str(cli)] + args, **kwargs)
|
83 |
return self
|
84 |
-
|
85 |
def get_link_tag(self):
|
86 |
from fasthtml.common import Link
|
87 |
|
|
|
11 |
)
|
12 |
|
13 |
# Add public/app.css to your .gitignore
|
14 |
+
# serve(reload_excludes=["public/app.css"])
|
15 |
```
|
16 |
|
17 |
Acknowledgement: This code is heavily inspired by the [pytailwindcss](https://github.com/timonweb/pytailwindcss) project.
|
|
|
55 |
`public/app.css` should be in .gitignore
|
56 |
"""
|
57 |
|
58 |
+
def __init__(
|
59 |
+
self,
|
60 |
+
static_path=None,
|
61 |
+
filename="app.css",
|
62 |
+
cfg: str = DEFAULT_CONFIG,
|
63 |
+
css: str = DEFAULT_SOURCE_CSS,
|
64 |
+
):
|
65 |
self.dir = tempfile.TemporaryDirectory()
|
66 |
path = Path(self.dir.name)
|
67 |
if static_path is None:
|
|
|
88 |
)
|
89 |
subprocess.run([str(cli)] + args, **kwargs)
|
90 |
return self
|
91 |
+
|
92 |
def get_link_tag(self):
|
93 |
from fasthtml.common import Link
|
94 |
|
src/wordle/ui.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
from fasthtml.common import fast_app, Div, serve, Button,
|
2 |
from wordle.tw import Tailwind
|
3 |
from wordle.game import Eval, Game, State
|
4 |
|
@@ -6,18 +6,22 @@ from wordle.game import Eval, Game, State
|
|
6 |
app, rt = fast_app(hdrs=[Tailwind("./").run().get_link_tag()], pico=False, static_path="./")
|
7 |
|
8 |
|
9 |
-
@
|
10 |
-
def
|
11 |
-
|
|
|
|
|
12 |
|
13 |
|
14 |
-
@
|
15 |
-
def
|
16 |
-
|
|
|
|
|
17 |
|
18 |
|
19 |
-
def make_app(g:
|
20 |
-
return
|
21 |
Div(
|
22 |
H1(Button("WORDLE", hx_post="/new"), cls="text-3xl font-bold"),
|
23 |
cls="w-full flex flex-row place-content-center text-black",
|
@@ -41,18 +45,25 @@ def make_app(g: "Game"):
|
|
41 |
|
42 |
|
43 |
@rt("/keypress")
|
44 |
-
def put(
|
45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
squares, keys = g.keypress(key)
|
|
|
47 |
return (
|
48 |
-
cookie("data", g.to_str()),
|
49 |
*[make_square(g, i) for i in squares],
|
50 |
*[make_key(g, k) for k in keys],
|
51 |
make_status(g),
|
52 |
)
|
53 |
|
54 |
|
55 |
-
def make_status(g:
|
56 |
msgs = {State.WIN: "You win", State.LOSE: "You lose"}
|
57 |
return Div(
|
58 |
msgs.get(g.state),
|
@@ -62,7 +73,7 @@ def make_status(g: "Game"):
|
|
62 |
)
|
63 |
|
64 |
|
65 |
-
def make_square(g:
|
66 |
cls = "grid h-12 w-12 sm:h-14 sm:w-14 place-items-center rounded-sm text-2xl font-bold"
|
67 |
c, state = g.get_square_state(i)
|
68 |
styles = {
|
@@ -75,10 +86,10 @@ def make_square(g: "Game", i):
|
|
75 |
return Div(c, cls=cls + " " + styles[state], id=f"sq{i}", hx_swap_oob="true")
|
76 |
|
77 |
|
78 |
-
def make_key(g:
|
79 |
state = g.get_keyboard_state(key)
|
80 |
-
cls = "grid h-14 cursor-pointer items-center rounded font-semibold"
|
81 |
-
size = "
|
82 |
styles = {
|
83 |
Eval.CORRECT: GREEN,
|
84 |
Eval.EXIST: YELLOW,
|
@@ -93,6 +104,7 @@ def make_key(g: "Game", key: str):
|
|
93 |
|
94 |
|
95 |
GREEN, YELLOW, GRAY = "bg-[#20AA57]", "bg-[#E5B22D]", "bg-[#989898]"
|
96 |
-
KEYBOARD = [list("QWERTYUIOP"), list("ASDFGHJKL"), ["
|
97 |
|
98 |
-
|
|
|
|
1 |
+
from fasthtml.common import fast_app, Div, serve, Button, H1
|
2 |
from wordle.tw import Tailwind
|
3 |
from wordle.game import Eval, Game, State
|
4 |
|
|
|
6 |
app, rt = fast_app(hdrs=[Tailwind("./").run().get_link_tag()], pico=False, static_path="./")
|
7 |
|
8 |
|
9 |
+
@app.get("/")
|
10 |
+
def homepage(session):
|
11 |
+
if data := session.get("game"):
|
12 |
+
return make_app(Game.from_str(data))
|
13 |
+
return new_game.__wrapped__(session)
|
14 |
|
15 |
|
16 |
+
@app.post("/new")
|
17 |
+
def new_game(session):
|
18 |
+
g = Game.from_str(None)
|
19 |
+
session["game"] = g.to_str()
|
20 |
+
return make_app(g)
|
21 |
|
22 |
|
23 |
+
def make_app(g: Game):
|
24 |
+
return Div(
|
25 |
Div(
|
26 |
H1(Button("WORDLE", hx_post="/new"), cls="text-3xl font-bold"),
|
27 |
cls="w-full flex flex-row place-content-center text-black",
|
|
|
45 |
|
46 |
|
47 |
@rt("/keypress")
|
48 |
+
def put(session, key: str):
|
49 |
+
if "game" not in session:
|
50 |
+
return Div(
|
51 |
+
"The app does not work inside iframe. Try it here https://phihung-wordle.hf.space/",
|
52 |
+
cls="h-screen flex items-center justify-center text-xl",
|
53 |
+
hx_swap_oob="true",
|
54 |
+
id="app",
|
55 |
+
)
|
56 |
+
g = Game.from_str(session["game"])
|
57 |
squares, keys = g.keypress(key)
|
58 |
+
session["game"] = g.to_str()
|
59 |
return (
|
|
|
60 |
*[make_square(g, i) for i in squares],
|
61 |
*[make_key(g, k) for k in keys],
|
62 |
make_status(g),
|
63 |
)
|
64 |
|
65 |
|
66 |
+
def make_status(g: Game):
|
67 |
msgs = {State.WIN: "You win", State.LOSE: "You lose"}
|
68 |
return Div(
|
69 |
msgs.get(g.state),
|
|
|
73 |
)
|
74 |
|
75 |
|
76 |
+
def make_square(g: Game, i):
|
77 |
cls = "grid h-12 w-12 sm:h-14 sm:w-14 place-items-center rounded-sm text-2xl font-bold"
|
78 |
c, state = g.get_square_state(i)
|
79 |
styles = {
|
|
|
86 |
return Div(c, cls=cls + " " + styles[state], id=f"sq{i}", hx_swap_oob="true")
|
87 |
|
88 |
|
89 |
+
def make_key(g: Game, key: str):
|
90 |
state = g.get_keyboard_state(key)
|
91 |
+
cls = "grid h-14 cursor-pointer items-center rounded font-semibold touch-manipulation"
|
92 |
+
size = " w-14 sm:w-16 " if len(key) > 1 else " w-9 sm:w-10 "
|
93 |
styles = {
|
94 |
Eval.CORRECT: GREEN,
|
95 |
Eval.EXIST: YELLOW,
|
|
|
104 |
|
105 |
|
106 |
GREEN, YELLOW, GRAY = "bg-[#20AA57]", "bg-[#E5B22D]", "bg-[#989898]"
|
107 |
+
KEYBOARD = [list("QWERTYUIOP"), list("ASDFGHJKL"), ["GO"] + list("ZXCVBNM") + ["DEL"]]
|
108 |
|
109 |
+
if __name__ == "__main__":
|
110 |
+
serve(reload_excludes=["./app.css"])
|
uv.lock
CHANGED
@@ -564,7 +564,7 @@ dev = [
|
|
564 |
]
|
565 |
|
566 |
[package.metadata]
|
567 |
-
requires-dist = [{ name = "python-fasthtml", specifier = ">=0.4.
|
568 |
|
569 |
[package.metadata.requires-dev]
|
570 |
dev = [{ name = "ruff", specifier = ">=0.6.3" }]
|
|
|
564 |
]
|
565 |
|
566 |
[package.metadata]
|
567 |
+
requires-dist = [{ name = "python-fasthtml", specifier = ">=0.4.6" }]
|
568 |
|
569 |
[package.metadata.requires-dev]
|
570 |
dev = [{ name = "ruff", specifier = ">=0.6.3" }]
|