magmacrunch-solitaire 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.
@@ -0,0 +1,14 @@
1
+ # The link tools/link-shared.mjs makes to the website's arcade/shared/.
2
+ /shared
3
+ __pycache__/
4
+ *.pyc
5
+ .pytest_cache/
6
+ node_modules/
7
+ .DS_Store
8
+ .ruff_cache/
9
+ *.egg-info/
10
+ build/
11
+ sdcard/
12
+ *.dol
13
+ *.elf
14
+ *.map
@@ -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,30 @@
1
+ solitaire
2
+ Copyright 2026 magmacrunch media
3
+
4
+
5
+ THE LICENCE
6
+
7
+ Everything in this repository -- the game in web/, its terminal version in tui/, the
8
+ tools and the documentation -- is licensed under the PolyForm Noncommercial License 1.0.0, in the
9
+ LICENSE file beside this one.
10
+
11
+ Read it, learn from it, build on it, play with it. Any noncommercial purpose is
12
+ permitted, and that includes personal projects, teaching and research. What is not
13
+ permitted is commercial use: selling the game or a derivative of it, shipping it inside
14
+ a product, or running it as part of a business.
15
+
16
+ Solitaire is a traditional game and nobody's property; this licence does not pretend
17
+ otherwise. What is licensed is this implementation: the rules engine, the board
18
+ renderer and the arcade's presentation of them.
19
+
20
+ What is reserved: the magmacrunch name and marks and the visual design of the arcade.
21
+ If you want to use any of it, ask.
22
+
23
+
24
+ THIRD-PARTY COMPONENTS
25
+
26
+ Fonts are loaded from Google Fonts under the SIL Open Font License 1.1 rather than
27
+ shipped here as files.
28
+
29
+ The page loads adenosine-cards.js, adenosine-score-client.js, arcade-base.css, cards/cards.css, cards/deck-choice.js, score-server.js from the website's arcade/shared/.
30
+ Those are part of the magmacrunch.com repository and adenosine, and are not copied here.
@@ -0,0 +1,127 @@
1
+ Metadata-Version: 2.5
2
+ Name: magmacrunch-solitaire
3
+ Version: 0.1.0
4
+ Summary: Klondike solitaire, draw three, as the magmacrunch arcade plays it. Terminal version.
5
+ Project-URL: Homepage, https://magmacrunch.com/arcade/solitaire/
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,klondike,solitaire,terminal,tui
11
+ Requires-Python: >=3.10
12
+ Requires-Dist: magmacrunch>=0.7.0
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=7.0; extra == 'dev'
15
+ Requires-Dist: ruff>=0.6; extra == 'dev'
16
+ Description-Content-Type: text/markdown
17
+
18
+ # Solitaire (terminal)
19
+
20
+ The magmacrunch arcade's Solitaire, Klondike draw three, in a terminal. It is
21
+ the second version of the game, beside `web/`. It runs on the
22
+ [magmacrunch](https://github.com/magmacrunch-media/magmacrunch) engine's
23
+ terminal backend, either as a cabinet in the magmacrunch arcade or as its own
24
+ command, and plays the same either way.
25
+
26
+ ```
27
+ pipx install magmacrunch-solitaire
28
+ solitaire # the title
29
+ solitaire --play --seed 7 # straight to a deal, the same deals every time
30
+ ```
31
+
32
+ For working on it, from this folder, once the website's `shared/` is linked
33
+ in beside `web/` (`node tools/link-shared.mjs` from the repository's root),
34
+ which the oracle test needs:
35
+
36
+ ```
37
+ pip install -e ".[dev]"
38
+ python -m klondike
39
+ ```
40
+
41
+ ## Keys
42
+
43
+ A cursor stands for the mouse, and ENTER is a click wherever it stands.
44
+
45
+ | key | |
46
+ |---|---|
47
+ | `←` `→` | along the top row (stock, waste, foundations) or across the columns |
48
+ | `↑` `↓` | up and down a column's face-up cards, and between the top row and the columns |
49
+ | `1` to `7` | a column |
50
+ | `ENTER` `SPACE` | click: draw from the stock, pick a card up, or put the held card there |
51
+ | `F` | double-click: a card to its foundation, if it can go |
52
+ | `D` | draw, wherever the cursor is |
53
+ | `X` | click the felt: put the held card down |
54
+ | `N` | deal again |
55
+ | `H` `S` | help, the scores; the clock stops while they are open |
56
+ | `Esc` `Esc` | leave the deal, for the title (and from there, the arcade) |
57
+ | `Q` | quit |
58
+
59
+ The stock shows its back and how many cards are left. The waste shows its
60
+ top three, fanned. A column shows its face-down cards as one back and a
61
+ count, then its face-up cards one to a row. The held card is lit cyan and
62
+ the cursor pink. A win shows the page's win screen: time, moves, score, and
63
+ the time bonus, with a NEW HIGH SCORE badge when the score makes the board.
64
+ It goes on the board with its time beside it, as the page saves it.
65
+
66
+ Needs a 41x24 terminal.
67
+
68
+ ## It is the browser's game, on the browser's clock
69
+
70
+ `klondike/rules.py` is `web/js/game.js`, method for method, with the deck and
71
+ its shuffle from the website's `shared/adenosine-cards.js`. The page keeps
72
+ its time with a one-second `setInterval`, and that is part of the rules: the
73
+ time on the table, and the bonus a win is paid, are whatever that timer last
74
+ set. So the port keeps the page's clock too. Its time only moves when the
75
+ screen moves it, by the real time gone, and the interval fires at each of
76
+ its seconds on the way. A double click is two clicks on one card inside 300
77
+ ms of that time. Two tests hold the port to the page:
78
+
79
+ - **`tests/test_oracle.py`.** `tools/js_oracle.mjs` loads those files
80
+ unmodified on a page that does nothing, with a seeded `Math.random` and a
81
+ clock of its own. It plays 40 games through the `Solitaire` class the way
82
+ the page's clicks reach it: the stock, a card, an empty foundation or
83
+ column, the felt, the menu's pauses and New. Half are a random clicker
84
+ that now and then clicks the same card again quickly. Half are a greedy
85
+ player, which won 16 of its 285 deals. That is 134,958 clicks and clock
86
+ steps. The test makes the same ones through the port and requires the same
87
+ state after every one: every pile, face up or down, the card held and
88
+ where from, moves, score, the seconds on the clock, whether it runs, and
89
+ the win screens set going. Widening the double click to 310 ms fails it at
90
+ seed 1, event 266; a bonus of x36 fails it at seed 2, event 3,194.
91
+ - **`tests/test_rules.py`.** It sets up by hand the cases worth pinning,
92
+ including the fixes below, so that undoing one shows as a change.
93
+
94
+ ## Fixed in both
95
+
96
+ - **Only a column's top card goes to a foundation.** The page checked the card
97
+ picked and moved the column's top card: with the 5♥ and 4♠ face up and the
98
+ hearts at 4, picking the 5♥ put the 4♠ on the hearts. Now nothing moves.
99
+ - **A card on a foundation stays there.** Moving one to another foundation
100
+ counted a move and moved nothing; now it counts nothing.
101
+ - **A won table is finished.** It still played: a king taken off its
102
+ foundation and put back won again, with a second bonus and a second score.
103
+ A win now stops the clock for good, and Help and the scores no longer start
104
+ it again.
105
+ - **The bonus is paid on the time to the win,** read at the winning move, not
106
+ on the timer's last tick up to a second before it. A win under 30 seconds is
107
+ paid as 30, so the bonus never falls as a win gets faster: it was 24,500,000
108
+ at one second and nothing under one.
109
+ - **The clock holds for every reason at once.** A modal and the page being
110
+ hidden each hold it, and it runs again only once neither does. A new deal
111
+ starts it clean, and drops a win screen still waiting from the last deal.
112
+ - **A draw puts down a waste card held.** The page kept it held by its old
113
+ place on the waste, so the next move took whatever card the draw had put
114
+ there. Worse, holding the waste's king, turning the waste back into the
115
+ stock and clicking an empty column pushed `undefined` into the column, and
116
+ every render after threw: in Chromium, "Cannot read properties of
117
+ undefined (reading 'getHTML')", and the table stopped answering clicks.
118
+ `drawFromStock` and `draw_from_stock` now put a held waste card down first;
119
+ a tableau card held stays held. In Chromium the same clicks on the fixed
120
+ page leave the column empty, and the king, drawn again, goes there. Taking
121
+ the fix out of the port alone fails the oracle at seed 1, event 252; the
122
+ trace has 57 draws with a waste card held, three of them turn-overs.
123
+
124
+ ## Licence
125
+
126
+ PolyForm Noncommercial 1.0.0, as for the rest of this repository. See
127
+ `LICENSE` and `NOTICE`.
@@ -0,0 +1,110 @@
1
+ # Solitaire (terminal)
2
+
3
+ The magmacrunch arcade's Solitaire, Klondike draw three, in a terminal. It is
4
+ the second version of the game, beside `web/`. It runs on the
5
+ [magmacrunch](https://github.com/magmacrunch-media/magmacrunch) engine's
6
+ terminal backend, either as a cabinet in the magmacrunch arcade or as its own
7
+ command, and plays the same either way.
8
+
9
+ ```
10
+ pipx install magmacrunch-solitaire
11
+ solitaire # the title
12
+ solitaire --play --seed 7 # straight to a deal, the same deals every time
13
+ ```
14
+
15
+ For working on it, from this folder, once the website's `shared/` is linked
16
+ in beside `web/` (`node tools/link-shared.mjs` from the repository's root),
17
+ which the oracle test needs:
18
+
19
+ ```
20
+ pip install -e ".[dev]"
21
+ python -m klondike
22
+ ```
23
+
24
+ ## Keys
25
+
26
+ A cursor stands for the mouse, and ENTER is a click wherever it stands.
27
+
28
+ | key | |
29
+ |---|---|
30
+ | `←` `→` | along the top row (stock, waste, foundations) or across the columns |
31
+ | `↑` `↓` | up and down a column's face-up cards, and between the top row and the columns |
32
+ | `1` to `7` | a column |
33
+ | `ENTER` `SPACE` | click: draw from the stock, pick a card up, or put the held card there |
34
+ | `F` | double-click: a card to its foundation, if it can go |
35
+ | `D` | draw, wherever the cursor is |
36
+ | `X` | click the felt: put the held card down |
37
+ | `N` | deal again |
38
+ | `H` `S` | help, the scores; the clock stops while they are open |
39
+ | `Esc` `Esc` | leave the deal, for the title (and from there, the arcade) |
40
+ | `Q` | quit |
41
+
42
+ The stock shows its back and how many cards are left. The waste shows its
43
+ top three, fanned. A column shows its face-down cards as one back and a
44
+ count, then its face-up cards one to a row. The held card is lit cyan and
45
+ the cursor pink. A win shows the page's win screen: time, moves, score, and
46
+ the time bonus, with a NEW HIGH SCORE badge when the score makes the board.
47
+ It goes on the board with its time beside it, as the page saves it.
48
+
49
+ Needs a 41x24 terminal.
50
+
51
+ ## It is the browser's game, on the browser's clock
52
+
53
+ `klondike/rules.py` is `web/js/game.js`, method for method, with the deck and
54
+ its shuffle from the website's `shared/adenosine-cards.js`. The page keeps
55
+ its time with a one-second `setInterval`, and that is part of the rules: the
56
+ time on the table, and the bonus a win is paid, are whatever that timer last
57
+ set. So the port keeps the page's clock too. Its time only moves when the
58
+ screen moves it, by the real time gone, and the interval fires at each of
59
+ its seconds on the way. A double click is two clicks on one card inside 300
60
+ ms of that time. Two tests hold the port to the page:
61
+
62
+ - **`tests/test_oracle.py`.** `tools/js_oracle.mjs` loads those files
63
+ unmodified on a page that does nothing, with a seeded `Math.random` and a
64
+ clock of its own. It plays 40 games through the `Solitaire` class the way
65
+ the page's clicks reach it: the stock, a card, an empty foundation or
66
+ column, the felt, the menu's pauses and New. Half are a random clicker
67
+ that now and then clicks the same card again quickly. Half are a greedy
68
+ player, which won 16 of its 285 deals. That is 134,958 clicks and clock
69
+ steps. The test makes the same ones through the port and requires the same
70
+ state after every one: every pile, face up or down, the card held and
71
+ where from, moves, score, the seconds on the clock, whether it runs, and
72
+ the win screens set going. Widening the double click to 310 ms fails it at
73
+ seed 1, event 266; a bonus of x36 fails it at seed 2, event 3,194.
74
+ - **`tests/test_rules.py`.** It sets up by hand the cases worth pinning,
75
+ including the fixes below, so that undoing one shows as a change.
76
+
77
+ ## Fixed in both
78
+
79
+ - **Only a column's top card goes to a foundation.** The page checked the card
80
+ picked and moved the column's top card: with the 5♥ and 4♠ face up and the
81
+ hearts at 4, picking the 5♥ put the 4♠ on the hearts. Now nothing moves.
82
+ - **A card on a foundation stays there.** Moving one to another foundation
83
+ counted a move and moved nothing; now it counts nothing.
84
+ - **A won table is finished.** It still played: a king taken off its
85
+ foundation and put back won again, with a second bonus and a second score.
86
+ A win now stops the clock for good, and Help and the scores no longer start
87
+ it again.
88
+ - **The bonus is paid on the time to the win,** read at the winning move, not
89
+ on the timer's last tick up to a second before it. A win under 30 seconds is
90
+ paid as 30, so the bonus never falls as a win gets faster: it was 24,500,000
91
+ at one second and nothing under one.
92
+ - **The clock holds for every reason at once.** A modal and the page being
93
+ hidden each hold it, and it runs again only once neither does. A new deal
94
+ starts it clean, and drops a win screen still waiting from the last deal.
95
+ - **A draw puts down a waste card held.** The page kept it held by its old
96
+ place on the waste, so the next move took whatever card the draw had put
97
+ there. Worse, holding the waste's king, turning the waste back into the
98
+ stock and clicking an empty column pushed `undefined` into the column, and
99
+ every render after threw: in Chromium, "Cannot read properties of
100
+ undefined (reading 'getHTML')", and the table stopped answering clicks.
101
+ `drawFromStock` and `draw_from_stock` now put a held waste card down first;
102
+ a tableau card held stays held. In Chromium the same clicks on the fixed
103
+ page leave the column empty, and the king, drawn again, goes there. Taking
104
+ the fix out of the port alone fails the oracle at seed 1, event 252; the
105
+ trace has 57 draws with a waste card held, three of them turn-overs.
106
+
107
+ ## Licence
108
+
109
+ PolyForm Noncommercial 1.0.0, as for the rest of this repository. See
110
+ `LICENSE` and `NOTICE`.
File without changes
@@ -0,0 +1,27 @@
1
+ """``python -m klondike`` -- play the arcade's Solitaire: Klondike, draw three."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+
8
+ def main() -> None:
9
+ parser = argparse.ArgumentParser(prog="solitaire",
10
+ description="Klondike solitaire, draw three.")
11
+ parser.add_argument("--play", action="store_true", help="skip the title screen")
12
+ parser.add_argument("--seed", type=int, default=None, help="the same deals every time")
13
+ parser.add_argument(
14
+ "--ascii", action="store_true", dest="ascii_only",
15
+ help="draw with plain ASCII instead of block and box glyphs. Detected "
16
+ "automatically from the terminal's encoding; this forces it. "
17
+ "MAGMACRUNCH_ASCII=1 says the same for every cabinet.",
18
+ )
19
+ args = parser.parse_args()
20
+
21
+ from klondike.app import run
22
+
23
+ run(args.seed, args.play, args.ascii_only)
24
+
25
+
26
+ if __name__ == "__main__":
27
+ main()
@@ -0,0 +1,98 @@
1
+ """Wiring -- what outlives one deal, and how the screens reach it.
2
+
3
+ Everything that draws lives in :mod:`klondike.scenes`; everything that decides
4
+ lives in :mod:`klondike.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
+ import time
13
+ from collections.abc import Callable
14
+ from typing import Any
15
+
16
+ from magmacrunch.engine.scores import ScoreBook
17
+
18
+ from klondike.arcade import INFO
19
+ from klondike.scenes import GameScene, TitleScene
20
+
21
+
22
+ class SolitaireApp:
23
+ def __init__(self, host: Any, seed: int | None = None,
24
+ scores: ScoreBook | None = None,
25
+ clock: Callable[[], float] | None = None):
26
+ self.host = host
27
+ #: Where each deal's shuffle comes from. Seeded, so ``--seed``
28
+ #: replays a whole session.
29
+ self.rng = random.Random(seed)
30
+ #: The board the browser posts to: the score of a won game, with its
31
+ #: time beside it.
32
+ self.scores = scores or ScoreBook(INFO.scoreboard)
33
+ #: Seconds, for the page's clock. A test hands in its own.
34
+ self.clock = clock or time.monotonic
35
+ self.initials = "AAA"
36
+ self.last_rank: int | None = None
37
+ self.root_scene = TitleScene(self)
38
+
39
+ @property
40
+ def renderer(self):
41
+ return self.host.renderer
42
+
43
+ def new_rand(self):
44
+ return random.Random(self.rng.randrange(1 << 32)).random
45
+
46
+ def start_game(self) -> None:
47
+ self.host.push_scene(GameScene(self))
48
+
49
+ def show_scores(self) -> None:
50
+ from klondike.scenes import ScoresScene
51
+
52
+ self.host.push_scene(ScoresScene(self))
53
+
54
+ def show_rules(self) -> None:
55
+ from klondike.scenes import RulesScene
56
+
57
+ self.host.push_scene(RulesScene(self))
58
+
59
+ def enter_initials(self, score: int, extra: dict) -> None:
60
+ from klondike.scenes import InitialsScene
61
+
62
+ self.host.push_scene(InitialsScene(self, score, extra))
63
+
64
+ def to_title(self) -> None:
65
+ self.host.pop_scene()
66
+
67
+ def leave(self) -> None:
68
+ self.host.pop_scene()
69
+
70
+ def qualifies(self, score: int) -> bool:
71
+ """``isHighScore``: above nothing, and in the top ten."""
72
+ return score > 0 and self.scores.qualifies(score)
73
+
74
+ def record(self, score: int, initials: str | None = None,
75
+ extra: dict | None = None):
76
+ self.initials = initials or self.initials
77
+ result = self.scores.save(self.initials, score, **(extra or {}))
78
+ self.last_rank = result.rank
79
+ return result
80
+
81
+ @property
82
+ def scene(self):
83
+ return self.host.scene
84
+
85
+
86
+ def run(seed: int | None = None, skip_title: bool = False,
87
+ ascii_only: bool = False) -> None:
88
+ """Play Solitaire as its own command."""
89
+ from magmacrunch.engine.core.tui_host import TuiHost
90
+ from magmacrunch.engine.ui.glyphs import Glyphs
91
+
92
+ host = TuiHost(title=INFO.title, fps=INFO.fps, hold_ms=INFO.hold_ms,
93
+ glyphs=Glyphs.detect(ascii_only=ascii_only))
94
+ app = SolitaireApp(host, seed)
95
+ host.push_scene(app.root_scene)
96
+ if skip_title:
97
+ host.push_scene(GameScene(app))
98
+ 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 klondike import theme
11
+
12
+ INFO = GameInfo(
13
+ key="solitaire",
14
+ title="Solitaire",
15
+ blurb="Klondike, draw three, against the clock.",
16
+ # The frame rate moves the page's clock and the cursor, nothing else.
17
+ # Edge input, so one arrow is one step.
18
+ fps=20,
19
+ hold_ms=0,
20
+ min_cols=theme.MIN_COLS,
21
+ min_rows=theme.MIN_ROWS,
22
+ accent=theme.PINK,
23
+ )
24
+
25
+
26
+ class SolitaireGame:
27
+ """Satisfies :class:`magmacrunch.engine.arcade.ArcadeGame`."""
28
+
29
+ info = INFO
30
+
31
+ def start(self, host: Any) -> Any:
32
+ from klondike.app import SolitaireApp
33
+
34
+ return SolitaireApp(host).root_scene
35
+
36
+
37
+ GAME = SolitaireGame()
38
+
39
+ __all__ = ["GAME", "INFO", "SolitaireGame"]