Metadata-Version: 2.5
Name: magmacrunch-chinese-checkers
Version: 0.1.0
Summary: Chinese checkers against the computer, for two to six. Terminal version.
Project-URL: Homepage, https://magmacrunch.com/arcade/chinese-checkers/
Author: magmacrunch media
License-Expression: PolyForm-Noncommercial-1.0.0
License-File: LICENSE
License-File: NOTICE
Keywords: arcade,board,chinese-checkers,games,terminal,tui
Requires-Python: >=3.10
Requires-Dist: magmacrunch>=0.7.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Requires-Dist: websockets>=14.0; extra == 'dev'
Provides-Extra: online
Requires-Dist: websockets>=14.0; extra == 'online'
Description-Content-Type: text/markdown

# Chinese Checkers (terminal)

Chinese checkers against the computer, in a terminal, for two, three, four or
six players: you and the rest computers. It is the second version of the game,
beside `web/`. It runs on the
[magmacrunch](https://github.com/magmacrunch-media/magmacrunch) engine's
terminal backend, either as a cabinet in the magmacrunch arcade or as its own
command, and plays the same either way.

```
pipx install magmacrunch-chinese-checkers
chinese-checkers                     # the title, then how many play
chinese-checkers --play --players 6  # straight into a six-player game
```

For working on it, from this folder:

```
pip install -e ".[dev]"
python -m chinese_checkers
```

## Keys

| key | |
|---|---|
| `←` `→` | along the row |
| `↑` `↓` | to the row above or below, keeping to your column |
| `ENTER` | pick up the piece under the cursor; on a marked hole, put it down |
| `Esc` | put the piece back; with nothing picked up, the title (and from there, the arcade) |
| `R` | new game, same number of players |
| `Q` | quit |

Each player's pieces are their initial in their colour: C (cyan, you),
M, G, Y, O and P. So six players stay apart with no colour at all, and under
`--ascii`. Once a piece is picked up, a `✦` marks every hole it can reach:
green for a step, yellow for the end of a chain of hops. Each player's empty
goal holes are tinted their colour. The cursor is bracketed as well as shaded,
so it shows under `NO_COLOR`. The panel lists everyone's pieces home and whose
turn it is. The last moves are written as holes, a letter along the row and a
number for the row from the top: `e4-e5` for a step, `e4xg6xi8` for a chain.
Each computer moves after half a second, as on the page.

The star is drawn with its points up and down, where the page has them to the
sides. On the page you start at the upper right; here you start at the top. It
is the same board turned a twelfth of a turn, and every hole has the same
neighbours. Points to the sides would need 33 rows of terminal; this needs 17.

Needs a 77x19 terminal.

## Playing people online (a prototype)

PLAY ONLINE on the title, or `chinese-checkers --online`, connects to the
arcade's own multiplayer server, the one behind the page's PLAY ONLINE button.
Up to six can share a room, in browsers and terminals. Type a name, then press
`C` to create a room and read its four-letter code to the others, or `J` to
join one. The host starts once two or more are in. You play the colour and
point of the order you joined in, and everyone sees the same star, the page's
way. You move as against the computers. `X` then `Y` resigns.

```
pip install "magmacrunch-chinese-checkers[online]"     # websockets, only for this
chinese-checkers --online
chinese-checkers --online --server ws://127.0.0.1:8772  # a web/server.py on this machine
```

The server keeps the rules. This client sends the hole a piece leaves and the
hole it lands on, and draws the board the server sends back. It works out the
legal moves only to mark them, with `rules.py`, which `test_server_parity.py`
holds to the server's own. `chinese_checkers/online.py` holds the protocol,
split into `Table` (the server's state, with no I/O) and `Connection` (the
WebSocket, on its own thread). `online_scenes.py` reuses the computer game's
screen with the server's state in place of a local game.

`tests/test_online.py` runs `web/server.py` in-process on a free port and
plays it over real sockets. It covers:

- **a whole three-player game,** each seat playing the careful player's move.
  After every move, every client's board must match a shadow kept with
  `rules.py`;
- two and three players taking their seats;
- out-of-turn and illegal moves, which the server ignores;
- a resignation, a wrong room code, a player leaving and a dead address;
- **three complete terminal sessions**, through the lobby, a move from each
  seat and a resignation, using keys alone.

It never contacts the live server. By hand, the real page in headless Chromium
played a terminal client 30 moves each way, hop chains included, and the two
boards still matched.

The server's rules, which a prototype can only follow:

- **Seats go in joining order, 0 to 5.** Three players online are seats 0, 1
  and 2, where the page's game against the computers uses 0, 2 and 4. So two
  of them face each other and the third heads for an empty point. Five can
  play online, which the page's own menu doesn't offer.
- **One resignation ends the game for everyone,** however many play. The next
  seat round wins outright.
- **Nobody can take a leaver's place.** If a player leaves, the game waits on
  their turn for good.

What a prototype has not done yet:

- **Nobody has played the live server from a terminal**, only a copy of it run
  locally. The default address is `wss://magmacrunch.duckdns.org/chinese-checkers`,
  which is what `mpServerFor('chinese-checkers', 8772)` gives the public site.
- **No reconnect.** A dropped connection says so, and the game is lost.
- **No chat**, which the browser has.

**The page used to draw no pieces online.** Its renderer draws only the
players in `Board.getActivePlayers()`. That list was filled by `Board.reset()`,
which the online game never calls, so a browser player saw an empty star.
After a game against the computers in the same page, it was that game's
seating instead, so some seats stayed hidden. The page now loads a server
state with `Board.setOnlineState()`, which sets the server's seats, and
`web/tests/test-online-board.js` counts every piece the renderer draws.
`test_online.py` checks the same thing here, on every terminal: every seat's
pieces, at their own holes.

## It is the browser's game, and the browser's opponent

`chinese_checkers/rules.py` is `web/js/config.js`, `board.js`, `ai.js` and
`game.js`, ported function for function. The board's 121 holes are kept in
the page's own order, because that is the order the page searches for moves,
and so the order its computer breaks ties in. Three tests hold the port to the
other versions:

- **`tests/test_oracle.py`.** `tools/js_oracle.mjs` loads those files
  unmodified and plays 48 games through `Game`'s own API: twelve each with two,
  three, four and six players. Half are against a random player, half against
  a careful one who wins its four-player games and loses the rest to the
  computers. Nothing on
  the page is random, so the player's choices are all there is to record. The
  test replays every game through the port and requires the same state after
  each of about 16,000 moves: whose turn, all 121 holes, and every legal move
  in the page's order, hops and all. It also checks that the coordinate tables
  are the page's.
- **`tests/test_server_parity.py`.** It compares the port's moves with
  `web/server.py`'s, piece by piece, in positions from played games of every
  size.
- **`tests/test_rules.py`.** It sets up by hand the cases worth pinning.

## The computer, fixed in both

**The page's computer used never to get home.** It scored ground gained
towards the goal's centre and paid 15 for every hop, gained or not. A sideways
hop and back outscored a step forward, so it shuffled: against itself it never
finished a game at any size, and it won none of 80 games against a scripted
player. The centre was the wrong mark for the last pieces too, which must fill
the holes at the goal's edges.

Now:
- each piece makes for the nearest goal hole not yet held, and a long hop
  scores for the ground it gains and no more;
- a piece already home moves only deeper, to leave the way in open.

Against itself it finishes at every size, in close races. Against the
scripted player every game finishes, and it wins most: 18 of 20 with two
players, 13 with three, 10 with four and 18 with six. This is fixed in
`web/js/ai.js` and here together, and `web/tests/test-ai.js` holds the page
to it.

## The page's rules

These are the page's own, kept because the oracle requires them:

- **Only other players' pieces can be hopped.** Standard Chinese checkers lets
  you hop your own as well. `web/server.py` has the same rule, so single-player
  and online agree.
- **A computer with no move holds the game for good.** On the page,
  `executeAIMove` gives up and nothing calls it again. Here the panel says so,
  and `R` starts a new game. It takes a computer boxed in completely, which is
  rare.
- **Nothing stops a piece sitting in another player's goal.** With many
  players this can leave someone unable to finish.
- **The page says nothing when a game ends.** It goes back to its start screen.
  Here the panel says who won.

There is no high-score table, because the page keeps none.

## Licence

PolyForm Noncommercial 1.0.0, as for the rest of this repository. See `LICENSE`
and `NOTICE`.
