magmacrunch-scandinavian-stud 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- magmacrunch_scandinavian_stud-0.1.0/.gitignore +9 -0
- magmacrunch_scandinavian_stud-0.1.0/LICENSE +75 -0
- magmacrunch_scandinavian_stud-0.1.0/NOTICE +34 -0
- magmacrunch_scandinavian_stud-0.1.0/PKG-INFO +151 -0
- magmacrunch_scandinavian_stud-0.1.0/README.md +131 -0
- magmacrunch_scandinavian_stud-0.1.0/pyproject.toml +53 -0
- magmacrunch_scandinavian_stud-0.1.0/stud/__init__.py +0 -0
- magmacrunch_scandinavian_stud-0.1.0/stud/__main__.py +31 -0
- magmacrunch_scandinavian_stud-0.1.0/stud/app.py +106 -0
- magmacrunch_scandinavian_stud-0.1.0/stud/arcade.py +39 -0
- magmacrunch_scandinavian_stud-0.1.0/stud/online.py +197 -0
- magmacrunch_scandinavian_stud-0.1.0/stud/online_scenes.py +335 -0
- magmacrunch_scandinavian_stud-0.1.0/stud/rules.py +670 -0
- magmacrunch_scandinavian_stud-0.1.0/stud/scenes.py +596 -0
- magmacrunch_scandinavian_stud-0.1.0/stud/theme.py +41 -0
- magmacrunch_scandinavian_stud-0.1.0/tests/conftest.py +53 -0
- magmacrunch_scandinavian_stud-0.1.0/tests/test_app.py +282 -0
- magmacrunch_scandinavian_stud-0.1.0/tests/test_online.py +460 -0
- magmacrunch_scandinavian_stud-0.1.0/tests/test_oracle.py +112 -0
- magmacrunch_scandinavian_stud-0.1.0/tests/test_rules.py +206 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Required Notice: Copyright 2026 magmacrunch media (https://magmacrunch.com)
|
|
2
|
+
|
|
3
|
+
# PolyForm Noncommercial License 1.0.0
|
|
4
|
+
|
|
5
|
+
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
|
6
|
+
|
|
7
|
+
## Acceptance
|
|
8
|
+
|
|
9
|
+
In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
|
|
10
|
+
|
|
11
|
+
## Copyright License
|
|
12
|
+
|
|
13
|
+
The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
|
|
14
|
+
|
|
15
|
+
## Distribution License
|
|
16
|
+
|
|
17
|
+
The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
|
|
18
|
+
|
|
19
|
+
## Notices
|
|
20
|
+
|
|
21
|
+
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
|
|
22
|
+
|
|
23
|
+
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
|
24
|
+
|
|
25
|
+
## Changes and New Works License
|
|
26
|
+
|
|
27
|
+
The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
|
|
28
|
+
|
|
29
|
+
## Patent License
|
|
30
|
+
|
|
31
|
+
The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
|
|
32
|
+
|
|
33
|
+
## Noncommercial Purposes
|
|
34
|
+
|
|
35
|
+
Any noncommercial purpose is a permitted purpose.
|
|
36
|
+
|
|
37
|
+
## Personal Uses
|
|
38
|
+
|
|
39
|
+
Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, is use for a permitted purpose.
|
|
40
|
+
|
|
41
|
+
## Noncommercial Organizations
|
|
42
|
+
|
|
43
|
+
Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution is use for a permitted purpose regardless of the source of funding or obligations resulting from the funding.
|
|
44
|
+
|
|
45
|
+
## Fair Use
|
|
46
|
+
|
|
47
|
+
You may have "fair use" rights for the software under the law. These terms do not limit them.
|
|
48
|
+
|
|
49
|
+
## No Other Rights
|
|
50
|
+
|
|
51
|
+
These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
|
|
52
|
+
|
|
53
|
+
## Patent Defense
|
|
54
|
+
|
|
55
|
+
If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
|
|
56
|
+
|
|
57
|
+
## Violations
|
|
58
|
+
|
|
59
|
+
The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
|
|
60
|
+
|
|
61
|
+
## No Liability
|
|
62
|
+
|
|
63
|
+
***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
|
|
64
|
+
|
|
65
|
+
## Definitions
|
|
66
|
+
|
|
67
|
+
The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
|
|
68
|
+
|
|
69
|
+
**You** refers to the individual or entity agreeing to these terms.
|
|
70
|
+
|
|
71
|
+
**Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
|
|
72
|
+
|
|
73
|
+
**Your licenses** are all the licenses granted to you for the software under these terms.
|
|
74
|
+
|
|
75
|
+
**Use** means anything you do with the software requiring one of your licenses.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
scandinavian-stud
|
|
2
|
+
Copyright 2026 magmacrunch media
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
THE LICENCE
|
|
6
|
+
|
|
7
|
+
Everything in this repository -- the browser client and the WebSocket server
|
|
8
|
+
in web/, its terminal version in tui/, the tools, and the documentation -- is
|
|
9
|
+
licensed under the PolyForm Noncommercial License 1.0.0, in the LICENSE file
|
|
10
|
+
beside this one.
|
|
11
|
+
|
|
12
|
+
Read it, learn from it, build on it, play with it. Any noncommercial purpose is
|
|
13
|
+
permitted, and that includes personal projects, teaching and research. What is
|
|
14
|
+
not permitted is commercial use: selling the game or a derivative of it,
|
|
15
|
+
shipping it inside a product, or running it as part of a business.
|
|
16
|
+
|
|
17
|
+
Scandinavian Stud is a traditional card game and nobody's property; this licence
|
|
18
|
+
does not pretend otherwise. What is licensed is this implementation: the rules, the hand evaluator, the betting, the table renderer and the
|
|
19
|
+
server.
|
|
20
|
+
|
|
21
|
+
What is reserved: the magmacrunch name and marks and the visual design of the
|
|
22
|
+
arcade. If you want to use any of it, ask.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
THIRD-PARTY COMPONENTS
|
|
26
|
+
|
|
27
|
+
Fonts are loaded from Google Fonts under the SIL Open Font License 1.1 rather
|
|
28
|
+
than shipped here as files.
|
|
29
|
+
|
|
30
|
+
The client loads the adenosine cards, multiplayer and score bundles, the deck
|
|
31
|
+
art and the arcade styles from the website's arcade/shared/, and the server
|
|
32
|
+
imports server_base and card_constants from arcade/shared/multiplayer/. Those
|
|
33
|
+
are part of the magmacrunch.com repository and adenosine, and are not copied
|
|
34
|
+
here.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: magmacrunch-scandinavian-stud
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Sökö, Scandinavian stud poker, against three computers. Terminal version.
|
|
5
|
+
Project-URL: Homepage, https://magmacrunch.com/arcade/scandinavian-stud/
|
|
6
|
+
Author: magmacrunch media
|
|
7
|
+
License-Expression: PolyForm-Noncommercial-1.0.0
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
License-File: NOTICE
|
|
10
|
+
Keywords: arcade,cards,games,poker,stud,terminal,tui
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Requires-Dist: magmacrunch>=0.15.0
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
15
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
16
|
+
Requires-Dist: websockets>=14.0; extra == 'dev'
|
|
17
|
+
Provides-Extra: online
|
|
18
|
+
Requires-Dist: websockets>=14.0; extra == 'online'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# Sökö (terminal)
|
|
22
|
+
|
|
23
|
+
Sökö, Scandinavian stud poker, against the page's three computers, Mikko,
|
|
24
|
+
Liisa and Jussi, in a terminal. It is the second version of the game, beside
|
|
25
|
+
`web/`. It runs on the [magmacrunch](https://github.com/magmacrunch-media/magmacrunch)
|
|
26
|
+
engine's terminal backend, either as a cabinet in the magmacrunch arcade or as
|
|
27
|
+
its own command, and plays the same either way.
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
pipx install magmacrunch-scandinavian-stud
|
|
31
|
+
soko # the title
|
|
32
|
+
soko --play --seed 7 # straight to the table, the same shuffles every time
|
|
33
|
+
soko --online # PLAY ONLINE (needs the [online] extra)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
PLAY ONLINE, a prototype, plays people instead, through the page's own
|
|
37
|
+
server: `pipx install "magmacrunch-scandinavian-stud[online]"`.
|
|
38
|
+
|
|
39
|
+
For working on it, from this folder, once the website's `shared/` is linked
|
|
40
|
+
in beside `web/` (`node tools/link-shared.mjs` from the repository's root),
|
|
41
|
+
which the oracle test needs:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
pip install -e ".[dev]"
|
|
45
|
+
python -m stud
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Keys
|
|
49
|
+
|
|
50
|
+
| key | |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `←` `→` | choose among the actions the page would offer |
|
|
53
|
+
| `ENTER` | take it; at the end of a hand, deal the next |
|
|
54
|
+
| `C` `R` `F` | check or call, raise, fold |
|
|
55
|
+
| `N` | a new table |
|
|
56
|
+
| `Esc` | the title (and from there, the arcade) |
|
|
57
|
+
| `Q` | quit |
|
|
58
|
+
|
|
59
|
+
The three computers sit above, you below. Your cards are all shown, your hole
|
|
60
|
+
card first; theirs show their face-up cards and a `▓▓` for the hole card
|
|
61
|
+
until a showdown. Each seat says its chips, its bet this round, and when it
|
|
62
|
+
has folded or gone all-in; your hand is named as the page names it, Finnish
|
|
63
|
+
and English. The labels are the page's own pairs: *Sekötä / check*, *Maksaa /
|
|
64
|
+
call*, *Nostaa / raise*, *Luovuttaa / fold*. The computers keep the page's
|
|
65
|
+
clock: one decides 0.8 seconds after its turn comes, and a finished round of
|
|
66
|
+
betting deals the next street after 0.6.
|
|
67
|
+
|
|
68
|
+
Win every chip the computers have and the table is yours, and it goes on
|
|
69
|
+
the high-score table as the page scores it: your chips per hand it took,
|
|
70
|
+
x100, so the faster the clear, the higher. A
|
|
71
|
+
player with no chips sits every hand out after; when that is you, the table
|
|
72
|
+
is lost and the next is a new one.
|
|
73
|
+
|
|
74
|
+
Needs a 78x24 terminal.
|
|
75
|
+
|
|
76
|
+
## It is the browser's game, and the browser's opponents
|
|
77
|
+
|
|
78
|
+
`stud/rules.py` is `web/js/config.js`, `hand-eval.js` and `game.js`, with the
|
|
79
|
+
deck and its shuffle from the website's `shared/adenosine-cards.js` and the
|
|
80
|
+
turn-taking of `main.js`, function for function. The shuffle is the only
|
|
81
|
+
random thing in the game: the computers decide by rule. Two tests hold the
|
|
82
|
+
port to the page:
|
|
83
|
+
|
|
84
|
+
- **`tests/test_oracle.py`.** `tools/js_oracle.mjs` loads those files
|
|
85
|
+
unmodified, with a seeded `Math.random`, and plays 40 tables of 80 hands
|
|
86
|
+
through `SokoGame`'s own API in `main.js`'s order: 3,200 hands, about 55,000
|
|
87
|
+
actions. Half have a random player in your seat, half a calling station. The
|
|
88
|
+
test shuffles the same decks through the port and requires the same state
|
|
89
|
+
after every action: every player's chips, cards, bet and hand, the pot, the
|
|
90
|
+
bet to match, the street, whose turn, the raises and who has acted, the
|
|
91
|
+
phase and the winner, and starts a new table where the page does. Moving
|
|
92
|
+
a style's mark, or dealing in a player with no chips again, fails it within
|
|
93
|
+
the first table.
|
|
94
|
+
- **`tests/test_rules.py`.** It sets up by hand the cases worth pinning,
|
|
95
|
+
including the page's own rules below, so that fixing one shows as a change.
|
|
96
|
+
|
|
97
|
+
## The page's rules
|
|
98
|
+
|
|
99
|
+
The page's betting, side pots, ties and computers were fixed in
|
|
100
|
+
scandinavian-stud#3, and the port with them. Two places where it is still not
|
|
101
|
+
poker, kept because the oracle requires them:
|
|
102
|
+
|
|
103
|
+
- **A tie is not split.** The pot goes to the tied player nearest the
|
|
104
|
+
dealer's left. Each pot is its own: an all-in player wins only the part
|
|
105
|
+
they matched, and the rest goes to the best hand among those who matched it.
|
|
106
|
+
- **A conservative or moderate computer with nothing never calls.** High card
|
|
107
|
+
scores 0, under their call marks, so they check when they can and fold to
|
|
108
|
+
any bet. The aggressive one calls anything.
|
|
109
|
+
|
|
110
|
+
## Online, a prototype
|
|
111
|
+
|
|
112
|
+
PLAY ONLINE meets two to four people, in terminals or browsers, in a room on
|
|
113
|
+
the arcade's own server, `web/server.py`, the one behind the page's
|
|
114
|
+
Multiplayer button. One creates the room and says its four-letter code, the
|
|
115
|
+
others join with it, and the host deals. `--server ws://host:8767` points at
|
|
116
|
+
another server, a Pi on the same network say.
|
|
117
|
+
|
|
118
|
+
The server keeps the game: it deals, says whose turn it is and what they may
|
|
119
|
+
do, and sends the whole table after every action. `stud/online.py` keeps that
|
|
120
|
+
latest table and sends the action chosen; `stud/online_scenes.py` draws it
|
|
121
|
+
with the same table screen as the computers, you below and the others above
|
|
122
|
+
in the server's order from you. Hand after hand anyone at the table deals the
|
|
123
|
+
next with ENTER, until one player holds every chip.
|
|
124
|
+
|
|
125
|
+
`tests/test_online.py` runs the real server in-process and plays real
|
|
126
|
+
clients against it over real sockets. Three players take seeded random
|
|
127
|
+
actions through a whole table to a table won, and after every
|
|
128
|
+
action every client's table is the server's view for that player. Two terminal sessions also
|
|
129
|
+
play a hand by keys. Making the client ignore the server's `hand_started`
|
|
130
|
+
frames fails both. A terminal session has also played three hands against
|
|
131
|
+
the page in Chromium through `web/server.py` run as the Pi runs it, the two
|
|
132
|
+
agreeing on every chip.
|
|
133
|
+
|
|
134
|
+
**Each player is sent only their own hole card.** The server builds the
|
|
135
|
+
table again for each player as it sends a frame: their own hole card, and
|
|
136
|
+
everyone's still in once a hand goes to a showdown; a hand won by a fold
|
|
137
|
+
shows nobody's. A spectator sees what a player sees of the others. It used
|
|
138
|
+
to send one frame to the whole room with every hole card and hand in it,
|
|
139
|
+
which the page and this client drew face down but anyone reading the frames
|
|
140
|
+
could see. `tests/test_online.py` searches every frame of a table played to
|
|
141
|
+
showdowns and folds, by three players and a spectator, for anything else,
|
|
142
|
+
and fails against the old server.
|
|
143
|
+
|
|
144
|
+
The server plays the page's rules. A raise is always the street's step,
|
|
145
|
+
whatever amount a client sends, and a player who resigns or leaves folds the
|
|
146
|
+
hand and sits out every one after it while the rest play on.
|
|
147
|
+
|
|
148
|
+
## Licence
|
|
149
|
+
|
|
150
|
+
PolyForm Noncommercial 1.0.0, as for the rest of this repository. See `LICENSE`
|
|
151
|
+
and `NOTICE`.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Sökö (terminal)
|
|
2
|
+
|
|
3
|
+
Sökö, Scandinavian stud poker, against the page's three computers, Mikko,
|
|
4
|
+
Liisa and Jussi, in a terminal. It is the second version of the game, beside
|
|
5
|
+
`web/`. It runs on the [magmacrunch](https://github.com/magmacrunch-media/magmacrunch)
|
|
6
|
+
engine's terminal backend, either as a cabinet in the magmacrunch arcade or as
|
|
7
|
+
its own command, and plays the same either way.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
pipx install magmacrunch-scandinavian-stud
|
|
11
|
+
soko # the title
|
|
12
|
+
soko --play --seed 7 # straight to the table, the same shuffles every time
|
|
13
|
+
soko --online # PLAY ONLINE (needs the [online] extra)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
PLAY ONLINE, a prototype, plays people instead, through the page's own
|
|
17
|
+
server: `pipx install "magmacrunch-scandinavian-stud[online]"`.
|
|
18
|
+
|
|
19
|
+
For working on it, from this folder, once the website's `shared/` is linked
|
|
20
|
+
in beside `web/` (`node tools/link-shared.mjs` from the repository's root),
|
|
21
|
+
which the oracle test needs:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
pip install -e ".[dev]"
|
|
25
|
+
python -m stud
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Keys
|
|
29
|
+
|
|
30
|
+
| key | |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `←` `→` | choose among the actions the page would offer |
|
|
33
|
+
| `ENTER` | take it; at the end of a hand, deal the next |
|
|
34
|
+
| `C` `R` `F` | check or call, raise, fold |
|
|
35
|
+
| `N` | a new table |
|
|
36
|
+
| `Esc` | the title (and from there, the arcade) |
|
|
37
|
+
| `Q` | quit |
|
|
38
|
+
|
|
39
|
+
The three computers sit above, you below. Your cards are all shown, your hole
|
|
40
|
+
card first; theirs show their face-up cards and a `▓▓` for the hole card
|
|
41
|
+
until a showdown. Each seat says its chips, its bet this round, and when it
|
|
42
|
+
has folded or gone all-in; your hand is named as the page names it, Finnish
|
|
43
|
+
and English. The labels are the page's own pairs: *Sekötä / check*, *Maksaa /
|
|
44
|
+
call*, *Nostaa / raise*, *Luovuttaa / fold*. The computers keep the page's
|
|
45
|
+
clock: one decides 0.8 seconds after its turn comes, and a finished round of
|
|
46
|
+
betting deals the next street after 0.6.
|
|
47
|
+
|
|
48
|
+
Win every chip the computers have and the table is yours, and it goes on
|
|
49
|
+
the high-score table as the page scores it: your chips per hand it took,
|
|
50
|
+
x100, so the faster the clear, the higher. A
|
|
51
|
+
player with no chips sits every hand out after; when that is you, the table
|
|
52
|
+
is lost and the next is a new one.
|
|
53
|
+
|
|
54
|
+
Needs a 78x24 terminal.
|
|
55
|
+
|
|
56
|
+
## It is the browser's game, and the browser's opponents
|
|
57
|
+
|
|
58
|
+
`stud/rules.py` is `web/js/config.js`, `hand-eval.js` and `game.js`, with the
|
|
59
|
+
deck and its shuffle from the website's `shared/adenosine-cards.js` and the
|
|
60
|
+
turn-taking of `main.js`, function for function. The shuffle is the only
|
|
61
|
+
random thing in the game: the computers decide by rule. Two tests hold the
|
|
62
|
+
port to the page:
|
|
63
|
+
|
|
64
|
+
- **`tests/test_oracle.py`.** `tools/js_oracle.mjs` loads those files
|
|
65
|
+
unmodified, with a seeded `Math.random`, and plays 40 tables of 80 hands
|
|
66
|
+
through `SokoGame`'s own API in `main.js`'s order: 3,200 hands, about 55,000
|
|
67
|
+
actions. Half have a random player in your seat, half a calling station. The
|
|
68
|
+
test shuffles the same decks through the port and requires the same state
|
|
69
|
+
after every action: every player's chips, cards, bet and hand, the pot, the
|
|
70
|
+
bet to match, the street, whose turn, the raises and who has acted, the
|
|
71
|
+
phase and the winner, and starts a new table where the page does. Moving
|
|
72
|
+
a style's mark, or dealing in a player with no chips again, fails it within
|
|
73
|
+
the first table.
|
|
74
|
+
- **`tests/test_rules.py`.** It sets up by hand the cases worth pinning,
|
|
75
|
+
including the page's own rules below, so that fixing one shows as a change.
|
|
76
|
+
|
|
77
|
+
## The page's rules
|
|
78
|
+
|
|
79
|
+
The page's betting, side pots, ties and computers were fixed in
|
|
80
|
+
scandinavian-stud#3, and the port with them. Two places where it is still not
|
|
81
|
+
poker, kept because the oracle requires them:
|
|
82
|
+
|
|
83
|
+
- **A tie is not split.** The pot goes to the tied player nearest the
|
|
84
|
+
dealer's left. Each pot is its own: an all-in player wins only the part
|
|
85
|
+
they matched, and the rest goes to the best hand among those who matched it.
|
|
86
|
+
- **A conservative or moderate computer with nothing never calls.** High card
|
|
87
|
+
scores 0, under their call marks, so they check when they can and fold to
|
|
88
|
+
any bet. The aggressive one calls anything.
|
|
89
|
+
|
|
90
|
+
## Online, a prototype
|
|
91
|
+
|
|
92
|
+
PLAY ONLINE meets two to four people, in terminals or browsers, in a room on
|
|
93
|
+
the arcade's own server, `web/server.py`, the one behind the page's
|
|
94
|
+
Multiplayer button. One creates the room and says its four-letter code, the
|
|
95
|
+
others join with it, and the host deals. `--server ws://host:8767` points at
|
|
96
|
+
another server, a Pi on the same network say.
|
|
97
|
+
|
|
98
|
+
The server keeps the game: it deals, says whose turn it is and what they may
|
|
99
|
+
do, and sends the whole table after every action. `stud/online.py` keeps that
|
|
100
|
+
latest table and sends the action chosen; `stud/online_scenes.py` draws it
|
|
101
|
+
with the same table screen as the computers, you below and the others above
|
|
102
|
+
in the server's order from you. Hand after hand anyone at the table deals the
|
|
103
|
+
next with ENTER, until one player holds every chip.
|
|
104
|
+
|
|
105
|
+
`tests/test_online.py` runs the real server in-process and plays real
|
|
106
|
+
clients against it over real sockets. Three players take seeded random
|
|
107
|
+
actions through a whole table to a table won, and after every
|
|
108
|
+
action every client's table is the server's view for that player. Two terminal sessions also
|
|
109
|
+
play a hand by keys. Making the client ignore the server's `hand_started`
|
|
110
|
+
frames fails both. A terminal session has also played three hands against
|
|
111
|
+
the page in Chromium through `web/server.py` run as the Pi runs it, the two
|
|
112
|
+
agreeing on every chip.
|
|
113
|
+
|
|
114
|
+
**Each player is sent only their own hole card.** The server builds the
|
|
115
|
+
table again for each player as it sends a frame: their own hole card, and
|
|
116
|
+
everyone's still in once a hand goes to a showdown; a hand won by a fold
|
|
117
|
+
shows nobody's. A spectator sees what a player sees of the others. It used
|
|
118
|
+
to send one frame to the whole room with every hole card and hand in it,
|
|
119
|
+
which the page and this client drew face down but anyone reading the frames
|
|
120
|
+
could see. `tests/test_online.py` searches every frame of a table played to
|
|
121
|
+
showdowns and folds, by three players and a spectator, for anything else,
|
|
122
|
+
and fails against the old server.
|
|
123
|
+
|
|
124
|
+
The server plays the page's rules. A raise is always the street's step,
|
|
125
|
+
whatever amount a client sends, and a player who resigns or leaves folds the
|
|
126
|
+
hand and sits out every one after it while the rest play on.
|
|
127
|
+
|
|
128
|
+
## Licence
|
|
129
|
+
|
|
130
|
+
PolyForm Noncommercial 1.0.0, as for the rest of this repository. See `LICENSE`
|
|
131
|
+
and `NOTICE`.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
# Prefixed like every other cabinet: PyPI cannot scope a family, so the prefix
|
|
7
|
+
# claims it. The command is `soko`, the page's own name for the game; the
|
|
8
|
+
# import package is `stud`.
|
|
9
|
+
name = "magmacrunch-scandinavian-stud"
|
|
10
|
+
version = "0.1.0"
|
|
11
|
+
description = "Sökö, Scandinavian stud poker, against three computers. Terminal version."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = "PolyForm-Noncommercial-1.0.0"
|
|
14
|
+
license-files = ["LICENSE", "NOTICE"]
|
|
15
|
+
requires-python = ">=3.10"
|
|
16
|
+
authors = [{ name = "magmacrunch media" }]
|
|
17
|
+
keywords = ["arcade", "terminal", "tui", "games", "cards", "poker", "stud"]
|
|
18
|
+
|
|
19
|
+
# The engine, with its terminal backend. stud/rules.py
|
|
20
|
+
# imports neither and runs on a bare interpreter. 0.7.0 is where
|
|
21
|
+
# magmacrunch.engine.ui.glyphs arrives, which run() imports.
|
|
22
|
+
dependencies = ["magmacrunch>=0.15.0"]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
# Only for PLAY ONLINE: the client imports websockets when it connects.
|
|
26
|
+
online = ["websockets>=14.0"]
|
|
27
|
+
# websockets for tests/test_online.py.
|
|
28
|
+
dev = ["pytest>=7.0", "ruff>=0.6", "websockets>=14.0"]
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
soko = "stud.__main__:main"
|
|
32
|
+
|
|
33
|
+
[project.entry-points."magmacrunch.games"]
|
|
34
|
+
scandinavian-stud = "stud.arcade:GAME"
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://magmacrunch.com/arcade/scandinavian-stud/"
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.targets.wheel]
|
|
40
|
+
packages = ["stud"]
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.sdist]
|
|
43
|
+
exclude = ["tools"]
|
|
44
|
+
|
|
45
|
+
[tool.pytest.ini_options]
|
|
46
|
+
testpaths = ["tests"]
|
|
47
|
+
|
|
48
|
+
[tool.ruff]
|
|
49
|
+
target-version = "py310"
|
|
50
|
+
line-length = 100
|
|
51
|
+
|
|
52
|
+
[tool.ruff.lint]
|
|
53
|
+
select = ["E", "F", "I", "UP", "B"]
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""``python -m stud`` -- play Sökö, Scandinavian stud, against the computers or online."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> None:
|
|
9
|
+
parser = argparse.ArgumentParser(prog="soko", description="Sökö, Scandinavian stud poker.")
|
|
10
|
+
parser.add_argument("--play", action="store_true", help="skip the title screen")
|
|
11
|
+
parser.add_argument("--seed", type=int, default=None, help="the same shuffles every time")
|
|
12
|
+
parser.add_argument(
|
|
13
|
+
"--ascii", action="store_true", dest="ascii_only",
|
|
14
|
+
help="draw with plain ASCII instead of block and box glyphs. Detected "
|
|
15
|
+
"automatically from the terminal's encoding; this forces it. "
|
|
16
|
+
"MAGMACRUNCH_ASCII=1 says the same for every cabinet.",
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument("--online", action="store_true",
|
|
19
|
+
help="go straight to PLAY ONLINE (needs the [online] extra)")
|
|
20
|
+
parser.add_argument("--server", default=None,
|
|
21
|
+
help="the multiplayer server for PLAY ONLINE, e.g. "
|
|
22
|
+
"ws://192.168.1.16:8767 (default: the arcade's own)")
|
|
23
|
+
args = parser.parse_args()
|
|
24
|
+
|
|
25
|
+
from stud.app import run
|
|
26
|
+
|
|
27
|
+
run(args.seed, args.play, args.ascii_only, server=args.server, online=args.online)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
main()
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Wiring -- what outlives one game, and how the screens reach it.
|
|
2
|
+
|
|
3
|
+
Everything that draws lives in :mod:`stud.scenes`; everything that decides
|
|
4
|
+
lives in :mod:`stud.rules`. **The terminal is not owned here**: it belongs
|
|
5
|
+
to a ``TuiHost``, which is what lets this run as its own command or be seated
|
|
6
|
+
by the arcade.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import random
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from magmacrunch.engine.scores import ScoreBook
|
|
15
|
+
|
|
16
|
+
from stud.arcade import INFO
|
|
17
|
+
from stud.scenes import GameScene, TitleScene
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class StudApp:
|
|
21
|
+
def __init__(self, host: Any, seed: int | None = None,
|
|
22
|
+
scores: ScoreBook | None = None, server: str | None = None):
|
|
23
|
+
self.host = host
|
|
24
|
+
#: Where PLAY ONLINE connects; None for the arcade's public server.
|
|
25
|
+
self.server = server
|
|
26
|
+
#: Where each table's shuffles come from. Seeded, so ``--seed``
|
|
27
|
+
#: replays a whole session.
|
|
28
|
+
self.rng = random.Random(seed)
|
|
29
|
+
#: The board the browser posts to. The page saves only when you win
|
|
30
|
+
#: the table, your chips; so does this.
|
|
31
|
+
self.scores = scores or ScoreBook(INFO.scoreboard)
|
|
32
|
+
self.initials = "AAA"
|
|
33
|
+
self.last_rank: int | None = None
|
|
34
|
+
self.root_scene = TitleScene(self)
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def renderer(self):
|
|
38
|
+
return self.host.renderer
|
|
39
|
+
|
|
40
|
+
def new_rand(self):
|
|
41
|
+
return random.Random(self.rng.randrange(1 << 32)).random
|
|
42
|
+
|
|
43
|
+
def start_game(self) -> None:
|
|
44
|
+
self.host.push_scene(GameScene(self))
|
|
45
|
+
|
|
46
|
+
def play_online(self) -> None:
|
|
47
|
+
from stud.online import DEFAULT_SERVER
|
|
48
|
+
from stud.online_scenes import OnlineLobbyScene
|
|
49
|
+
|
|
50
|
+
self.host.push_scene(OnlineLobbyScene(self, self.server or DEFAULT_SERVER))
|
|
51
|
+
|
|
52
|
+
def show_scores(self) -> None:
|
|
53
|
+
from stud.scenes import ScoresScene
|
|
54
|
+
|
|
55
|
+
self.host.push_scene(ScoresScene(self))
|
|
56
|
+
|
|
57
|
+
def show_rules(self) -> None:
|
|
58
|
+
from stud.scenes import RulesScene
|
|
59
|
+
|
|
60
|
+
self.host.push_scene(RulesScene(self))
|
|
61
|
+
|
|
62
|
+
def enter_initials(self, score: int, extra: dict) -> None:
|
|
63
|
+
from stud.scenes import InitialsScene
|
|
64
|
+
|
|
65
|
+
self.host.push_scene(InitialsScene(self, score, extra))
|
|
66
|
+
|
|
67
|
+
def to_title(self) -> None:
|
|
68
|
+
self.host.pop_scene()
|
|
69
|
+
|
|
70
|
+
def leave(self) -> None:
|
|
71
|
+
self.host.pop_scene()
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def best(self) -> int:
|
|
75
|
+
return self.scores.best()
|
|
76
|
+
|
|
77
|
+
def qualifies(self, score: int) -> bool:
|
|
78
|
+
return score > 0 and self.scores.qualifies(score)
|
|
79
|
+
|
|
80
|
+
def record(self, score: int, initials: str | None = None,
|
|
81
|
+
extra: dict | None = None):
|
|
82
|
+
self.initials = initials or self.initials
|
|
83
|
+
result = self.scores.save(self.initials, score, **(extra or {}))
|
|
84
|
+
self.last_rank = result.rank
|
|
85
|
+
return result
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def scene(self):
|
|
89
|
+
return self.host.scene
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def run(seed: int | None = None, skip_title: bool = False,
|
|
93
|
+
ascii_only: bool = False, server: str | None = None, online: bool = False) -> None:
|
|
94
|
+
"""Play Sökö as its own command."""
|
|
95
|
+
from magmacrunch.engine.core.tui_host import TuiHost
|
|
96
|
+
from magmacrunch.engine.ui.glyphs import Glyphs
|
|
97
|
+
|
|
98
|
+
host = TuiHost(title=INFO.title, fps=INFO.fps, hold_ms=INFO.hold_ms,
|
|
99
|
+
glyphs=Glyphs.detect(ascii_only=ascii_only))
|
|
100
|
+
app = StudApp(host, seed, server=server)
|
|
101
|
+
host.push_scene(app.root_scene)
|
|
102
|
+
if online:
|
|
103
|
+
app.play_online()
|
|
104
|
+
elif skip_title:
|
|
105
|
+
host.push_scene(GameScene(app))
|
|
106
|
+
host.run()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""What the arcade needs to know to launch this game. Kept tiny: a menu loads
|
|
2
|
+
one of these per installed game just to draw a card."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from magmacrunch.engine.arcade import GameInfo
|
|
9
|
+
|
|
10
|
+
from stud import theme
|
|
11
|
+
|
|
12
|
+
INFO = GameInfo(
|
|
13
|
+
key="scandinavian-stud",
|
|
14
|
+
title="Sökö",
|
|
15
|
+
blurb="Scandinavian stud poker against three computers.",
|
|
16
|
+
# Turn-based: the frame rate paces the computers' bets and the cursor,
|
|
17
|
+
# nothing else. Edge input, so one arrow is one choice.
|
|
18
|
+
fps=20,
|
|
19
|
+
hold_ms=0,
|
|
20
|
+
min_cols=theme.MIN_COLS,
|
|
21
|
+
min_rows=theme.MIN_ROWS,
|
|
22
|
+
accent=theme.ACCENT,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class StudGame:
|
|
27
|
+
"""Satisfies :class:`magmacrunch.engine.arcade.ArcadeGame`."""
|
|
28
|
+
|
|
29
|
+
info = INFO
|
|
30
|
+
|
|
31
|
+
def start(self, host: Any) -> Any:
|
|
32
|
+
from stud.app import StudApp
|
|
33
|
+
|
|
34
|
+
return StudApp(host).root_scene
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
GAME = StudGame()
|
|
38
|
+
|
|
39
|
+
__all__ = ["GAME", "INFO", "StudGame"]
|