diff --git a/.gitignore b/.gitignore index e2eef37..0edcb84 100644 --- a/.gitignore +++ b/.gitignore @@ -4,18 +4,3 @@ /card-games-*-src.tar.gz *.sketch *~ -# Generated documentation (built from doc/card-games.texi with makeinfo) -/doc/card-games.info -/doc/card-games.html -/doc/card-games.pdf -/doc/card-games.aux -/doc/card-games.log -/doc/card-games.toc -/doc/card-games.cp -/doc/card-games.cps -/doc/card-games.fn -/doc/card-games.fns -/doc/card-games.ky -/doc/card-games.pg -/doc/card-games.tp -/doc/card-games.vr diff --git a/Makefile b/Makefile index 9bd7920..638e9a4 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # Makefile for card-games -- byte-compile, test, and package. EMACS ?= emacs PKG = card-games -VERSION = 1.0.91 +VERSION = 1.0.90 # Source files in dependency order (cg-core first). EL = cg-core.el cg-svg.el cg-render.el cg-net.el cg-bid.el cg-gaps.el cg-bid-ui.el cg-bid-net.el cg-solitaire.el cg-trick.el cg-eights.el cg-patience.el cg-president.el cg-rummy.el cg-rum500.el cg-handfoot.el cg-match.el cg-cribbage.el cg-scopa.el cg-trick-ext.el cg-spite.el cg-bridge.el cg-crapette.el card-games.el ELC = $(EL:.el=.elc) @@ -11,17 +11,9 @@ TAR = $(TARDIR).tar SRCTAR = $(PKG)-$(VERSION)-src.tar.gz DIST = dist BATCH = $(EMACS) -Q --batch -L . -MANUAL = doc/card-games.texi -VERTEXI = doc/version.texi -INFO = doc/card-games.info -IMAGES = doc/images/klondike.png doc/images/hearts.png -MAKEINFO ?= makeinfo -TEXI2PDF ?= texi2pdf -README = README.md -EXTRA = README.org $(README) $(PKGDESC) $(MANUAL) $(VERTEXI) $(IMAGES) +EXTRA = README.org $(PKGDESC) -.PHONY: all compile test clean distclean checkdoc lint package tarball elpa release help \ - info info-emacs html pdf docclean version readme hooks +.PHONY: all compile test clean distclean checkdoc lint package tarball elpa release help help: @echo "card-games $(VERSION) -- make targets:" @@ -29,13 +21,6 @@ help: @echo " test run the ERT test suite" @echo " checkdoc run checkdoc on all sources" @echo " lint run package-lint (if installed)" - @echo " version regenerate $(VERTEXI) from VERSION" - @echo " readme export README.org -> README.md (Emacs batch)" - @echo " hooks install the git pre-commit hook" - @echo " info build the Info manual ($(INFO)) with makeinfo" - @echo " info-emacs build the Info manual with Emacs alone (fallback)" - @echo " html build the one-file HTML manual" - @echo " pdf build the PDF manual (needs a TeX installation)" @echo " package build the installable package tarball ($(TAR))" @echo " elpa build a one-package ELPA archive in $(DIST)/" @echo " release clean + test + package + source tarball" @@ -81,8 +66,8 @@ elpa: tarball # Source snapshot for a GitHub release. Archive an explicit file list # (not ".") so the growing output tarball and editor lock files are never # read mid-write -- which is what caused "tar: .: file changed as we read it". -SRCFILES = $(EL) $(EXTRA) build.el hooks Makefile .gitignore test -release: distclean version readme test tarball +SRCFILES = $(EL) $(EXTRA) Makefile .gitignore test +release: distclean test tarball rm -f $(SRCTAR) tar --transform 's,^,$(TARDIR)/,' \ --exclude='*.elc' --exclude='*.tar' --exclude='*.tar.gz' \ @@ -90,58 +75,8 @@ release: distclean version readme test tarball -czf $(SRCTAR) $(SRCFILES) @echo "Built $(SRCTAR) and $(TAR) for release $(VERSION)" -# Documentation. The Texinfo source lives in doc/; the Info manual is -# built from it with makeinfo. doc/version.texi carries the version and -# is committed; `make version' regenerates it from VERSION (run at release). -version: - @printf '@set VERSION %s\n@set UPDATED %s\n@set YEAR %s\n' \ - "$(VERSION)" "$$(date '+%-d %B %Y')" "$$(date +%Y)" > $(VERTEXI) - @echo "Wrote $(VERTEXI) (VERSION $(VERSION))" - -# README: Org is the source; GitHub and MELPA render Markdown better than -# Org, so we export README.org -> README.md with Emacs batch (ox-md). The -# git pre-commit hook (make hooks) keeps README.md in step automatically. -readme: $(README) -$(README): README.org build.el - $(BATCH) -l build.el - -# Install the pre-commit hook into this checkout's .git/hooks. -hooks: - @if [ -d .git ]; then \ - cp hooks/pre-commit .git/hooks/pre-commit && \ - chmod +x .git/hooks/pre-commit && \ - echo "Installed .git/hooks/pre-commit"; \ - else \ - echo "No .git directory here; skipping hook install."; \ - fi - -info: $(INFO) -$(INFO): $(MANUAL) $(VERTEXI) - $(MAKEINFO) -o $@ $(MANUAL) - -# Fallback: build the Info manual with Emacs alone (no makeinfo needed). -# Handy on Windows/MSYS2 where Emacs is present but texinfo may not be. -# The native formatter is lower fidelity than makeinfo, so `make info' -# stays the default; this is only for a makeinfo-less environment. -info-emacs: $(MANUAL) $(VERTEXI) - cd doc && $(EMACS) -Q --batch --eval "(require 'texinfmt)" \ - -f batch-texinfo-format card-games.texi - -html: $(MANUAL) $(VERTEXI) - $(MAKEINFO) --html --no-split -o doc/card-games.html $(MANUAL) - -pdf: $(MANUAL) $(VERTEXI) - cd doc && $(TEXI2PDF) -q card-games.texi - -docclean: - rm -f $(INFO) doc/card-games.html doc/card-games.pdf \ - doc/card-games.aux doc/card-games.cp doc/card-games.cps \ - doc/card-games.fn doc/card-games.ky doc/card-games.log \ - doc/card-games.pg doc/card-games.toc doc/card-games.tp \ - doc/card-games.vr doc/card-games.fns - clean: rm -f $(ELC) -distclean: clean docclean +distclean: clean rm -rf $(DIST) $(TARDIR) $(TAR) $(SRCTAR) diff --git a/NEWS b/NEWS index 91faeb2..c2f0156 100644 --- a/NEWS +++ b/NEWS @@ -1,18 +1,6 @@ card-games NEWS -- user-visible changes ======================================== -* Version 1.0.91 (pretest2: playtest) - -** Documentation - - A complete Info manual now ships with the package: ~C-h i~ and choose - *Card Games* (or ~M-x info RET (card-games) RET~). It is built with - makeinfo (~make info~); ~make info-emacs~ builds it with Emacs alone - for environments without a texinfo installation. - - The README is generated from ~README.org~ to ~README.md~ with Emacs - batch (~ox-md~), so GitHub and MELPA render it faithfully; a - ~pre-commit~ hook (~make hooks~) keeps ~README.md~ in step. Added - board screenshots and refreshed the game list and customization notes. - * Version 1.0.90 (pre-test snapshot) This release grows the package from the five-game 1.0.60 candidate to a @@ -51,16 +39,6 @@ the mouse as well as the keyboard. the middle, and your fanned hand, with the legal cards you may play ringed. Click a card to play it. Set ~cg-trick-svg-cards~ to nil for the plain-text board. - - The Emacs emblem in the full-window (SVG) UIs is now switchable via - ~cg-svg-emacs-logo~: it embeds a real logo that ships with Emacs -- - the modern icon (default), the classic icon, a GNU head, or the - splash image -- or the small built-in drawn emblem, or none. - - More card backs, and a random default. ~cg-svg-card-back~ now offers - ~dots~, ~rings~, ~solid~, ~lattice~, ~waves~, ~diamond~, and four - backs stamped with an Emacs logo (~emacs~, ~emacs-classic~, ~gnu~, - ~splash~). It defaults to ~random~, which picks a back for the - session; ~M-x cg-svg-shuffle-card-back~ (or reopening the game menu) - rolls a new one. - Full SVG board for the rummy games (Gin Rummy, Rummy, Rummy 500): the stock and discard, the melds already down on the table, and your fanned hand with cursor, marks, and lay-off hints; click a card to @@ -68,15 +46,6 @@ the mouse as well as the keyboard. - The same board now covers Hand & Foot (each team's books, and your hand or foot) and the fishing games Scopa and Casino (the loose table cards and the deck). - - SVG boards for Crazy Eights (the discard, the suit in play, and the - stock), Spite & Malice (the four centre piles, your goal and discard - piles), and Old Maid (the opponents and your hand; click one of the - next player's face-down cards to draw it). - - SVG boards for Cribbage (a peg-track for each player, the starter, the - pegging count and cards, and the crib at the show) and Bridge (the - rubber and contract, all four seats with the dummy exposed, the trick - in the middle, and the hand you are playing). With these every game - in the collection now has a full graphical board. ** Rummy-family rules completed - Rummy 500: take a card from anywhere in the discard pile (key ~T~) -- @@ -93,30 +62,6 @@ the mouse as well as the keyboard. "Next hand" button and the hand fan. - Live multiplayer 500 over TCP (~M-x cg-bid-host~ / ~M-x cg-bid-join~). -** Pick how the games are drawn - - From the ~M-x card-game~ menu (or ~M-x card-games-set-treatment~) you - can switch all the games between ~text~ (UNICODE cards), ~svg~ (drawn - cards), and ~full~ (the full-window SVG table where a game has one, - Gaps and 500). - -** Adjustable computer opponents - - A new ~cg-ai-level~ (easy, normal, hard) sets how hard the computer - plays, and you can change it right from the ~M-x card-game~ menu (or - with ~M-x card-games-set-ai-level~). Russian Bank (Crapette) plays - all three levels; the trick-taking games play a random legal card on - easy. Other games ignore it for now. - -** A craftier Russian Bank (Crapette) opponent - - On normal and hard the computer empties its reserve first (the pile - you must clear to win) and prefers loading its cards onto you; on hard - it also looks a move ahead to rearrange the houses when doing so frees - a card it could not otherwise place. Easy keeps the old simple play. - -** Bug fixes - - Alternating-colour building (the tableau solitaires and Russian Bank) - wrongly treated a diamond and a heart as opposite colours, so a red - card could be built on another red card. Both are red; fixed. - ** Playtest fixes - ~q~ now returns to the ~M-x card-game~ menu from any game, instead of burying the buffer and leaving the previous buffer on screen. diff --git a/README.md b/README.md deleted file mode 100644 index 3644c7d..0000000 --- a/README.md +++ /dev/null @@ -1,306 +0,0 @@ -Card games for Emacs: about thirty of them, from Klondike and FreeCell -to Hearts, 500, Gin, Cribbage, and two-player Russian Bank against the -computer. Every game plays with the keyboard everywhere and with the -mouse on a graphical display. - -![img](doc/images/klondike.png) - -On a graphical display the cards are drawn as SVG; in a terminal they -fall back to UNICODE glyphs (customize `card-game-symbols`). You can -switch how every game is drawn from the menu – `text` (UNICODE), -`svg` (drawn cards), or `full` (a full-window SVG table) – and dial the -computer opponents between `easy`, `normal`, and `hard`. - -A full Info manual ships with the package: after installing, `C-h i` and -choose **Card Games**, or `M-x info RET (card-games) RET`. - - -# Games - -To open the game menu type `M-x card-game`, or start a game directly -with its command. From the menu you can also switch the card treatment -(text / SVG / full-window) and the AI difficulty. - - -## Trick-taking - -- `cg-bid` – 500 (Bid). Win the auction, name the trump suit, then take - tricks with your partner to reach 500 points before the opposing pair. - Also playable live over the network (`M-x cg-bid-host` / `cg-bid-join`). -- `cg-hearts` – Hearts. Avoid taking hearts and the Queen of Spades, or - take them all to "shoot the moon"; lowest score loses. -- `cg-spades` – Spades. Partnership bidding to 500; spades are always - trump. Make your side's combined bid, mind the bags, dare a nil. -- `cg-whist` – Whist. Trump is the turned card, there is no bidding; - score one point for each trick past the book of six. -- `cg-ohhell` – Oh Hell. The hand shrinks each round; bid the exact - number of tricks you will take, no more and no fewer. - - -## Solitaire - -- `cg-montana` – Montana (also called Gaps). Each row is anchored by a - Two and built upward in one suit, 2 through King; slide cards into the - gaps until all four rows are sorted. -- `cg-gaps` – an alias for `cg-montana`. -- `cg-hells-half-acre` – the build-down variant: each row is anchored by - a King and built downward, King through 2. -- `cg-klondike` – Klondike, the classic "Solitaire": build the four - foundations up by suit from the Ace. -- `cg-freecell` – FreeCell: every card in view, four free cells, a game - of nearly pure skill. -- `cg-spider` – Spider (two decks): build down regardless of suit, but - only same-suit runs move; clear eight King-to-Ace runs. -- `cg-yukon` – Yukon: Klondike's layout dealt mostly face up, with any - buried group movable and no stock. -- `cg-canfield` – Canfield: a 13-card reserve and a foundation base rank - set by the deal; foundations wrap King to Ace. -- `cg-forty-thieves` – Forty Thieves: two decks, ten columns, eight - foundations, build down by suit, and no second pass through the stock. -- `cg-scorpion` – Scorpion: build down by suit and free any buried group - to assemble four King-to-Ace runs. -- `cg-golf` – Golf: clear the layout by playing exposed cards one rank - above or below the waste top. -- `cg-tripeaks` – TriPeaks: the same, on three overlapping peaks, with - Ace-King wrapping for long chains. -- `cg-pyramid` – Pyramid: remove pairs of exposed cards whose ranks sum - to thirteen; Kings go alone. - - -## Shedding and climbing - -- `cg-eights` – Crazy Eights. Match the suit or rank of the discard; - eights are wild and let you name the next suit. -- `cg-president` – President (Scum). Climb: play one to four of a rank, - beat it or pass; first out rules, last out scrubs, and the roles trade - cards on the next deal. - - -## Rummy - -- `cg-gin` – Gin Rummy. A two-handed duel: draw or take the discard, - build sets and runs, and knock once your deadwood is ten or less, or go - gin with none; your opponent then lays off and may undercut you. First - to 100 wins. -- `cg-rummy-basic` – Rummy. Meld sets and runs onto the table and lay - cards off onto them; empty your hand to go out and score the cards left - in the other hands. -- `cg-rum500` – Rummy 500. As above, but you score the cards you lay - down and lose the cards left in your hand; first past 500 wins. Take a - buried discard card with `T`: you take it and every card above it, and - meld the chosen card at once. -- `cg-handfoot` – Hand & Foot. A partnership Canasta cousin: play a hand - and then a foot, build books of a rank with Twos and Jokers wild, and go - out once your side has completed two of them. Each round opens with a - rising go-down minimum (50, 90, 120, 150); red threes are bonus cards; - and you can pick up the discard pile (`p`) by melding its top card with - two matching naturals. - - -## Matching - -- `cg-go-fish` – Go Fish. Ask another player for a rank you hold; - collect all four to lay down a book, and make the most books. -- `cg-old-maid` – Old Maid. One Queen is set aside; discard pairs and - draw blind from your neighbour, and do not be left with the odd Queen. - - -## Pegging - -- `cg-cribbage` – Cribbage. Lay two cards to the crib, cut a starter, - peg toward 31, then count fifteens, pairs, runs, flushes, and his nobs. - Two-handed to 121. - - -## Capturing - -- `cg-scopa` – Scopa. A 40-card deck; capture table cards by value and - sweep the board for a scopa. Score cards, coins, the sette bello, and - primiera to 11. -- `cg-casino` – Casino. The full deck; capture by pairs and sums and - score cards, spades, the casinos, and aces to 21. - - -## More trick-taking - -- `cg-euchre` – Euchre. A 24-card deck with the two bowers; order up or - call trump and take three of five tricks. Partnership to 10. -- `cg-pitch` – Auction Pitch. Bid for the pitch; your first lead sets - trump. Score High, Low, Jack, and Game; first to 7. -- `cg-briscola` – Briscola. A fixed trump turned from the deal and no - obligation to follow suit; capture the Aces and Threes. Partnership to - 61 of the 120 points. - - -## Climbing patience - -- `cg-spite` – Spite & Malice. Race the computer to empty your goal - pile onto shared centre piles that build Ace to Queen; Kings are wild. - - -## Bridge - -- `cg-bridge` – Contract Bridge. A full auction (bids, pass, double, - redouble), play with the dummy exposed, and classic rubber scoring with - vulnerability. You are South; when you declare you play the dummy too. - The bidding AI is a small natural system, sensible but no expert. - - -## Two-player patience - -- `cg-russian-bank` / `cg-crapette` – Russian Bank (Crapette). A race - against the computer: build the eight shared foundations up by suit, - build the shared houses down in alternating colour, and empty your - reserve to win. Load your cards onto your opponent's piles when the - rank and suit line up, mind the "stop" rule that ends your turn if you - skip a foundation play, and sequence house-to-house moves within the - free houses (`[` / `]` choose how many cards of a run to drop on an - empty house). The opponent plays all three difficulty levels. - - -# TODO - -- [X] make the suit symbols customizable (`cg-symbols`) and obey them -- [X] a Texinfo manual -- [ ] finish `checkdoc` docstrings across the per-game files - (the shared engine files are clean; `make compile` is warning-free) -- [ ] renderer "skins": let games subclass the display components (text, - SVG, full-window SVG) -- [X] a manual card-size control for the full-window SVG UI -- [ ] more games - - -# Install - - -## From the package tarball - - make package # builds card-games-1.0.91.tar - -Then in Emacs: `M-x package-install-file RET card-games-1.0.91.tar`. - - -## With `use-package` - -Once the package is on your `load-path` (installed from the tarball or an -ELPA archive), the whole collection loads from the single `card-games` -feature: - - (use-package card-games - :commands (card-game cg-klondike cg-bid cg-hearts cg-gin cg-crapette)) - -Then `M-x card-game` for the menu. - - -## From a local ELPA archive - - make elpa # builds dist/ (archive-contents + tar) - - (add-to-list 'package-archives '("cg" . "/path/to/dist/")) - (package-refresh-contents) - (package-install 'card-games) - - -## Manually - -Put the `cg-*.el` and `card-games.el` files on your `load-path` and -`(require 'card-games)`. - - -# Manual - -A complete Info manual is included. After installing, `C-h i` and pick -**Card Games**, or `M-x info RET (card-games) RET`. To build it from a -source checkout: - - make info # builds doc/card-games.info with makeinfo - -The manual text is licensed CC-BY-4.0; the code is GPL-3.0-or-later. - - -# Playing - -Every game works with the keyboard everywhere and with the mouse on a -graphical display. - -- 500: `b` bid, `p` pass, arrows + `RET` to play (or click a card), - `n` next hand / new game, `?` help. -- Gaps: arrows to move (or `hjkl` when `cg-keys` is `classic`), `RET` to - fill a gap (or click it), `r` redeal, `u` undo, `n` new, `?` help. -- Klondike / FreeCell / Spider / Yukon: arrows move between piles, `RET` - picks up a movable run and drops it, `f` sends a card to a foundation, - `a` auto-plays everything it can, `u` undo, `n` new, `?` help. On the - stock pile, `RET` deals or recycles. -- Hearts / Spades: arrows choose a card, `RET` plays it (in Hearts, `RET` - marks a card to pass and `p` sends the three), `n` new match, `?` help. -- Crazy Eights: arrows choose, `RET` plays, `d` draws, `x` passes, `n` - new deal, `?` help. - -On a graphical display, `v` toggles the full-window SVG table. A -**Card size** slider (and the `+` / `-` / `0` keys) resizes the cards, and -in 500 the `? Help / Rules` button explains play. Every game accepts the -mouse: click cards, board slots, buttons, and the slider. - -![img](doc/images/hearts.png) - - -# Testing - -This is a 1.0.91 pre-test snapshot. To try it: - -1. `make compile && make test` – should be warning-free and all green. -2. `M-x card-game` opens the menu, or jump straight in, e.g. - `M-x cg-klondike`, `M-x cg-bid` (500), `M-x cg-gin`, `M-x cg-handfoot`. -3. On a graphical display, press `v` in 500 for the full-window SVG - table and play entirely with the mouse: click a bid, click five kitty - cards and the **Discard** button, click cards to play, move the **Card - size** slider, and open `? Help / Rules`. - -Feedback most wanted: anything a mouse-only player who is new to Emacs -finds confusing or unreachable, rules bugs, and rendering glitches. - - -# Customization - -`M-x customize-group RET cg-svg` and `RET card-games`: - -- `cg-ai-level` – how hard the computer plays: `easy`, `normal`, or - `hard` (also on the `M-x card-game` menu, or `M-x - card-games-set-ai-level`). -- `card-games-treatment` – how the games are drawn: `text`, `svg`, or - `full` (also on the menu, or `M-x card-games-set-treatment`). -- `cg-svg-theme-colors` – derive the highlight ring and card backs - from your theme (on by default). -- `cg-svg-highlight-color` – the cursor/selection ring (gold by default). -- `cg-bid-felt-color` – the 500 table felt. -- `cg-svg-card-width`, `cg-svg-card-height`, `cg-svg-card-shadow`, - `cg-svg-font-family` – card appearance. -- `cg-svg-card-back` – the card-back design: `dots`, `rings`, `solid`, - `lattice`, `waves`, `diamond`, an Emacs-logo back (`emacs`, - `emacs-classic`, `gnu`, `splash`), or `random` (the default – picks - one for the session; `M-x cg-svg-shuffle-card-back` rolls a new one). -- `cg-svg-emacs-logo` – the emblem on the full-window table: - `modern` (default), `classic`, `gnu`, `splash`, `drawn`, or `none`. -- `cg-symbols` – the Unicode suit glyphs (and the joker) drawn on cards. -- `cg-svg-four-color` – draw a four-colour deck (clubs green, diamonds - blue-purple). -- `cg-keys` – `emacs` (default) or `classic` (adds vi-style `hjkl` and - `SPC`). -- `cg-bid-animate`, `cg-bid-ai-delay`, `cg-bid-trick-pause` – pace the - 500 AI so play is watchable and completed tricks linger. -- `M-x card-games-set-theme` – apply a preset (classic, dark, contrast). - - -# Development - - make compile # byte-compile (should be warning-free) - make test # run the ERT suite - make checkdoc # documentation lint - make release # clean + test + package + source tarball - - -# License - -GPL-3.0-or-later. See the file headers; add a COPYING file with the -full GPLv3 text for distribution. - diff --git a/README.org b/README.org index 900cd84..9a939c7 100644 --- a/README.org +++ b/README.org @@ -1,28 +1,16 @@ #+TITLE: card-games -- Play card games in Emacs #+AUTHOR: Corwin Brust -#+OPTIONS: toc:nil -Card games for Emacs: about thirty of them, from Klondike and FreeCell -to Hearts, 500, Gin, Cribbage, and two-player Russian Bank against the -computer. Every game plays with the keyboard everywhere and with the -mouse on a graphical display. +Card games for Emacs. -[[file:doc/images/klondike.png]] - -On a graphical display the cards are drawn as SVG; in a terminal they -fall back to UNICODE glyphs (customize ~card-game-symbols~). You can -switch how every game is drawn from the menu -- ~text~ (UNICODE), -~svg~ (drawn cards), or ~full~ (a full-window SVG table) -- and dial the -computer opponents between ~easy~, ~normal~, and ~hard~. - -A full Info manual ships with the package: after installing, ~C-h i~ and -choose *Card Games*, or ~M-x info RET (card-games) RET~. +Renders SVG by default when ~display-graphic-p~ is t and rsvg is +available. The default (UNICODE) symbols maybe customized by +configuring ~card-game-symbols~. * Games To open the game menu type ~M-x card-game~, or start a game directly -with its command. From the menu you can also switch the card treatment -(text / SVG / full-window) and the AI difficulty. +with its command. ** Trick-taking - ~cg-bid~ -- 500 (Bid). Win the auction, name the trump suit, then take @@ -128,19 +116,9 @@ with its command. From the menu you can also switch the card treatment vulnerability. You are South; when you declare you play the dummy too. The bidding AI is a small natural system, sensible but no expert. -** Two-player patience -- ~cg-russian-bank~ / ~cg-crapette~ -- Russian Bank (Crapette). A race - against the computer: build the eight shared foundations up by suit, - build the shared houses down in alternating colour, and empty your - reserve to win. Load your cards onto your opponent's piles when the - rank and suit line up, mind the "stop" rule that ends your turn if you - skip a foundation play, and sequence house-to-house moves within the - free houses (~[~ / ~]~ choose how many cards of a run to drop on an - empty house). The opponent plays all three difficulty levels. - * TODO - [X] make the suit symbols customizable (~cg-symbols~) and obey them -- [X] a Texinfo manual +- [ ] a Texinfo manual - [ ] finish ~checkdoc~ docstrings across the per-game files (the shared engine files are clean; ~make compile~ is warning-free) - [ ] renderer "skins": let games subclass the display components (text, @@ -151,19 +129,9 @@ with its command. From the menu you can also switch the card treatment * Install ** From the package tarball #+begin_src -make package # builds card-games-1.0.91.tar +make package # builds card-games-1.0.90.tar #+end_src -Then in Emacs: ~M-x package-install-file RET card-games-1.0.91.tar~. - -** With ~use-package~ -Once the package is on your ~load-path~ (installed from the tarball or an -ELPA archive), the whole collection loads from the single ~card-games~ -feature: -#+begin_src emacs-lisp -(use-package card-games - :commands (card-game cg-klondike cg-bid cg-hearts cg-gin cg-crapette)) -#+end_src -Then ~M-x card-game~ for the menu. +Then in Emacs: ~M-x package-install-file RET card-games-1.0.90.tar~. ** From a local ELPA archive #+begin_src @@ -179,17 +147,8 @@ make elpa # builds dist/ (archive-contents + tar) Put the ~cg-*.el~ and ~card-games.el~ files on your ~load-path~ and ~(require 'card-games)~. -* Manual -A complete Info manual is included. After installing, ~C-h i~ and pick -*Card Games*, or ~M-x info RET (card-games) RET~. To build it from a -source checkout: -#+begin_src -make info # builds doc/card-games.info with makeinfo -#+end_src -The manual text is licensed CC-BY-4.0; the code is GPL-3.0-or-later. - * Playing -Every game works with the keyboard everywhere and with the mouse on a +Both games work with the keyboard everywhere and with the mouse on a graphical display. - 500: ~b~ bid, ~p~ pass, arrows + ~RET~ to play (or click a card), @@ -210,10 +169,8 @@ On a graphical display, ~v~ toggles the full-window SVG table. A in 500 the ~? Help / Rules~ button explains play. Every game accepts the mouse: click cards, board slots, buttons, and the slider. -[[file:doc/images/hearts.png]] - * Testing -This is a 1.0.91 pre-test snapshot. To try it: +This is a 1.0.90 pre-test snapshot. To try it: 1. ~make compile && make test~ -- should be warning-free and all green. 2. ~M-x card-game~ opens the menu, or jump straight in, e.g. ~M-x cg-klondike~, ~M-x cg-bid~ (500), ~M-x cg-gin~, ~M-x cg-handfoot~. @@ -227,23 +184,12 @@ finds confusing or unreachable, rules bugs, and rendering glitches. * Customization ~M-x customize-group RET cg-svg~ and ~RET card-games~: -- ~cg-ai-level~ -- how hard the computer plays: ~easy~, ~normal~, or - ~hard~ (also on the ~M-x card-game~ menu, or ~M-x - card-games-set-ai-level~). -- ~card-games-treatment~ -- how the games are drawn: ~text~, ~svg~, or - ~full~ (also on the menu, or ~M-x card-games-set-treatment~). - ~cg-svg-theme-colors~ -- derive the highlight ring and card backs from your theme (on by default). -- ~cg-svg-highlight-color~ -- the cursor/selection ring (gold by default). - ~cg-bid-felt-color~ -- the 500 table felt. - ~cg-svg-card-width~, ~cg-svg-card-height~, ~cg-svg-card-shadow~, ~cg-svg-font-family~ -- card appearance. -- ~cg-svg-card-back~ -- the card-back design: ~dots~, ~rings~, ~solid~, - ~lattice~, ~waves~, ~diamond~, an Emacs-logo back (~emacs~, - ~emacs-classic~, ~gnu~, ~splash~), or ~random~ (the default -- picks - one for the session; ~M-x cg-svg-shuffle-card-back~ rolls a new one). -- ~cg-svg-emacs-logo~ -- the emblem on the full-window table: - ~modern~ (default), ~classic~, ~gnu~, ~splash~, ~drawn~, or ~none~. +- ~cg-svg-card-back~ -- card-back pattern: dots, rings, or solid. - ~cg-symbols~ -- the Unicode suit glyphs (and the joker) drawn on cards. - ~cg-svg-four-color~ -- draw a four-colour deck (clubs green, diamonds blue-purple). diff --git a/build.el b/build.el deleted file mode 100644 index 3e90ead..0000000 --- a/build.el +++ /dev/null @@ -1,77 +0,0 @@ -;;; build.el --- Batch Org -> Markdown export for card-games -*- lexical-binding: t; -*- - -;; Copyright (C) 2026 Corwin Brust -;; SPDX-License-Identifier: GPL-3.0-or-later - -;;; Commentary: - -;; A very small Emacs-batch exporter. It renders the project's Org -;; sources to Markdown siblings (foo.org -> foo.md) using the built-in -;; `ox-md' backend, so GitHub and MELPA -- which render Markdown more -;; faithfully than Org -- can display them. -;; -;; Run it by hand, from the Makefile, or from the git pre-commit hook: -;; -;; emacs -Q --batch -l build.el -;; -;; By default it exports the files named in `build-org-files' (README.org). -;; Set the CARD_GAMES_ORG environment variable to a space-separated list -;; to override that, e.g. to export more documents. It never touches -;; known-games.org (an internal research list) unless you ask for it. - -;;; Code: - -(require 'org) -(require 'ox-md) - -;; Keep batch mode from blocking on prompts. -(setq org-confirm-babel-evaluate nil - org-export-show-temporary-export-buffer nil - ;; A stray or not-yet-committed image link must never abort the run. - org-export-with-broken-links 'mark - make-backup-files nil) - -(defvar build-org-files '("README.org") - "Default list of Org files to export to Markdown. -Overridden by the CARD_GAMES_ORG environment variable when set.") - -(defvar build-inhibit-run nil - "When non-nil, loading build.el defines helpers but does not export. -ERT or an interactive session can bind this to exercise the helpers.") - -(defun build--targets () - "Return the list of Org files to export. -Honours the CARD_GAMES_ORG environment variable; falls back to -`build-org-files'." - (let ((env (getenv "CARD_GAMES_ORG"))) - (if (and env (not (string-empty-p (string-trim env)))) - (split-string (string-trim env) "[ \t\n]+" t) - build-org-files))) - -(defun build--export-one (orgfile) - "Export ORGFILE to a Markdown sibling. -Log the outcome; never signal, so one bad file cannot abort the run." - (cond - ((not (file-readable-p orgfile)) - (message "build: SKIP %s (not readable)" orgfile)) - (t - (message "build: exporting %s -> markdown" orgfile) - (with-current-buffer (find-file-noselect orgfile) - (condition-case err - (let ((out (org-md-export-to-markdown))) - (message "build: wrote %s" out)) - (error - (message "build: ERROR exporting %s: %s" - orgfile (error-message-string err)))))))) - -(defun build--run () - "Export every file in `build--targets' to Markdown." - (dolist (orgfile (build--targets)) - (build--export-one orgfile)) - (message "build: done")) - -(unless (bound-and-true-p build-inhibit-run) - (build--run)) - -(provide 'build) -;;; build.el ends here diff --git a/card-games-pkg.el b/card-games-pkg.el index 9439e5b..3a3cfa3 100644 --- a/card-games-pkg.el +++ b/card-games-pkg.el @@ -1,5 +1,5 @@ ;;; card-games-pkg.el --- Package metadata -*- no-byte-compile: t; -*- -(define-package "card-games" "1.0.91" +(define-package "card-games" "1.0.90" "Play card games in Emacs (console UNICODE and graphical SVG)." '((emacs "26.1")) :keywords '("games") diff --git a/card-games.el b/card-games.el index ded9abc..5b55d2d 100644 --- a/card-games.el +++ b/card-games.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el @@ -134,53 +134,6 @@ "Registry of playable games. Each entry is (NAME COMMAND DESCRIPTION); `card-game' lists them.") -(defvar card-games--svg-card-vars - '(cg-sol-svg-cards cg-trick-svg-cards cg-rummy-svg-cards cg-eights-svg-cards - cg-bridge-svg-cards cg-crapette-svg-cards cg-pat-svg-cards cg-pres-svg-cards) - "Per-game SVG-cards toggles that `card-games-set-treatment' flips together.") - -(defvar card-games--full-svg-vars - '(cg-gaps-svg-ui cg-bid-svg-ui) - "Full-window SVG toggles (Gaps and 500) used by the `full' treatment.") - -(defvar card-games-treatment 'svg - "Display treatment chosen from the menu: `text', `svg', or `full'.") - -;;;###autoload -(defun card-games-set-treatment (treatment) - "Set how games are drawn: `text' (UNICODE), `svg' (cards), or `full'. -`full' also uses the full-window SVG table where a game has one (Gaps and -500). Takes effect the next time a game is drawn -- press g to redraw an -open game. Gaps and 500 are always graphical on a window system." - (interactive - (list (intern (completing-read "Treatment: " '("text" "svg" "full") nil t)))) - (setq card-games-treatment treatment) - (let ((cards (and (memq treatment '(svg full)) t)) - (full (and (eq treatment 'full) t))) - (dolist (v card-games--svg-card-vars) (when (boundp v) (set v cards))) - (dolist (v card-games--full-svg-vars) (when (boundp v) (set v full)))) - (when (called-interactively-p 'interactive) - (message "Display treatment: %s" treatment))) - -(defun card-game--cycle-treatment (_button) - "Cycle the display treatment and refresh the chooser." - (card-games-set-treatment - (pcase card-games-treatment ('text 'svg) ('svg 'full) (_ 'text))) - (card-game)) - -(defun card-game--cycle-ai (_button) - "Cycle the AI difficulty (`cg-ai-level') and refresh the chooser." - (setq cg-ai-level (pcase cg-ai-level ('easy 'normal) ('normal 'hard) (_ 'easy))) - (card-game)) - -;;;###autoload -(defun card-games-set-ai-level (level) - "Set the computer-opponent difficulty to LEVEL (easy, normal, or hard)." - (interactive - (list (intern (completing-read "AI level: " '("easy" "normal" "hard") nil t)))) - (setq cg-ai-level level) - (message "AI level: %s" level)) - (defun card-game--launch (button) "Start the game whose command is stored on BUTTON." (let ((cmd (button-get button 'card-game-command))) @@ -206,9 +159,6 @@ open game. Gaps and 500 are always graphical on a window system." "Open a chooser listing the available card games. Press RET (or click) on a game to start it." (interactive) - (when (and (boundp 'cg-svg-card-back) (eq cg-svg-card-back 'random) - (fboundp 'cg-svg--roll-back)) - (cg-svg--roll-back)) ; a fresh random back per menu visit (let ((buf (get-buffer-create "*Card Games*"))) (with-current-buffer buf (card-game-mode) @@ -216,22 +166,8 @@ Press RET (or click) on a game to start it." (erase-buffer) (insert (propertize " Card Games for Emacs\n" 'face 'bold)) (insert (propertize - " Choose a game with RET or the mouse. q to quit.\n" + " Choose a game with RET or the mouse. q to quit.\n\n" 'face 'shadow)) - (insert " AI opponents: ") - (insert-text-button - (symbol-name cg-ai-level) - 'face 'link - 'help-echo "Click to change the AI difficulty (easy/normal/hard)" - 'action #'card-game--cycle-ai) - (insert (propertize " (click to cycle easy/normal/hard)\n" 'face 'shadow)) - (insert " Cards: ") - (insert-text-button - (symbol-name card-games-treatment) - 'face 'link - 'help-echo "Click to cycle the display: text / svg / full" - 'action #'card-game--cycle-treatment) - (insert (propertize " (click to cycle text/svg/full)\n\n" 'face 'shadow)) (dolist (g card-games-list) (insert " ") (insert-text-button diff --git a/cg-bid-net.el b/cg-bid-net.el index 3ad484c..9367a97 100644 --- a/cg-bid-net.el +++ b/cg-bid-net.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el diff --git a/cg-bid-ui.el b/cg-bid-ui.el index 19412cf..6803f80 100644 --- a/cg-bid-ui.el +++ b/cg-bid-ui.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el @@ -842,9 +842,14 @@ FS scales the N/S/E/W label fonts." (svg-circle svg cx cy 3 :fill "#cfeccf"))) (defun cg-bid--draw-logo (svg cx cy &optional fs) - "Draw the configured Emacs emblem centred at CX, CY on SVG, scaled by FS. -The emblem is chosen with `cg-svg-emacs-logo'." - (cg-svg-draw-logo svg cx cy fs)) + "Draw a GNU Emacs emblem centred at CX, CY on SVG, scaled by FS." + (let ((fs (or fs 1.0))) + (svg-gradient svg "cg-logo" 'linear '((0 . "#8056c8") (100 . "#3f1f9e"))) + (svg-circle svg cx cy (round (* 26 fs)) :gradient "cg-logo" + :stroke "#2a1370" :stroke-width 2) + (cg-svg--text svg "e" cx (+ cy (round (* 10 fs))) (round (* 30 fs)) "#ffffff" t) + (cg-svg--text svg "GNU Emacs" cx (+ cy (round (* 42 fs))) + (max 10 (round (* 11 fs))) "#c7bbe6"))) (defun cg-bid--grid-cell (bid gx gy cw ch g) "Return (X Y W H) for BID in a grid at GX,GY with cells CW by CH, gutter G. diff --git a/cg-bid.el b/cg-bid.el index 0ea82cf..979a20a 100644 --- a/cg-bid.el +++ b/cg-bid.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el diff --git a/cg-bridge.el b/cg-bridge.el index 31ac37a..2d6feb8 100644 --- a/cg-bridge.el +++ b/cg-bridge.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el @@ -498,108 +498,8 @@ vulnerability, and TRICKS the declarer side's trick count. Keys: (max 0 (- cg-svg-card-width 26)) 0) :region-tag region-tag)) -(defun cg-bridge--draw-backs (svg x y n) - "Draw up to three overlapped backs at X, Y for a hand of N cards." - (let ((k (min (max n 0) 3)) (xx x)) - (dotimes (_ k) (cg-svg-card svg xx y :down t) (setq xx (+ xx 16))))) - -(defun cg-bridge--svg (game) - "Return an SVG board for the Bridge GAME (four seats, dummy exposed)." - (let* ((w cg-svg-card-width) (h cg-svg-card-height) (gap cg-svg-card-gap) (pad 16) - (phase (cg-get game :phase)) (cursor (cg-get game :cursor)) - (turn (cg-get game :turn)) (dummy (cg-get game :dummy)) - (exposed (cg-get game :exposed)) (trick (cg-get game :trick)) - (act (if (and (eq phase 'play) (memq turn (cg-bridge--controls game))) turn 0)) - (ahand (cg-bridge--sort (cg-bridge--hand game act))) - (n (length ahand)) - (overlap (cond ((> n 11) (- w 26)) ((> n 8) 20) (t 0))) - (step (max 14 (- (+ w gap) overlap))) - (fanw (if (> n 0) (+ (* (1- n) step) w) w)) - (width (max (+ fanw (* 2 pad)) 760)) - (cx (/ width 2)) - (y-title 6) (y-info 24) (y-north 62) - (y-tn (+ y-north h 20)) - (cyc (+ y-tn (round (* h 0.5)))) - (y-ts (+ cyc (round (* h 0.15)))) - (y-hand (+ y-ts h 42)) - (height (+ y-hand h 30)) - (svg (svg-create width height)) - (lc (cg-color 'shadow :foreground "gray50")) - (regions '())) - (cl-labels - ((txt (str x y &optional sz bold) - (apply #'svg-text svg str :x x :y y :font-size (or sz 12) :fill lc - :font-family cg-svg-font-family (and bold '(:font-weight "bold")))) - (seat (s x y) - (if (and exposed (eql s dummy) (/= s act)) - (let ((cs (cg-bridge--sort (cg-bridge--hand game s))) (xx x)) - (dolist (c cs) - (let ((sp (cg-bridge--spec c))) - (cg-svg-card svg xx y :rank (car sp) :suit (cdr sp))) - (setq xx (+ xx 15)))) - (cg-bridge--draw-backs svg x (+ y 6) (length (cg-bridge--hand game s)))) - (txt (format "%s%s%s" (aref cg-bridge-seat-names s) - (if (eql s dummy) " (dummy)" "") - (if (= turn s) " <-" "")) - x y 11)) - (trick-card (s x y) - (let ((play (assq s trick))) - (when play - (let ((sp (cg-bridge--spec (cdr play)))) - (cg-svg-card svg x y :rank (car sp) :suit (cdr sp))))))) - (txt "Bridge" pad (+ y-title 12) 13 t) - (txt (format "Games N-S %d E-W %d Below %d/%d Above %d/%d" - (aref (cg-get game :games) 0) (aref (cg-get game :games) 1) - (aref (cg-get game :below) 0) (aref (cg-get game :below) 1) - (aref (cg-get game :above) 0) (aref (cg-get game :above) 1)) - pad (+ y-info 8) 11) - (pcase phase - ('auction - (txt (format "Auction: %s" (cg-bridge--auction-string game)) pad (+ y-info 24) 11) - (txt (format "Your bid: %d %s (arrows compose, RET bids)" - (cg-get game :bid-level) - (aref cg-bridge-strains (cg-get game :bid-strain))) - pad (+ y-info 40) 11)) - ((or 'play 'scored 'passed-out) - (txt (format "Contract: %s by %s Declarer tricks: %d" - (cg-bridge--contract-string game) - (if (cg-get game :declarer) - (aref cg-bridge-seat-names (cg-get game :declarer)) "--") - (cg-get game :tricks)) - pad (+ y-info 24) 11))) - (seat 2 (- cx 40) y-north) - (seat 1 pad cyc) - (seat 3 (- width pad 110) cyc) - (when (eq phase 'play) - (trick-card 2 (- cx (/ w 2)) y-tn) - (trick-card 0 (- cx (/ w 2)) y-ts) - (trick-card 1 (- cx w (round (* w 0.4))) (round (- cyc (* h 0.25)))) - (trick-card 3 (+ cx (round (* w 0.4))) (round (- cyc (* h 0.25))))) - (txt (format "%s%s" (aref cg-bridge-seat-names act) - (cond ((eq phase 'auction) " (you)") - ((= act 0) " (you)") - (t " (dummy -- you play)"))) - pad (- y-hand 6) 11) - (let ((x (max pad (- (/ width 2) (/ fanw 2)))) (i 0) - (legalp (and (eq phase 'play) (= turn act)))) - (dolist (c ahand) - (let ((sp (cg-bridge--spec c)) (curp (= i cursor)) - (hintp (and legalp (cg-bridge--legal-play-p game act c)))) - (cg-svg-card svg x y-hand :rank (car sp) :suit (cdr sp) - :highlight curp :hint hintp) - (push (cons (list x y-hand (if (= i (1- n)) w step) h) (cons 'hand i)) regions)) - (setq x (+ x step) i (1+ i)))) - (txt (or (cg-get game :message) "") pad (- height 8) 12)) - (propertize "*" 'display (cg-svg-image svg (cg-scale)) 'cg-regions (nreverse regions)))) - (cl-defmethod cg-render ((game cg-bridge-game)) - "Return a depiction of the Bridge GAME: SVG board if graphical, else text." - (if (and cg-bridge-svg-cards (display-graphic-p)) - (cg-bridge--svg game) - (cg-bridge--render-text game))) - -(defun cg-bridge--render-text (game) - "Return a plain-text depiction of the Bridge GAME." + "Return a propertized depiction of the Bridge GAME." (let* ((out '()) (phase (cg-get game :phase)) (cursor (cg-get game :cursor))) (push " Bridge\n" out) (push (format " Rubber: You/North games %d East/West games %d%s\n" diff --git a/cg-core.el b/cg-core.el index 7dafdfe..a831fe2 100644 --- a/cg-core.el +++ b/cg-core.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el @@ -73,17 +73,6 @@ and SPC as an action key. Takes effect the next time a game starts." (const :tag "Classic (adds hjkl, SPC)" classic)) :group 'card-games) -(defcustom cg-ai-level 'normal - "Difficulty of the computer opponents, where a game supports it. -`easy' plays a quick, simple game, `normal' plays soundly, and `hard' -thinks a little harder. Honoured by Russian Bank (Crapette) and the -trick-taking games so far; other games ignore it for now. Change it from -the `card-game' menu or with `card-games-set-ai-level'." - :type '(choice (const :tag "Easy" easy) - (const :tag "Normal" normal) - (const :tag "Hard" hard)) - :group 'card-games) - (defclass cg-game () ((name :initarg :name :initform "game" :type string :documentation "Human-readable game name.") @@ -207,10 +196,8 @@ The glyphs are taken from `cg-symbols'." "?")) (defsubst cg-red-suit-p (suit) - "Return t when SUIT index denotes a red suit, else nil. -Normalised to a boolean so callers may compare two results with `eq' -\(diamonds and hearts are both red but `memq' returns different tails)." - (and (memq suit '(2 3)) t)) + "Return non-nil when SUIT index denotes a red suit." + (memq suit '(2 3))) (defsubst cg-sister-suit (suit) "Return the other suit index of the same colour as SUIT." diff --git a/cg-crapette.el b/cg-crapette.el index d9f302b..1faaeac 100644 --- a/cg-crapette.el +++ b/cg-crapette.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el @@ -56,11 +56,9 @@ ;; somewhere you keep going, otherwise it goes to your waste and your turn ;; ends. ;; -;; The AI observes foundation priority, empties its reserve first (the -;; bottleneck), prefers loading its cards onto you, and looks one move -;; ahead to rearrange the houses when that frees a stuck reserve or waste -;; card. It never breaks foundation priority, so in practice only you can -;; be "stopped". +;; SIMPLIFICATION (documented): the AI is a straightforward greedy player +;; and always observes foundation priority, so in practice only you can be +;; "stopped". ;;; Code: @@ -435,56 +433,11 @@ Return non-nil when the offending action must be abandoned by its caller." for fi = (and card (cg-crap--found-for game card)) when fi return (cons spot (cons 'found fi)))) -(defun cg-crap--ai-unload-move (game) - "Return the best AI (SOURCE . DEST) move that empties its reserve or waste. -Emptying the RESERVE is the goal of the game, so it outscores the waste; -LOADING a card onto you (which also burdens you) outscores building a -house. Every such move reduces the AI's own cards, so its turn ends." - (let ((best nil) (bestscore 0)) - (dolist (spot (list (cons 'res 1) (cons 'was 1))) - (let ((card (cg-crap--spot-top game spot)) - (base (if (eq (car spot) 'res) 40 0))) ; the reserve is the bottleneck - (when card - (dolist (dst (list (cons 'res 0) (cons 'was 0))) - (when (cg-crap--load-accepts (cg-crap--spot-top game dst) card) - (let ((sc (+ base 60))) ; loading: rid a card AND burden you - (when (> sc bestscore) - (setq bestscore sc best (cons spot dst)))))) - (cl-loop for i below 8 - when (cg-crap--house-accepts game i card) - do (let ((sc (+ base 50))) ; else build it onto a house - (when (> sc bestscore) - (setq bestscore sc best (cons spot (cons 'house i))))) - (cl-return))))) - best)) - -(defun cg-crap--ai-enabling-move (game) - "Return a single-card house->house move that unlocks an unload, or nil. -This is the crafty bit: when the AI cannot place its reserve or waste top -anywhere, it looks one move ahead for a house rearrangement that would -make such a placement legal. It only fires when no direct unload exists, -and only when the shuffle genuinely opens one, so the turn still ends." - (when (null (cg-crap--ai-unload-move game)) - (catch 'found - (dotimes (i 8) - (dotimes (j 8) - (let ((pilei (cg-crap--house game i)) (pilej (cg-crap--house game j))) - (when (and (/= i j) pilei) - (let ((card (cg-crap--top pilei))) - (when (cg-crap--house-accepts game j card) - (aset (cg-get game :houses) i (butlast pilei 1)) - (aset (cg-get game :houses) j (append pilej (list card))) - (let ((opens (cg-crap--ai-unload-move game))) - (aset (cg-get game :houses) i pilei) - (aset (cg-get game :houses) j pilej) - (when opens - (throw 'found (cons (cons 'house i) (cons 'house j))))))))))) - nil))) - -(defun cg-crap--ai-greedy-move (game) - "A simple first-fit unload move -- the `easy' AI. -Empties the reserve or waste top onto the first legal spot, without the -scoring or the house-rearranging lookahead of the tougher levels." +(defun cg-crap--ai-useful-move (game) + "Return an AI (SOURCE . DEST) move that empties its reserve or waste, or nil. +The AI only moves its own reserve/waste tops -- onto your piles (loading) +or onto a house -- so every such move reduces the AI's cards and its turn +is guaranteed to end." (catch 'm (dolist (spot (list (cons 'res 1) (cons 'was 1))) (let ((card (cg-crap--spot-top game spot))) @@ -498,17 +451,14 @@ scoring or the house-rearranging lookahead of the tougher levels." nil)) (defun cg-crap--ai-play (game) - "Play the AI opponent's whole turn on GAME, per `cg-ai-level'." - (let ((guard 0) (level cg-ai-level)) + "Play the AI opponent's whole turn on GAME." + (let ((guard 0)) (catch 'done (while t - (when (> (setq guard (1+ guard)) 800) (throw 'done nil)) + (when (> (setq guard (1+ guard)) 400) (throw 'done nil)) (when (cg-crap--won-p game 1) (throw 'done nil)) (let ((mv (or (cg-crap--ai-found-move game) - (if (eq level 'easy) - (cg-crap--ai-greedy-move game) - (cg-crap--ai-unload-move game)) - (and (eq level 'hard) (cg-crap--ai-enabling-move game))))) + (cg-crap--ai-useful-move game)))) (cond (mv (cg-crap--move game (car mv) (cdr mv) 1)) ((cg-crap--hand game 1) diff --git a/cg-cribbage.el b/cg-cribbage.el index 25e24dd..ecacb3c 100644 --- a/cg-cribbage.el +++ b/cg-cribbage.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el @@ -297,83 +297,8 @@ TOTAL is the running count after the play." (defvar-local cg-crib--game nil "The Cribbage game in the current buffer.") -(defun cg-crib--svg (game) - "Return an SVG board for the Cribbage GAME (with a peg-track)." - (let* ((w cg-svg-card-width) (h cg-svg-card-height) (gap cg-svg-card-gap) (pad 16) - (phase (cg-get game :phase)) (scores (cg-get game :scores)) - (hand (if (eq phase 'play) (cg-crib--play game 0) (cg-crib--hand game 0))) - (n (length hand)) (cursor (cg-get game :cursor)) (marks (cg-get game :marks)) - (overlap (cond ((> n 11) (- w 24)) ((> n 8) 20) (t 0))) - (step (max 14 (- (+ w gap) overlap))) - (fanw (if (> n 0) (+ (* (1- n) step) w) w)) - (target cg-cribbage-target) (barw 220) (peg-h 14) (peg-gap 8) - (y-title 6) (y-peg 26) - (y-mid (+ y-peg (* 2 (+ peg-h peg-gap)) 18)) - (y-hand (+ y-mid h 44)) - (height (+ y-hand h 30)) - (width (max (+ fanw (* 2 pad)) (+ pad 90 barw 120) 620)) - (svg (svg-create width height)) - (lc (cg-color 'shadow :foreground "gray50")) - (regions '())) - (cl-labels ((txt (str x y &optional sz bold) - (apply #'svg-text svg str :x x :y y :font-size (or sz 12) :fill lc - :font-family cg-svg-font-family (and bold '(:font-weight "bold")))) - (peg (label sc y) - (txt label pad (+ y 11) 12) - (let ((bx (+ pad 90))) - (svg-rectangle svg bx y barw peg-h :rx 4 :fill "none" - :stroke lc :stroke-width 1) - (svg-rectangle svg bx y - (round (* barw (/ (float (min sc target)) target))) - peg-h :rx 4 :fill "#3aa15a") - (txt (format "%d" sc) (+ bx barw 8) (+ y 11) 12))) - (crow (cards x y) - (let ((xx x)) - (dolist (c cards) - (let ((sp (cg-rummy--card-spec c))) - (cg-svg-card svg xx y :rank (car sp) :suit (cdr sp))) - (setq xx (+ xx (round (* w 0.5)))))))) - (txt (format "Cribbage (to %d)" target) pad (+ y-title 12) 13 t) - (peg "You" (aref scores 0) y-peg) - (peg "Computer" (aref scores 1) (+ y-peg peg-h peg-gap)) - (txt (format "%s deals" (cg-crib--who (cg-get game :dealer))) - (+ pad 90 barw 60) (+ y-peg 11) 11) - (let ((mx pad)) - (when (cg-get game :starter) - (let ((sp (cg-rummy--card-spec (cg-get game :starter)))) - (cg-svg-card svg mx y-mid :rank (car sp) :suit (cdr sp)) - (txt "Starter" mx (+ y-mid h 13) 11) - (setq mx (+ mx w gap 24)))) - (cond - ((eq phase 'play) - (txt (format "Count: %d" (cg-get game :total)) mx (- y-mid 4) 12) - (crow (reverse (cg-get game :seq)) mx y-mid)) - ((memq phase '(show game-over)) - (when (cg-get game :crib) - (txt (format "Crib (%s)" (cg-crib--who (cg-get game :dealer))) mx (- y-mid 4) 11) - (crow (cg-get game :crib) mx y-mid))))) - (txt (format "Your %s" (if (eq phase 'play) "cards" "hand")) pad (- y-hand 6) 11) - (let ((x (max pad (- (/ width 2) (/ fanw 2)))) (i 0)) - (dolist (c hand) - (let ((sp (cg-rummy--card-spec c)) (curp (= i cursor)) - (markp (and marks (memq i marks)))) - (cg-svg-card svg x y-hand :rank (car sp) :suit (cdr sp) :highlight curp) - (when markp - (svg-rectangle svg (- x 3) (- y-hand 3) (+ w 6) (+ h 6) - :rx 8 :fill "none" :stroke "#4a90d9" :stroke-width 3)) - (push (cons (list x y-hand (if (= i (1- n)) w step) h) (cons 'hand i)) regions)) - (setq x (+ x step) i (1+ i)))) - (txt (or (cg-get game :message) "") pad (- height 8) 12)) - (propertize "*" 'display (cg-svg-image svg (cg-scale)) 'cg-regions (nreverse regions)))) - (cl-defmethod cg-render ((game cg-cribbage-game)) - "Return a depiction of the Cribbage GAME: SVG board if graphical, else text." - (if (and cg-rummy-svg-cards (display-graphic-p)) - (cg-crib--svg game) - (cg-crib--render-text game))) - -(defun cg-crib--render-text (game) - "Return a plain-text depiction of the Cribbage GAME." + "Return a propertized depiction of the Cribbage GAME." (let* ((out '()) (scores (cg-get game :scores)) (phase (cg-get game :phase)) (cursor (cg-get game :cursor))) (push (format " Cribbage to %d\n\n" cg-cribbage-target) out) diff --git a/cg-eights.el b/cg-eights.el index 238c752..b78a9d8 100644 --- a/cg-eights.el +++ b/cg-eights.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el @@ -213,65 +213,8 @@ Return the drawn card, or nil when none is available." "Return the cg-svg display spec (RANK-STRING . SUIT) for CARD." (cons (aref cg-eights-ranks (cdr card)) (car card))) -(defun cg-eights--board-svg (game) - "Return an SVG board for the Crazy Eights GAME." - (let* ((w cg-svg-card-width) (h cg-svg-card-height) (gap cg-svg-card-gap) (pad 16) - (hand (cg-eights--hand game 0)) (n (length hand)) - (cursor (cg-get game :cursor)) - (top (cg-eights--top game)) (suit (cg-get game :suit)) - (overlap (cond ((> n 11) (- w 24)) ((> n 8) 20) (t 0))) - (step (max 14 (- (+ w gap) overlap))) - (fanw (if (> n 0) (+ (* (1- n) step) w) w)) - (np (cg-get game :nplayers)) (nstock (length (cg-get game :stock))) - (y-title 6) (y-info 26) - (y-mid (+ y-info (* (1- np) 16) 14)) - (y-hand (+ y-mid h 42)) - (height (+ y-hand h 30)) - (width (max (+ fanw (* 2 pad)) 560)) - (svg (svg-create width height)) - (lc (cg-color 'shadow :foreground "gray50")) - (regions '())) - (cl-labels ((txt (str x y &optional sz bold) - (apply #'svg-text svg str :x x :y y :font-size (or sz 12) :fill lc - :font-family cg-svg-font-family (and bold '(:font-weight "bold"))))) - (txt "Crazy Eights" pad (+ y-title 12) 13 t) - (let ((yy (+ y-info 4))) - (dotimes (s np) - (unless (= s 0) - (txt (format "Player %d: %d cards (score %d)" s - (length (cg-eights--hand game s)) (aref (cg-get game :scores) s)) - pad yy 12) - (setq yy (+ yy 16))))) - (cg-svg-card svg pad y-mid :down (> nstock 0) :gap (= nstock 0)) - (txt (format "Stock %d" nstock) pad (+ y-mid h 13) 11) - (let ((dx (+ pad w gap 28)) (sp (cg-eights--spec top))) - (cg-svg-card svg dx y-mid :rank (car sp) :suit (cdr sp)) - (txt "Discard" dx (+ y-mid h 13) 11) - (let ((sx (+ dx w gap 34)) - (col (if (cg-red-suit-p suit) "#c0392b" "#2c3e50"))) - (txt "Suit in play" sx (- y-mid 4) 11) - (svg-text svg (cg-suit-glyph suit) :x (+ sx 12) :y (+ y-mid 46) - :font-size 44 :fill col :font-family cg-svg-font-family))) - (txt "Your hand" pad (- y-hand 6) 11) - (let ((x (max pad (- (/ width 2) (/ fanw 2)))) (i 0)) - (dolist (c hand) - (let ((sp (cg-eights--spec c)) (curp (= i cursor)) - (hintp (cg-eights--legal-p game c))) - (cg-svg-card svg x y-hand :rank (car sp) :suit (cdr sp) - :highlight curp :hint hintp) - (push (cons (list x y-hand (if (= i (1- n)) w step) h) (cons 'hand i)) regions)) - (setq x (+ x step) i (1+ i)))) - (txt (or (cg-get game :message) "") pad (- height 8) 12)) - (propertize "*" 'display (cg-svg-image svg (cg-scale)) 'cg-regions (nreverse regions)))) - (cl-defmethod cg-render ((game cg-eights-game)) - "Return a depiction of GAME: an SVG board if graphical, else text." - (if (and cg-eights-svg-cards (display-graphic-p)) - (cg-eights--board-svg game) - (cg-eights--render-text game))) - -(defun cg-eights--render-text (game) - "Return a plain-text depiction of GAME." + "Return a propertized string depicting GAME for a text display." (let* ((out (list)) (top (cg-eights--top game)) (hand (cg-eights--hand game 0)) (cursor (cg-get game :cursor))) (push (format " Crazy Eights\n\n") out) diff --git a/cg-gaps.el b/cg-gaps.el index 8a53833..f60feea 100644 --- a/cg-gaps.el +++ b/cg-gaps.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el diff --git a/cg-handfoot.el b/cg-handfoot.el index 352e9e8..07acc06 100644 --- a/cg-handfoot.el +++ b/cg-handfoot.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el diff --git a/cg-match.el b/cg-match.el index 951d25f..3e67917 100644 --- a/cg-match.el +++ b/cg-match.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el @@ -452,69 +452,8 @@ instead of hunting for one overlapped card in a big hand." (defvar-local cg-om--game nil "The Old Maid game in the current buffer.") -(defun cg-om--svg (game) - "Return an SVG board for the Old Maid GAME." - (let* ((w cg-svg-card-width) (h cg-svg-card-height) (gap cg-svg-card-gap) (pad 16) - (hand (cg-om--hand game 0)) (n (length hand)) - (target (cg-om--target game 0)) (pick (or (cg-get game :pick) 0)) - (yourp (and target (eq (cg-get game :phase) 'play) (= (cg-get game :turn) 0))) - (np (cg-get game :nplayers)) - (overlap (cond ((> n 11) (- w 24)) ((> n 8) 20) (t 0))) - (step (max 14 (- (+ w gap) overlap))) - (fanw (if (> n 0) (+ (* (1- n) step) w) w)) - (bstep 20) - (tn (and target (length (cg-om--hand game target)))) - (y-title 6) (y-info 26) - (y-target (+ y-info (* (1- np) 16) 18)) - (y-hand (+ y-target h 42)) - (targetw (if (and yourp tn (> tn 0)) (+ (* (1- tn) bstep) w) 0)) - (height (+ y-hand h 30)) - (width (max (+ fanw (* 2 pad)) (+ targetw (* 2 pad)) 560)) - (svg (svg-create width height)) - (lc (cg-color 'shadow :foreground "gray50")) - (regions '())) - (cl-labels ((txt (str x y &optional sz bold) - (apply #'svg-text svg str :x x :y y :font-size (or sz 12) :fill lc - :font-family cg-svg-font-family (and bold '(:font-weight "bold"))))) - (txt "Old Maid" pad (+ y-title 12) 13 t) - (let ((yy (+ y-info 4))) - (dotimes (s np) - (unless (= s 0) - (txt (format "Player %d: %d cards%s" s (length (cg-om--hand game s)) - (if (eql s target) " <- draw from here" "")) - pad yy 12) - (setq yy (+ yy 16))))) - (when (and yourp tn (> tn 0)) - (txt (format "Pick a card from Player %d:" target) pad (- y-target 6) 11) - (let ((x pad)) - (dotimes (i tn) - (cg-svg-card svg x y-target :down t :highlight (= i pick)) - (push (cons (list x y-target (if (= i (1- tn)) w bstep) h) (cons 'pick i)) - regions) - (setq x (+ x bstep))))) - (txt "Your hand" pad (- y-hand 6) 11) - (let ((x (max pad (- (/ width 2) (/ fanw 2))))) - (dolist (c hand) - (let ((sp (cg-rummy--card-spec c))) - (cg-svg-card svg x y-hand :rank (car sp) :suit (cdr sp))) - (setq x (+ x step)))) - (txt (or (cg-get game :message) "") pad (- height 8) 12)) - (propertize "*" 'display (cg-svg-image svg (cg-scale)) 'cg-regions (nreverse regions)))) - -(cl-defmethod cg-render-apply ((g cg-old-maid-game) action) - "Apply a click ACTION: pick that card from the target and draw it." - (pcase action - (`(pick . ,i) (cg-put g :pick i) (cg-om-draw)) - (_ (cl-call-next-method)))) - (cl-defmethod cg-render ((game cg-old-maid-game)) - "Return a depiction of the Old Maid GAME: SVG board if graphical, else text." - (if (and cg-rummy-svg-cards (display-graphic-p)) - (cg-om--svg game) - (cg-om--render-text game))) - -(defun cg-om--render-text (game) - "Return a plain-text depiction of the Old Maid GAME." + "Return a propertized depiction of the Old Maid GAME." (let* ((out '()) (target (cg-om--target game 0))) (push " Old Maid\n\n" out) (dotimes (s (cg-get game :nplayers)) @@ -533,7 +472,6 @@ instead of hunting for one overlapped card in a big hand." (defun cg-om--redisplay () (let ((game cg-om--game) (inhibit-read-only t)) - (setq cg-current-game game cg-redisplay-function #'cg-om--redisplay) (setq-local mode-line-process (format " [%s]" (cg-get game :phase))) (erase-buffer) (insert (cg-render game)) (goto-char (point-min)))) @@ -572,11 +510,6 @@ instead of hunting for one overlapped card in a big hand." (defvar cg-old-maid-mode-map (let ((map (make-sparse-keymap))) - (define-key map [mouse-1] #'cg-card-click) - (define-key map "+" #'cg-card-zoom-in) - (define-key map "=" #'cg-card-zoom-in) - (define-key map "-" #'cg-card-zoom-out) - (define-key map "0" #'cg-card-zoom-reset) (define-key map (kbd "") #'cg-om-left) (define-key map (kbd "") #'cg-om-right) (define-key map (kbd "RET") #'cg-om-draw) diff --git a/cg-net.el b/cg-net.el index 8019c5e..cff0ced 100644 --- a/cg-net.el +++ b/cg-net.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el diff --git a/cg-patience.el b/cg-patience.el index 6deebc0..e3bb1e9 100644 --- a/cg-patience.el +++ b/cg-patience.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el diff --git a/cg-president.el b/cg-president.el index 11ada49..80bc694 100644 --- a/cg-president.el +++ b/cg-president.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el diff --git a/cg-render.el b/cg-render.el index d031d93..265a619 100644 --- a/cg-render.el +++ b/cg-render.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el diff --git a/cg-rum500.el b/cg-rum500.el index 9c92630..d5b9ada 100644 --- a/cg-rum500.el +++ b/cg-rum500.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el diff --git a/cg-rummy.el b/cg-rummy.el index 98c42bf..c41450f 100644 --- a/cg-rummy.el +++ b/cg-rummy.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el diff --git a/cg-scopa.el b/cg-scopa.el index 6947920..17d8a7c 100644 --- a/cg-scopa.el +++ b/cg-scopa.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el diff --git a/cg-solitaire.el b/cg-solitaire.el index c186c2e..2ead28f 100644 --- a/cg-solitaire.el +++ b/cg-solitaire.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el diff --git a/cg-spite.el b/cg-spite.el index 83a30bf..2ff7ee2 100644 --- a/cg-spite.el +++ b/cg-spite.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el @@ -273,74 +273,8 @@ (push (format "%d:%s" (1+ d) (if top (cg-rummy-card-string top) "--")) parts))) (mapconcat #'identity (nreverse parts) " "))) -(defun cg-spite--board-svg (game) - "Return an SVG board for the Spite & Malice GAME." - (let* ((w cg-svg-card-width) (h cg-svg-card-height) (gap cg-svg-card-gap) (pad 16) - (hand (cg-spite--hand game 0)) (n (length hand)) - (cursor (cg-get game :cursor)) (center (cg-get game :center)) - (overlap (cond ((> n 11) (- w 24)) ((> n 8) 20) (t 0))) - (step (max 14 (- (+ w gap) overlap))) - (fanw (if (> n 0) (+ (* (1- n) step) w) w)) - (colstep (+ w 14)) - (y-title 6) (y-opp 26) - (y-center (+ y-opp 16)) - (y-sm (+ y-center h 16)) - (y-ylabel (+ y-sm 12)) - (y-yours (+ y-ylabel 6)) - (y-hand (+ y-yours h 42)) - (height (+ y-hand h 30)) - (width (max (+ fanw (* 2 pad)) (+ (* 5 colstep) (* 2 pad)) 620)) - (svg (svg-create width height)) - (lc (cg-color 'shadow :foreground "gray50")) - (regions '())) - (cl-labels ((txt (str x y &optional sz bold) - (apply #'svg-text svg str :x x :y y :font-size (or sz 12) :fill lc - :font-family cg-svg-font-family (and bold '(:font-weight "bold")))) - (pilecard (spec x y) - (if spec (cg-svg-card svg x y :rank (car spec) :suit (cdr spec)) - (cg-svg-card svg x y :gap t)))) - (txt (format "Spite & Malice (goal %d)" cg-spite-goal-size) pad (+ y-title 12) 13 t) - (txt (format "Computer: goal %d left hand %d discards %s" - (length (cg-spite--goal game 1)) (length (cg-spite--hand game 1)) - (cg-spite--disc-string game 1)) - pad (+ y-opp 4) 12) - (txt "Centre (build A..Q; King is wild)" pad (- y-center 4) 11) - (dotimes (i 4) - (let* ((x (+ pad (* i colstep))) (pp (aref center i)) - (spec (and pp (cons (aref cg-rummy-ranks (car pp)) (car (cadr pp)))))) - (pilecard spec x y-center))) - (txt (format "Stock %d Muck %d" - (length (cg-get game :stock)) (length (cg-get game :muck))) - pad y-sm 11) - (let* ((gtop (car (cg-spite--goal game 0))) - (gspec (and gtop (cg-rummy--card-spec gtop)))) - (txt (format "Your goal (%d left)" (length (cg-spite--goal game 0))) - pad y-ylabel 11) - (txt "Discards" (+ pad colstep) y-ylabel 11) - (pilecard gspec pad y-yours) - (dotimes (d 4) - (let* ((x (+ pad colstep (* d colstep))) - (dtop (car (aref (cg-spite--disc game 0) d))) - (dspec (and dtop (cg-rummy--card-spec dtop)))) - (pilecard dspec x y-yours)))) - (txt "Your hand" pad (- y-hand 6) 11) - (let ((x (max pad (- (/ width 2) (/ fanw 2)))) (i 0)) - (dolist (c hand) - (let ((sp (cg-rummy--card-spec c)) (curp (= i cursor))) - (cg-svg-card svg x y-hand :rank (car sp) :suit (cdr sp) :highlight curp) - (push (cons (list x y-hand (if (= i (1- n)) w step) h) (cons 'hand i)) regions)) - (setq x (+ x step) i (1+ i)))) - (txt (or (cg-get game :message) "") pad (- height 8) 12)) - (propertize "*" 'display (cg-svg-image svg (cg-scale)) 'cg-regions (nreverse regions)))) - (cl-defmethod cg-render ((game cg-spite-game)) - "Return a depiction of the GAME: an SVG board if graphical, else text." - (if (and cg-rummy-svg-cards (display-graphic-p)) - (cg-spite--board-svg game) - (cg-spite--render-text game))) - -(defun cg-spite--render-text (game) - "Return a plain-text depiction of the Spite & Malice GAME." + "Return a propertized depiction of the Spite & Malice GAME." (let* ((out '()) (cursor (cg-get game :cursor))) (push " Spite & Malice\n\n" out) (push (format " Computer goal: %d left hand: %d discards: %s\n\n" diff --git a/cg-svg.el b/cg-svg.el index d549af0..5141276 100644 --- a/cg-svg.el +++ b/cg-svg.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el @@ -86,47 +86,9 @@ not themed this way -- see `cg-svg--highlight' -- so it never picks up a theme's `region' colour." :type 'boolean :group 'cg-svg) -(defcustom cg-svg-card-back 'random - "Pattern drawn on a face-down card back. -The `emacs', `emacs-classic', `gnu' and `splash' backs stamp the card with -a logo that ships with Emacs. `random' picks one of the concrete backs -for the session (reshuffle with `cg-svg-shuffle-card-back')." - :type '(choice (const dots) (const rings) (const solid) - (const lattice) (const waves) (const diamond) - (const emacs) (const emacs-classic) (const gnu) (const splash) - (const random)) - :group 'cg-svg) - -(defconst cg-svg--card-backs - '(dots rings solid lattice waves diamond emacs emacs-classic gnu splash) - "Concrete card backs that `random' chooses among.") - -(defvar cg-svg--random-back nil - "The concrete back currently chosen for the `random' setting.") - -(defun cg-svg--roll-back () - "Choose a fresh concrete back for `random' and return it." - (setq cg-svg--random-back - (nth (random (length cg-svg--card-backs)) cg-svg--card-backs))) - -;;;###autoload -(defun cg-svg-shuffle-card-back () - "Pick a new random card back (used when `cg-svg-card-back' is `random')." - (interactive) - (cg-svg--roll-back) - (when (called-interactively-p 'interactive) - (message "Card back: %s" cg-svg--random-back))) - -(defun cg-svg--effective-back () - "Return the concrete back to draw, resolving `random'." - (if (eq cg-svg-card-back 'random) - (or cg-svg--random-back (cg-svg--roll-back)) - cg-svg-card-back)) - -(defun cg-svg--back-logo-name (back) - "Map a logo card-back BACK to a `cg-svg--logo-files' key." - (pcase back ('emacs 'modern) ('emacs-classic 'classic) - ('gnu 'gnu) ('splash 'splash))) +(defcustom cg-svg-card-back 'dots + "Pattern drawn on a face-down card back." + :type '(choice (const dots) (const rings) (const solid)) :group 'cg-svg) (defcustom cg-svg-four-color nil "Use a four-colour deck when non-nil. @@ -293,60 +255,6 @@ X, Y and W, H give the card's top-left corner and size." (cg-svg--text svg "★" (+ x (/ w 2.0)) (+ y (* h 0.52)) (* h 0.40) color) (cg-svg--text svg "JOKER" (+ x (/ w 2.0)) (+ y (* h 0.74)) (* h 0.135) color t)) -(defun cg-svg--back-dots (svg x y w h) - "Draw the dotted-medallion back pattern." - (let ((gy (+ y 10))) - (while (< gy (- (+ y h) 8)) - (let ((gx (+ x 10))) - (while (< gx (- (+ x w) 8)) - (svg-circle svg gx gy 1.1 :fill cg-svg-back-trim) - (setq gx (+ gx 9)))) - (setq gy (+ gy 9))))) - -(defun cg-svg--back-lattice (svg x y w h) - "Draw a small-cross lattice back pattern." - (let ((gy (+ y 13))) - (while (< gy (- (+ y h) 10)) - (let ((gx (+ x 13))) - (while (< gx (- (+ x w) 10)) - (svg-line svg (- gx 2) (- gy 2) (+ gx 2) (+ gy 2) - :stroke cg-svg-back-trim :stroke-width 1) - (svg-line svg (- gx 2) (+ gy 2) (+ gx 2) (- gy 2) - :stroke cg-svg-back-trim :stroke-width 1) - (setq gx (+ gx 11)))) - (setq gy (+ gy 11))))) - -(defun cg-svg--back-waves (svg x y w h) - "Draw a staggered-dash (brickwork) back pattern." - (let ((gy (+ y 12)) (row 0)) - (while (< gy (- (+ y h) 9)) - (let ((gx (+ x (if (cl-evenp row) 9 15)))) - (while (< gx (- (+ x w) 9)) - (svg-line svg gx gy (+ gx 6) gy :stroke cg-svg-back-trim :stroke-width 1.4) - (setq gx (+ gx 12)))) - (setq gy (+ gy 8) row (1+ row))))) - -(defun cg-svg--back-diamond (svg x y w h) - "Draw concentric diamonds as the back pattern." - (let ((cx (+ x (/ w 2.0))) (cy (+ y (/ h 2.0)))) - (dolist (f '(0.40 0.28 0.16)) - (let ((dw (* w f)) (dh (* h f))) - (svg-polygon svg (list (cons cx (- cy dh)) (cons (+ cx dw) cy) - (cons cx (+ cy dh)) (cons (- cx dw) cy)) - :fill "none" :stroke cg-svg-back-trim :stroke-width 1))))) - -(defun cg-svg--back-logo (svg x y w h back) - "Stamp the Emacs logo for BACK centred on the card, or dots if unavailable." - (let ((file (and (fboundp 'svg-embed) - (cg-svg--logo-file (cg-svg--back-logo-name back))))) - (if (null file) - (cg-svg--back-dots svg x y w h) - (let ((size (round (* h 0.52)))) - (svg-embed svg file "image/png" nil - :x (round (+ x (/ (- w size) 2.0))) - :y (round (+ y (/ (- h size) 2.0))) - :width size :height size))))) - (defun cg-svg--draw-back (svg x y w h r) "Draw a face-down card back on SVG at X, Y (W by H, corner R). The pattern is controlled by `cg-svg-card-back'." @@ -354,20 +262,21 @@ The pattern is controlled by `cg-svg-card-back'." :stroke cg-svg-border-color :stroke-width 1) (svg-rectangle svg (+ x 4) (+ y 4) (- w 8) (- h 8) :rx 4 :fill "none" :stroke cg-svg-back-trim :stroke-width 1) - (let ((back (cg-svg--effective-back))) - (pcase back - ('solid nil) - ('rings - (svg-rectangle svg (+ x 8) (+ y 8) (- w 16) (- h 16) :rx 5 :fill "none" - :stroke cg-svg-back-trim :stroke-width 1) - (svg-rectangle svg (+ x 12) (+ y 12) (- w 24) (- h 24) :rx 4 :fill "none" - :stroke cg-svg-back-trim :stroke-width 1)) - ('lattice (cg-svg--back-lattice svg x y w h)) - ('waves (cg-svg--back-waves svg x y w h)) - ('diamond (cg-svg--back-diamond svg x y w h)) - ((or 'emacs 'emacs-classic 'gnu 'splash) - (cg-svg--back-logo svg x y w h back)) - (_ (cg-svg--back-dots svg x y w h))))) + (pcase cg-svg-card-back + ('solid nil) + ('rings + (svg-rectangle svg (+ x 8) (+ y 8) (- w 16) (- h 16) :rx 5 :fill "none" + :stroke cg-svg-back-trim :stroke-width 1) + (svg-rectangle svg (+ x 12) (+ y 12) (- w 24) (- h 24) :rx 4 :fill "none" + :stroke cg-svg-back-trim :stroke-width 1)) + (_ + (let ((gy (+ y 10))) + (while (< gy (- (+ y h) 8)) + (let ((gx (+ x 10))) + (while (< gx (- (+ x w) 8)) + (svg-circle svg gx gy 1.1 :fill cg-svg-back-trim) + (setq gx (+ gx 9)))) + (setq gy (+ gy 9))))))) (defun cg-svg--draw-face (svg x y w h r rank suit) "Draw a face-up card (RANK of SUIT) on SVG at X, Y (W by H, corner R)." @@ -562,59 +471,5 @@ card-size slider beneath the row." (cg-svg-slider-draw svg pad (+ pad h 8) cg-card-scale))) (propertize "*" 'display (cg-svg-image svg (cg-scale)) 'cg-regions regions)))) -(defcustom cg-svg-emacs-logo 'modern - "Which Emacs emblem to show in the full-window (svg-fill) games. -The image choices embed a logo that ships with Emacs, falling back to the -drawn emblem when the file is unavailable. `drawn' is a small built-in -emblem and `none' shows nothing." - :type '(choice (const :tag "Modern Emacs icon" modern) - (const :tag "Classic Emacs icon" classic) - (const :tag "GNU head (Gnus)" gnu) - (const :tag "GNU Emacs splash" splash) - (const :tag "Drawn emblem" drawn) - (const :tag "None" none)) - :group 'cg-svg) - -(defconst cg-svg--logo-files - '((modern . ("images/icons/hicolor/48x48/apps/emacs.png" - "images/icons/hicolor/128x128/apps/emacs.png")) - (classic . ("images/icons/hicolor/48x48/apps/emacs23.png" - "images/icons/hicolor/128x128/apps/emacs23.png")) - (gnu . ("images/gnus/gnus.png")) - (splash . ("images/splash.png"))) - "Map a logo name to candidate image files relative to `data-directory'.") - -(defun cg-svg--logo-file (name) - "Return the first readable image file for logo NAME, or nil." - (cl-loop for rel in (cdr (assq name cg-svg--logo-files)) - for f = (expand-file-name rel data-directory) - when (file-readable-p f) return f)) - -(defun cg-svg--draw-logo-emblem (svg cx cy fs) - "Draw the built-in purple GNU Emacs emblem centred at CX, CY, scaled FS." - (svg-gradient svg "cg-logo" 'linear '((0 . "#8056c8") (100 . "#3f1f9e"))) - (svg-circle svg cx cy (round (* 26 fs)) :gradient "cg-logo" - :stroke "#2a1370" :stroke-width 2) - (cg-svg--text svg "e" cx (+ cy (round (* 10 fs))) (round (* 30 fs)) "#ffffff" t) - (cg-svg--text svg "GNU Emacs" cx (+ cy (round (* 42 fs))) - (max 10 (round (* 11 fs))) "#c7bbe6")) - -(defun cg-svg-draw-logo (svg cx cy &optional fs) - "Draw the configured Emacs emblem (`cg-svg-emacs-logo') centred at CX, CY. -FS scales it. Embeds a real Emacs logo image when one is available, and -otherwise draws the built-in emblem." - (let ((fs (or fs 1.0)) (choice cg-svg-emacs-logo)) - (pcase choice - ('none nil) - ('drawn (cg-svg--draw-logo-emblem svg cx cy fs)) - (_ (let ((file (and (fboundp 'svg-embed) (cg-svg--logo-file choice)))) - (if (null file) - (cg-svg--draw-logo-emblem svg cx cy fs) - (let ((size (round (* 56 fs)))) - (svg-embed svg file "image/png" nil - :x (round (- cx (/ size 2))) - :y (round (- cy (/ size 2))) - :width size :height size)))))))) - (provide 'cg-svg) ;;; cg-svg.el ends here diff --git a/cg-trick-ext.el b/cg-trick-ext.el index 57409da..a5b9050 100644 --- a/cg-trick-ext.el +++ b/cg-trick-ext.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el diff --git a/cg-trick.el b/cg-trick.el index 0d5cd83..dae410b 100644 --- a/cg-trick.el +++ b/cg-trick.el @@ -4,7 +4,7 @@ ;; Author: Corwin Brust ;; Maintainer: Corwin Brust -;; Version: 1.0.91 +;; Version: 1.0.90 ;; Package-Requires: ((emacs "26.1")) ;; Keywords: games ;; URL: https://code.bru.st/corwin/card-game.el @@ -394,11 +394,7 @@ "Play a whole hand with AI for every seat (used by tests)." (while (not (cg-trick--hand-over-p game)) (let ((seat (cg-get game :turn))) - (cg-trick--play game seat - (if (eq cg-ai-level 'easy) - (let ((moves (cg-trick--legal-moves game seat))) - (nth (random (length moves)) moves)) - (cg-trick--ai-play game seat))))) + (cg-trick--play game seat (cg-trick--ai-play game seat)))) (cg-trick--score-hand game)) ;;;; New-game / hand lifecycle diff --git a/doc/card-games.texi b/doc/card-games.texi deleted file mode 100644 index 17af0ed..0000000 --- a/doc/card-games.texi +++ /dev/null @@ -1,1182 +0,0 @@ -\input texinfo @c -*- texinfo -*- -@c %**start of header -@setfilename card-games.info -@settitle Card Games for Emacs -@documentencoding UTF-8 -@documentlanguage en -@syncodeindex vr cp -@syncodeindex fn cp -@c %**end of header - -@include version.texi - -@copying -This manual is for Card Games for Emacs (version @value{VERSION}), an -Emacs package that plays card games as UNICODE text in a terminal and as -SVG cards on a graphical display. - -Copyright @copyright{} @value{YEAR} Corwin Brust. - -@quotation -This manual is licensed under the Creative Commons Attribution 4.0 -International License. To view a copy of this license, visit -@url{https://creativecommons.org/licenses/by/4.0/}. - -The Card Games for Emacs @emph{program} is free software, licensed under -the GNU General Public License, version 3 or later. -@end quotation -@end copying - -@dircategory Emacs -@direntry -* Card Games: (card-games). Play card games in Emacs (text and SVG). -@end direntry - -@titlepage -@title Card Games for Emacs -@subtitle A collection of solitaire, two-player, and partnership games -@subtitle for GNU Emacs, version @value{VERSION} -@author Corwin Brust -@page -@vskip 0pt plus 1filll -@insertcopying -@end titlepage - -@contents - -@ifnottex -@node Top -@top Card Games for Emacs - -@insertcopying -@end ifnottex - -@menu -* Introduction:: What this package is. -* Installation:: Getting it, and starting to play. -* The Game Menu:: The chooser, and its controls. -* Playing:: Controls shared by every game. -* Customization:: Colours, cards, keys, and opponents. -* Solitaire Games:: One-player patiences. -* Trick-Taking Games:: Hearts, Spades, Bridge, and friends. -* Rummy Games:: Melds, knocks, and books. -* Matching Games:: Go Fish and Old Maid. -* Capturing Games:: Scopa and Casino. -* Climbing Games:: President and Spite & Malice. -* Cribbage:: The pegging game, to 121. -* Russian Bank:: The two-player duel. -* Networked Play:: Live 500 over the network. -* Credits:: Sources and thanks. -* License:: Terms for this manual. -* Index:: Commands, variables, and concepts. -@end menu - -@node Introduction -@chapter Introduction - -@cindex introduction -@cindex about -Card Games for Emacs is a collection of more than thirty card games --- -solitaires, two-player games, and four-handed partnership games --- -played entirely inside GNU Emacs. - -@ifnotinfo -@center @image{images/klondike, , , Klondike solitaire in Emacs, png} -@end ifnotinfo - -Every game draws itself two ways. In a terminal it uses plain UNICODE -text, so it works anywhere Emacs runs, including over @command{ssh} and -on a phone. On a graphical display it draws proper SVG cards, with a -felt table for the four-handed games. You can switch between these -treatments at any time (@pxref{Customization}). - -You play with the keyboard, the mouse, or both. A highlighted cursor -marks the card or pile you are about to act on; the arrow keys move it -and @key{RET} acts. Clicking a card does the same thing. - -@cindex families of games -The games are grouped into families that share an engine and a feel: - -@table @asis -@item Solitaire -Klondike, FreeCell, Spider, Yukon, Canfield, Forty Thieves, Scorpion, -Golf, TriPeaks, Pyramid, Gaps (Montana), and Hell's Half-Acre, plus a -one-player Russian Bank. @xref{Solitaire Games}. - -@item Trick-taking -500, Hearts, Spades, Whist, Oh Hell, Euchre, Pitch, Briscola, and -Contract Bridge. @xref{Trick-Taking Games}. - -@item Rummy -Gin Rummy, Rummy, Rummy 500, and the partnership game Hand & Foot. -@xref{Rummy Games}. - -@item Matching, capturing, and climbing -Go Fish and Old Maid; Scopa and Casino; President, Spite & Malice, and -Cribbage. - -@item Two-player Russian Bank -The competitive duel, Crapette, against the computer. @xref{Russian -Bank}. -@end table - -To start, type @kbd{M-x card-game}. - -@node Installation -@chapter Installation - -@cindex installation -@cindex requirements -Card Games for Emacs needs GNU Emacs 26.1 or newer. SVG display -additionally needs an Emacs built with image support (any modern -graphical Emacs has it); in a terminal the games fall back to text. - -@menu -* From a package archive:: The easy way. -* From source:: Cloning the repository. -* Building:: Compiling, testing, and the manual. -@end menu - -@node From a package archive -@section From a package archive - -@cindex ELPA -When the package is available from a package archive, install it the -usual way with @kbd{M-x package-install @key{RET} card-games @key{RET}}. -The autoloads let you run @kbd{M-x card-game} straight away. - -@node From source -@section From source - -@cindex source, installing from -Clone the repository from @url{https://code.bru.st/corwin/card-game.el}, -add it to your @code{load-path}, and load the umbrella file: - -@example -(add-to-list 'load-path "/path/to/card-game.el") -(require 'card-games) -@end example - -@noindent -Then @kbd{M-x card-game}. You can also build an installable tarball with -@kbd{make package} and install it with @kbd{M-x package-install-file}. - -@node Building -@section Building - -@cindex Makefile -@cindex tests -The @file{Makefile} provides the usual developer targets: - -@table @code -@item make compile -Byte-compile all the sources. -@item make test -Run the ERT test suite. -@item make info -Build this manual (also @code{make html} and @code{make pdf}). -@item make package -Build the installable @file{.tar}. -@end table - -@node The Game Menu -@chapter The Game Menu - -@findex card-game -@findex card-games -@cindex chooser -@cindex menu -@kbd{M-x card-game} (also available as @code{card-games}) opens the -chooser: a buffer listing every game with a one-line description. Move -between games with @key{TAB} and @kbd{S-@key{TAB}}, or @kbd{n} and -@kbd{p}, and press @key{RET} --- or click a game's name --- to start it. -@kbd{q} buries the menu. - -Two controls sit at the top of the list. - -@table @asis -@item AI opponents -@vindex cg-ai-level -Sets how hard the computer plays: @code{easy}, @code{normal}, or -@code{hard}. Click it to cycle, or use @kbd{M-x card-games-set-ai-level}. -@xref{Opponents}. - -@item Cards -@vindex card-games-treatment -Chooses how the games are drawn: @code{text}, @code{svg}, or @code{full}. -Click it to cycle, or use @kbd{M-x card-games-set-treatment}. -@xref{Display}. -@end table - -@node Playing -@chapter Playing - -@cindex controls, shared -@cindex keys -Each game has its own rules and a few keys of its own, but the basic -controls are the same everywhere. Press @kbd{?} in any game to see that -game's keys in the echo area. - -@table @kbd -@item @key{LEFT} @key{RIGHT} @key{UP} @key{DOWN} -Move the highlighted cursor between cards and piles. -@item @key{RET} -Act on the cursor: pick up or drop a card, play it, or select it. Many -games also accept @key{SPC}. -@item mouse-1 -Click a card or pile to do the same as moving the cursor there and -acting. -@item f -Where there are foundations, send the chosen card to one. -@item u -Undo the last move, in the games that support it. -@item n -Deal a new game. -@item g -Redraw the board. -@item ? -Describe the game's controls. -@item q -Leave the game and return to the menu (@pxref{The Game Menu}). -@end table - -@cindex zoom -@vindex cg-card-scale -On a graphical display, @kbd{+} and @kbd{-} (and @kbd{=}) make the cards -larger and smaller, and @kbd{0} resets the size. Emacs's own -@code{text-scale-adjust} works too. Many boards show a one-line legend -of the current game's keys along the bottom. - -@node Customization -@chapter Customization - -@cindex customization -All the options live in the @code{card-games} customization group; type -@kbd{M-x customize-group @key{RET} card-games @key{RET}} to browse them. -The most useful are collected here. - -@menu -* Display:: Text, SVG cards, or a full table. -* Cards and colours:: Backs, decks, glyphs, and themes. -* The Emacs emblem:: Which logo the full table shows. -* Keys:: Emacs or classic movement. -* Opponents:: How hard the computer plays. -@end menu - -@node Display -@section Display - -@vindex card-games-treatment -@findex card-games-set-treatment -@kbd{M-x card-games-set-treatment} switches every game between three -treatments: @code{text} draws UNICODE cards, @code{svg} draws SVG cards, -and @code{full} additionally uses the full-window SVG table for the games -that have one (Gaps and 500). You can also cycle it from the menu -(@pxref{The Game Menu}). A change takes effect the next time a game is -drawn; press @kbd{g} to redraw an open game. - -@vindex cg-card-scale -The card size follows @code{cg-card-scale} and the zoom keys -(@pxref{Playing}). - -@node Cards and colours -@section Cards and colours - -@vindex cg-svg-card-back -@findex cg-svg-shuffle-card-back -@cindex card backs -The pattern on a face-down card is @code{cg-svg-card-back}. Besides -@code{dots}, @code{rings}, and @code{solid} there are the drawn patterns -@code{lattice}, @code{waves}, and @code{diamond}, and four backs stamped -with an Emacs logo: @code{emacs}, @code{emacs-classic}, @code{gnu}, and -@code{splash}. The default, @code{random}, chooses a back for the -session; @kbd{M-x cg-svg-shuffle-card-back} rolls a new one, and -reopening the menu also re-rolls. - -@vindex cg-svg-four-color -@cindex four-colour deck -With @code{cg-svg-four-color} non-@code{nil}, clubs are drawn green and -diamonds blue, so all four suits are told apart by colour. - -@vindex cg-symbols -@cindex suit glyphs -@code{cg-symbols} maps each suit to the glyph used for it, in both the -text and the SVG cards; customize it to use the outlined suits -@samp{♤ ♧ ♢ ♡}, say, instead of the filled ones. - -@findex card-games-set-theme -@vindex card-games-themes -@cindex themes -@kbd{M-x card-games-set-theme} applies a colour preset --- -@code{classic}, @code{dark}, or @code{contrast} --- setting the felt -colour, the card back, and the highlight together. The individual -colours (@code{cg-svg-highlight-color}, @code{cg-bid-felt-color}, and the -rest) can also be set on their own. - -@vindex cg-cursor-type -@cindex cursor -Card buffers hide the text cursor by default, since you act on the -highlighted card rather than on point; @code{cg-cursor-type} can bring it -back. - -@node The Emacs emblem -@section The Emacs emblem - -@vindex cg-svg-emacs-logo -@cindex logo -The full-window tables (500 and Gaps) show an Emacs emblem in a corner. -@code{cg-svg-emacs-logo} chooses it: @code{modern} (the current Emacs -icon, the default), @code{classic} (the older icon), @code{gnu} (a GNU -head), @code{splash} (the startup image), @code{drawn} (a small built-in -emblem), or @code{none}. The image choices embed a logo that ships with -your Emacs, falling back to the drawn emblem when it cannot be found. - -@node Keys -@section Keys - -@vindex cg-keys -@cindex key scheme -@code{cg-keys} selects a movement scheme. @code{emacs} (the default) -follows Emacs conventions --- the arrow keys move and @key{RET} acts. -@code{classic} additionally enables @kbd{h} @kbd{j} @kbd{k} @kbd{l} and -@key{SPC}. A change takes effect the next time a game starts. - -@node Opponents -@section Opponents - -@vindex cg-ai-level -@findex card-games-set-ai-level -@cindex difficulty -@code{cg-ai-level} sets how hard the computer plays: @code{easy}, -@code{normal}, or @code{hard}. Russian Bank plays all three levels; the -trick-taking games play a random legal card on @code{easy} and their -usual game otherwise. Other games do not yet consult it. Set it with -@kbd{M-x card-games-set-ai-level} or from the menu. - -@node Solitaire Games -@chapter Solitaire Games - -@cindex solitaire -@cindex patience -The one-player games. Most are @dfn{tableau builders} that share the -same controls; the pile games (Golf, TriPeaks, Pyramid) and the gaps -games (Gaps, Hell's Half-Acre) play a little differently and say so in -their own sections. - -@cindex solitaire, controls -The shared keys for the tableau builders are: - -@table @kbd -@item @key{LEFT} @key{RIGHT} -Move the cursor between piles. -@item @key{RET} @r{(or} @key{SPC}@r{)} -Pick up the card, or the movable run, under the cursor; press again on a -destination to drop it. On the stock, deal cards to the waste (or, when -the stock is empty, recycle the waste where the game allows). -@item f -Send the card under the cursor to a foundation. -@item a -Auto-play the cards that plainly belong on the foundations. -@item u -Undo the last move. -@item n @r{/} g @r{/} ? @r{/} q -New deal, redraw, describe the keys, and return to the menu. -@end table - -@noindent -With @code{cg-keys} set to @code{classic} the vi keys @kbd{h} @kbd{j} -@kbd{k} @kbd{l} move as well (@pxref{Keys}). - -@menu -* Klondike:: The classic. -* FreeCell:: A game of pure skill. -* Spider:: Two decks; clear eight suited runs. -* Yukon:: Klondike, but move any group. -* Canfield:: A reserve and a wrapping base rank. -* Forty Thieves:: Two decks, no redeal, and hard. -* Scorpion:: Free four buried runs. -* Russian Bank Solitaire:: Houses and a feeding reserve. -* Golf:: One rank up or down. -* TriPeaks:: Golf with wrapping chains. -* Pyramid:: Remove pairs that sum to thirteen. -* Gaps:: Order the rows through the gaps. -* Hell's Half-Acre:: Gaps, built the other way. -@end menu - -@node Klondike -@section Klondike - -@findex cg-klondike -@cindex Klondike -The classic. Build the four foundations up in suit, Ace to King. Seven -columns hold a descending, alternating-colour tableau; move a card or an -ordered run onto the next-higher card of the other colour, and fill an -empty column with a King (or a King-headed run). Turn cards from the -stock to the waste --- one at a time by default --- and recycle the -waste when the stock runs out. - -@vindex cg-sol-klondike-draw -Set @code{cg-sol-klondike-draw} to @code{3} for the harder -turn-three variant. - -@cindex strategy, Klondike -@strong{Strategy.} Uncover face-down cards before anything else, and -keep a column open for a King. Do not rush low cards to the foundations -if you may still need them to receive tableau cards. - -@node FreeCell -@section FreeCell - -@findex cg-freecell -@cindex FreeCell -Every card is dealt face up into eight columns, and four @dfn{free cells} -each hold a single card. Build the tableau down in alternating colour -and the foundations up in suit. How large a run you can shift at once -depends on how many cells and empty columns are free. Almost every deal -can be won --- this is a game of skill, not luck. - -@strong{Strategy.} Plan several moves ahead, free the aces early, and -resist filling all four cells; an empty column is worth more than a full -cell. - -@node Spider -@section Spider - -@findex cg-spider -@cindex Spider -Two decks, ten columns, no separate foundations. Build down regardless -of suit, but only a same-suit run moves as a block. Complete a -King-to-Ace run in one suit and it is lifted off the table; clear all -eight to win. Press @key{RET} on the stock to deal one card to every -column --- but only when no column is empty. - -@strong{Strategy.} Build in suit whenever you have the choice, and empty -a column as soon as you can: it is the key to untangling the rest. - -@node Yukon -@section Yukon - -@findex cg-yukon -@cindex Yukon -Klondike's layout, dealt mostly face up, with one liberating difference: -you may move @emph{any} face-up card, together with everything piled on -top of it, onto a card one higher of the other colour --- the group need -not be in order. There is no stock. - -@strong{Strategy.} Expose the face-down cards; the freedom to lift -buried groups wins many deals that Klondike would lose. - -@node Canfield -@section Canfield - -@findex cg-canfield -@cindex Canfield -A thirteen-card @dfn{reserve}, four tableau columns, and a stock dealt -three at a time. The first card sets the base rank for the foundations, -which build up in suit and wrap from King round to Ace. The tableau -builds down in alternating colour, and an empty column refills from the -reserve. - -@strong{Strategy.} Clear the reserve --- it is the bottleneck --- and -keep the wrapping base rank in mind when you choose what to bank. - -@node Forty Thieves -@section Forty Thieves - -@findex cg-forty-thieves -@cindex Forty Thieves -Two decks, ten columns, eight foundations. The tableau builds down -@emph{in suit} and moves one card at a time; the foundations build up in -suit; the stock deals to the waste with no redeal. A hard, skilful game. - -@strong{Strategy.} Be patient, keep the waste short, and avoid burying -the low cards you will need. - -@node Scorpion -@section Scorpion - -@findex cg-scorpion -@cindex Scorpion -Seven columns, built down in suit. As in Yukon you may move any card -with everything on top of it, ordered or not. Free four King-to-Ace runs -to win. A small stock deals onto the first columns when you are stuck. - -@strong{Strategy.} Dig the buried low cards out early, and think about -which King you can afford to complete first. - -@node Russian Bank Solitaire -@section Russian Bank - -@findex cg-russian-bank -@cindex Russian Bank, solitaire -The one-player patience: eight @dfn{houses} built down in alternating -colour, four foundations built up in suit from the Ace, and a -thirteen-card reserve that feeds an empty house. (For the competitive -two-player game, @pxref{Russian Bank}.) - -@strong{Strategy.} Play to the foundations first and empty the reserve; -the houses are just working space. - -@node Golf -@section Golf - -@findex cg-golf -@cindex Golf -A layout of thirty-five cards over a single waste pile. Play any exposed -card onto the waste when it is one rank above @emph{or} below the waste's -top card; the sequence does @emph{not} wrap, so nothing follows a King. -Turn a fresh card from the stock when you are stuck. Clear the whole -layout to win. - -@cindex controls, pile games -Use the arrow keys or the mouse to choose a card, @key{RET} to play it -(or, on the stock, to deal), @kbd{u} to undo, and @kbd{n} for a new deal. - -@strong{Strategy.} Look for long up-and-down chains before you spend a -card from the stock. - -@node TriPeaks -@section TriPeaks - -@findex cg-tripeaks -@cindex TriPeaks -Golf played over three overlapping peaks, with one change: the sequence -@emph{wraps}, so an Ace follows a King and a King follows an Ace. That -lets you run long chains across the peaks. Clear all three to win. The -keys are the same as Golf. - -@strong{Strategy.} Plan the chain that uncovers the most cards, and hold -the stock in reserve for when the board truly stalls. - -@node Pyramid -@section Pyramid - -@findex cg-pyramid -@cindex Pyramid -A twenty-eight-card pyramid. Remove pairs of exposed cards whose ranks -sum to thirteen --- Ace counts 1, Jack 11, Queen 12, and a King is 13, so -a King leaves on its own. Mark one card and then its partner to remove -them, and deal from the stock for more matches. Clear the pyramid to -win. - -@strong{Strategy.} Free the cards that block two others at once, and do -not strand a card whose only partner is already gone. - -@node Gaps -@section Gaps - -@findex cg-montana -@findex cg-gaps -@cindex Gaps -@cindex Montana -Also called Montana. The pack is dealt into four rows with gaps between -the cards. Each row is built as one suit, with a Two at the head, rising -Two, Three, @dots{}, up to the King. Move a card into a gap when it -continues the row --- the card one higher, in the same suit, than the -card to the gap's left. Fillable gaps are ringed and marked with a green -@samp{+}. - -When no move remains, @kbd{r} reshuffles the misplaced cards for another -try; you get a limited number of these redeals. @kbd{v} toggles the -full-window layout. Order every row to win. - -@strong{Strategy.} Open the gaps that let a Two, then a Three, begin -each row, and spend redeals only when you are truly stuck. - -@node Hell's Half-Acre -@section Hell's Half-Acre - -@findex cg-hells-half-acre -@cindex Hell's Half-Acre -The same game as Gaps, built the other way: a King anchors the head of -each row and the rows descend King, Queen, @dots{}, down to the Two. The -controls are identical. - -@node Trick-Taking Games -@chapter Trick-Taking Games - -@cindex trick-taking -@cindex tricks -In these games the four players each play a card to the @dfn{trick}, and -the highest card (or the highest trump) wins it. You sit South; the -other three seats are computer players, and for the partnership games -North is your partner. - -@ifnotinfo -@center @image{images/hearts, , , A hand of Hearts, png} -@end ifnotinfo - -@cindex trick games, controls -Most of them share these controls: - -@table @kbd -@item @key{LEFT} @key{RIGHT} -Choose a card in your hand. -@item @key{RET} @r{(or} @key{SPC}@r{)} -Play the chosen card. During the Hearts pass it instead marks a card. -@item p -Confirm the Hearts pass, once three cards are marked. -@item n @r{/} g @r{/} ? @r{/} q -New match, redraw, describe the keys, and return to the menu. -@end table - -@noindent -When a game calls for a bid, you are prompted for it in the minibuffer. -@xref{Opponents}, to set how hard the computer plays. 500 and Bridge -have richer boards and a few keys of their own, noted below. - -@menu -* Five Hundred: 500. Partnership bidding to 500. -* Hearts:: Avoid the hearts and the black lady. -* Spades:: Bid your tricks; spades are trump. -* Whist:: Fixed trump, no bidding. -* Oh Hell:: Bid exactly, on shrinking hands. -* Euchre:: Bowers, and going alone. -* Pitch:: Set the trump with your lead. -* Briscola:: No follow; capture the points. -* Bridge: Contract Bridge. The auction, the dummy, the rubber. -@end menu - -@node 500 -@section 500 - -@findex cg-bid -@cindex 500 -Australia's national card game, and the flagship of this collection. You -and North play against East and West. After the deal there is an -auction: each side bids a number of tricks (six to ten) and a trump --- -or Misère, to take no tricks at all. The high bidder takes the five-card -@dfn{kitty}, then discards five cards, and play begins. - -The trump order runs Joker (highest), then the Jack of trump (right -bower) and the other Jack of the same colour (left bower), then Ace down -to Four. Score the Avondale table for making or breaking the contract. -A side wins by reaching 500 on a contract it made (the ``front door''), -and loses by falling to @minus{}500 (the ``back door''). - -@table @kbd -@item b -Make a bid; @kbd{p} passes. -@item @key{RET} -Select the card, or the kitty card, under the pointer. -@item x -Discard the five cards you have marked for the kitty. -@item v -Toggle the full-window table. -@item M-@key{UP} M-@key{DOWN} -Scroll the message log (the mouse wheel works too). -@item n @r{/} ? @r{/} q -New game, help, and the menu. -@end table - -@noindent -500 can also be played live against other people over the network. -@xref{Networked Play}. - -@strong{Strategy.} Bid what your trumps and side-suit aces can actually -make; take the kitty only when it can complete a contract; and keep -Misère for a hand too weak to win a single trick. - -@node Hearts -@section Hearts - -@findex cg-hearts -@cindex Hearts -An avoidance game with no trump. Every heart costs one point and the -Queen of Spades costs thirteen, and you want as few as possible. Before -each hand you pass three cards to another player --- left, right, across, -then a hand with no passing, and around again. The Two of Clubs leads -the first trick, on which no points may be played. - -Take @emph{all} the points in a hand and you ``shoot the moon'': the -twenty-six are charged to everyone else instead. When a player reaches -100 the game ends and the lowest score wins. - -@strong{Strategy.} Void a suit so you can throw the Queen or a heart into -it; keep track of who might hold the Queen; and only chase the moon with a -hand that cannot be stopped. - -@node Spades -@section Spades - -@findex cg-spades -@cindex Spades -Spades are always trump. Each player bids the number of tricks they -expect to win, and the two partners' bids are added: your side must make -that combined total. Extra tricks (``bags'') score a point each, but -every tenth bag costs you 100. A bid and made @dfn{Nil} --- no tricks at -all --- scores 100, or loses 100 if broken. Play to 500. - -@strong{Strategy.} Count your sure winners before you bid; go Nil only -with low spades and short, duckable side suits; and protect your -partner's Nil when you can. - -@node Whist -@section Whist - -@findex cg-whist -@cindex Whist -The old English ancestor of Bridge, with no bidding. The dealer's last -card is turned for trump. Follow the suit led if you can; trumps beat the -plain suits. Each side counts one point for every trick it wins beyond -the @dfn{book} of six, and the first side to five wins. - -@strong{Strategy.} Lead from your longest suit, draw the opponents' -trumps, and remember the cards that have gone. - -@node Oh Hell -@section Oh Hell - -@findex cg-ohhell -@cindex Oh Hell -The hand shrinks every deal, from seven cards down to one, and a card is -turned for trump. Each player bids the @emph{exact} number of tricks -they will take and scores a bonus only for hitting it precisely --- one -over or under and the bonus is lost. Play the fixed run of rounds; it is -every player for themselves. - -@strong{Strategy.} On the small hands a high card is a sure trick and a -low one a sure miss; do not be afraid to bid zero and duck everything. - -@node Euchre -@section Euchre - -@findex cg-euchre -@cindex Euchre -A brisk 24-card game (Nine to Ace). The Jack of the trump suit (the -@dfn{right bower}) and the other Jack of the same colour (the @dfn{left -bower}) are the two highest trumps. A card is turned up, and you may -@dfn{order it up} as trump or, later, name a different suit. The side -that names trump must take three of the five tricks. Partnership to 10. - -You are asked whether to order up the turned card (@kbd{y} or @kbd{n}) -and, if bidding comes round again, to name a trump. - -@strong{Strategy.} Order up with three trumps, or two trumps and an -off-suit Ace; a very strong hand can be played ``alone'' for a bigger -score. - -@node Pitch -@section Pitch - -@findex cg-pitch -@cindex Pitch -Auction Pitch. Players bid for the right to @dfn{pitch}, and the -pitcher's first lead sets the trump suit. Thereafter follow the led suit -if you can, but you may always trump instead. Four points are at stake -each hand --- High, Low, Jack, and Game --- and the first to seven wins. - -@strong{Strategy.} Bid only on a solid trump holding, ideally with the -Jack or with both the highest and lowest trumps. - -@node Briscola -@section Briscola - -@findex cg-briscola -@cindex Briscola -A 40-card Italian game. One card is turned to fix the trump, the -@dfn{briscola}, and --- unusually --- there is no need to follow suit: you -may play any card to any trick. Winning a trick captures its cards, and -the Aces and Threes carry the most points. Capture the points; the first -to 61 of the 120 wins. - -@strong{Strategy.} Save your briscole to capture the opponents' Aces, and -lead worthless cards to coax points out of them. - -@node Contract Bridge -@section Bridge - -@findex cg-bridge -@cindex Bridge -@cindex Contract Bridge -The full game: the auction, then the play with the dummy exposed, and -rubber scoring. You sit South and partner North against East and West. - -In the auction you compose a call and make it: @key{UP} and @key{DOWN} -change the level, @key{LEFT} and @key{RIGHT} change the strain, @key{RET} -bids it, @kbd{p} passes, and @kbd{d} doubles. When the auction ends, the -declaring side's first player is declarer and their partner is the dummy, -whose hand is turned face up; the declarer plays both hands, so you play -the dummy from your seat when it is the dummy's turn. Making your -contract scores below the line toward game and the rubber. - -@table @kbd -@item arrows @r{and} @key{RET} -Compose and make a call (auction), or choose and play a card (play). -@item p @r{/} d -Pass and double, during the auction. -@item n @r{/} g @r{/} q -Next deal, redraw, and the menu. -@end table - -@strong{Strategy.} Count your high-card points, look for an eight-card -trump fit with your partner, and plan the whole hand --- drawing trumps, -setting up a long suit --- before you play to the first trick. - -@node Rummy Games -@chapter Rummy Games - -@cindex rummy -@cindex melds -Draw a card --- from the stock or the discard pile --- then throw one -away, and work your hand into @dfn{melds}: @dfn{sets} of three or four of -a kind, and @dfn{runs} of three or more cards in one suit. - -@menu -* Gin Rummy:: Knock with little deadwood. -* Rummy:: Meld your whole hand to go out. -* Rummy 500:: Score the cards you lay down. -* Hand & Foot:: The partnership Canasta cousin. -@end menu - -@node Gin Rummy -@section Gin Rummy - -@findex cg-gin -@cindex Gin Rummy -@cindex deadwood -@cindex knock -Two-handed, with ten-card hands. Draw from the stock (@kbd{s}) or take -the up-card (@kbd{t}), then discard with @key{RET}. When your unmatched -cards --- your @dfn{deadwood} --- add up to ten points or fewer you may -@dfn{knock} (@kbd{k}) and end the hand; with no deadwood at all you go -@dfn{gin} for a bonus. Your opponent then lays their own deadwood off -onto your melds. First to 100 points wins. - -@strong{Strategy.} Keep cards that could join more than one meld, knock -early against a slow hand, and note which cards your opponent takes. - -@node Rummy -@section Rummy - -@findex cg-rummy-basic -@cindex Rummy -Draw and discard, and lay your melds down on the table. Mark the cards -of a meld with @key{SPC} and lay it down with @kbd{m}; add a single card -to a meld already on the table (yours or anyone's) with @kbd{l}. Get rid -of your whole hand to go out. - -@table @kbd -@item @key{SPC} @r{/} m @r{/} l -Mark cards, meld them, lay one off. -@item s @r{/} t @r{/} @key{RET} -Draw from the stock, take the up-card, discard. -@end table - -@strong{Strategy.} Do not meld too soon --- cards on the table cannot be -rearranged, and they tell your opponents what you hold. - -@node Rummy 500 -@section Rummy 500 - -@findex cg-rum500 -@cindex Rummy 500 -Rummy played for points over many hands, first past 500. You score the -cards you lay down and lose the ones left in your hand. Besides the top -of the discard pile you may take a card from deeper in it with @kbd{T}, -picking up that card and everything above it --- as long as you meld the -chosen card at once. Aces count 15. - -@strong{Strategy.} Chase the high-scoring cards, and raid the discard -pile when the reward outweighs the cards it puts in your hand. - -@node Hand & Foot -@section Hand & Foot - -@findex cg-handfoot -@cindex Hand and Foot -A partnership cousin of Canasta. You are dealt a @dfn{hand} and a -@dfn{foot}; play out the hand, then take up the foot. Build @dfn{books} -of seven cards of a rank --- @dfn{clean} with no wild cards, or -@dfn{dirty} with some. Twos and Jokers are wild; threes never meld. -Draw two cards with @kbd{s}, or pick up the whole discard pile with -@kbd{p} by melding its top card; meld (@kbd{m}), lay off (@kbd{l}), and -discard (@key{RET}). Your side goes out once it has two complete books -and you have emptied your foot. - -@strong{Strategy.} Hoard wild cards for clean books, and do not go out -before your partner has played their foot. - -@node Matching Games -@chapter Matching Games - -@cindex matching games -Two light games of collecting and shedding, good for a quick sit-down. - -@menu -* Go Fish:: Ask for ranks; collect books of four. -* Old Maid:: Shed pairs; dodge the odd Queen. -@end menu - -@node Go Fish -@section Go Fish - -@findex cg-go-fish -@cindex Go Fish -@cindex books -Collect @dfn{books} of four of a kind. On your turn pick a rank you hold ---- click one of the rank buttons, or move the cursor to a card and it -selects that rank --- then press @kbd{1}, @kbd{2}, @kbd{3}, or @kbd{4} to -ask that player for it. If they have any they must hand them over and you -ask again; if not, you ``go fish'' and draw from the stock. Complete the -most books to win. - -@strong{Strategy.} Remember what everyone has asked for; it tells you -who holds what. - -@node Old Maid -@section Old Maid - -@findex cg-old-maid -@cindex Old Maid -One Queen is set aside, so a single Queen is left without a partner. -Throw out every pair in your hand, then draw a card, unseen, from the next -player: move over their face-down cards with the arrows (or click one) and -press @key{RET}. Whoever is left holding the odd Queen at the end loses. - -@strong{Strategy.} Mostly luck --- but do not let your reactions reveal -where the odd Queen is hiding. - -@node Capturing Games -@chapter Capturing Games - -@cindex capturing games -@cindex fishing games -Play a card from your hand to the table; if it matches, it @dfn{captures} -table cards and takes them for scoring. Press @key{RET} to play the card -under the cursor. - -@menu -* Scopa:: Sweep the table for a scopa. -* Casino:: Big and little casino, and the sweeps. -@end menu - -@node Scopa -@section Scopa - -@findex cg-scopa -@cindex Scopa -A 40-card Italian game. The card you play captures a single table card of -the same value, or a set of table cards that add up to its value. -Clearing the whole table at a stroke is a @dfn{scopa} and scores a bonus. -At the end of each round you score for taking the most cards, the most -coins, the Seven of Coins (the @dfn{sette bello}), and the @dfn{primiera}; -play to 11. - -@strong{Strategy.} Watch the running total on the table, and try to leave -your opponent unable to make the capture that would sweep it. - -@node Casino -@section Casino - -@findex cg-casino -@cindex Casino -The 52-card cousin of Scopa. Number cards capture by value as in Scopa; -face cards capture only by matching rank. Score the @dfn{big casino} -(the Ten of Diamonds), the @dfn{little casino} (the Two of Spades), the -Aces, and the most cards and most spades; play to 21. - -@strong{Strategy.} Guard the two casinos and grab spades when you can --- -the little points decide close games. - -@node Climbing Games -@chapter Climbing Games - -@cindex climbing games -@cindex shedding games -Be the first to get rid of your cards by playing something that beats -what is already on the table. - -@menu -* President:: First out rules; last out scrubs. -* Spite & Malice:: Race to empty your goal pile. -@end menu - -@node President -@section President - -@findex cg-president -@cindex President -@cindex Scum -Also called Scum. The leader plays one to four cards of a rank; the next -player must answer with the same number of a @emph{higher} rank, or pass ---- and here the Two is the highest card of all. Whoever empties their -hand first is President; the last one out is the Scum, and before the next -deal the Scum must hand the President their best cards. - -To lead more than one card of a rank, give a numeric prefix first --- -@kbd{C-u 2 @key{RET}} leads a pair. @kbd{p} passes. - -@strong{Strategy.} Shed your low cards while the lead is cheap, and save -your Twos and your pairs to grab the lead back when it matters. - -@node Spite & Malice -@section Spite & Malice - -@findex cg-spite -@cindex Spite and Malice -@cindex Cat and Mouse -Race to empty your @dfn{goal} pile. Four shared centre piles are built up -from Ace to Queen, and Kings are wild. On your turn play a card from your -hand with @key{RET}, the top of your goal pile with @kbd{G}, or the top of -one of your four discard piles with @kbd{1}--@kbd{4}; then end your turn by -discarding a hand card with @kbd{d}. The first to clear their goal pile -wins. - -@strong{Strategy.} Spend a wild King to reach the next goal card, and -keep your discard piles in order so you can unload them in turn. - -@node Cribbage -@chapter Cribbage - -@findex cg-cribbage -@cindex Cribbage -@cindex pegging -@cindex the crib -The classic two-player game, pegged to 121. Each deal has three parts. - -First, both players lay two cards face down to the dealer's @dfn{crib}: -mark them with @key{SPC} and confirm with @kbd{m}. A @dfn{starter} card is -then cut. Next comes @dfn{the play}: take turns laying cards (@key{RET}), -scoring as the running total reaches fifteen and thirty-one and for pairs -and runs made along the way. Finally, @dfn{the show} scores each hand and -then the crib for its fifteens, pairs, runs, flushes, and @dfn{his nobs} -(the Jack of the starter's suit). First to 121 wins. - -@strong{Strategy.} Weigh a strong hand against what you give away to the -crib, and always be counting toward the next fifteen and thirty-one. - -@node Russian Bank -@chapter Russian Bank - -@findex cg-crapette -@findex cg-russian-bank-duel -@cindex Russian Bank -@cindex Crapette -Russian Bank, also called Crapette, is the competitive two-player -ancestor of the solitaire (@pxref{Russian Bank Solitaire}). You (South) -play against the computer (North), each with your own 52-card pack. - -In the centre sit eight @dfn{foundations}, built up by suit from the Ace, -and eight @dfn{houses}, built down in alternating colour; both are common -ground either player may build on. Each of you also has a thirteen-card -@dfn{reserve} (its top card face up), a @dfn{waste}, and a face-down -@dfn{hand}. You win by getting rid of every card in your reserve, hand, -and waste. - -On your turn you make as many legal moves as you like: - -@itemize @bullet -@item -move the top of your reserve, your waste, or any house onto a foundation -or a house; -@item -move a whole @dfn{sequence} (a run built down in alternating colours) from -one house to another --- but only when there are enough empty houses to -have shifted it a card at a time; and -@item -@dfn{load} a card from your reserve or waste onto the opponent's reserve -or waste, when it is the same suit and one rank higher or lower. -@end itemize - -@cindex stop rule -@vindex cg-crapette-stops -@strong{Foundation priority and ``Stop''.} A card that can go to a -foundation must be played there before anything else. If you build a -house, load your opponent, turn a card, or end your turn while a -foundation play is waiting, your opponent calls ``Stop!'' and your turn -ends at once --- the piles that owe a foundation play are ringed to warn -you. Set @code{cg-crapette-stops} to @code{nil} for a gentler mode that -simply blocks the slip instead of ending your turn. - -When you can do no more, turn the top of your hand: if it fits somewhere -you keep going, otherwise it goes to your waste and your turn ends. - -@table @kbd -@item arrows -Move the cursor between piles. -@item @key{RET} @r{(or click)} -Pick up the card or run under the cursor; press again on a destination to -drop it. -@item f -Send the chosen card to a foundation. -@item [ @r{and} ] -Choose how many cards of a picked-up run to move onto an empty house. -@item @key{SPC} @r{(or} d@r{)} -Turn the top card of your hand. -@item e -End your turn. -@item u @r{/} n @r{/} q -Undo, new game, and the menu. -@end table - -@noindent -@code{cg-ai-level} sets how hard North plays (@pxref{Opponents}): on -@code{hard} it even looks a move ahead to rearrange the houses. - -@strong{Strategy.} Play to the foundations first, and empty your reserve ---- it is the pile you must clear to win. Load your spare cards onto your -opponent to slow them down, and keep an empty house free as room to shift -a sequence. - -@node Networked Play -@chapter Networked Play - -@findex cg-bid-host -@findex cg-bid-join -@cindex networked play -@cindex multiplayer -500 can be played live against other people over a TCP connection. One -player hosts the game and the others join it. - -@table @kbd -@item M-x cg-bid-host @key{RET} @var{port} @key{RET} -Start a game as the host. You take the South seat and wait for others to -connect. -@item M-x cg-bid-join @key{RET} @var{host} @key{RET} @var{port} @key{RET} @var{name} @key{RET} -Connect to a host at @var{host} and @var{port} under a display -@var{name}. -@end table - -@cindex lobby -The game starts automatically once four people have joined. The host may -also press @kbd{s} to start early, with the computer filling any empty -seats. - -@vindex cg-bid-shuffle-partners -With @code{cg-bid-shuffle-partners} non-@code{nil}, the joining players -are dealt randomly among the seats. Each player's view is rotated so -they sit South, so the ordinary controls (@pxref{500}) work unchanged. -Play is turn-based and the host is authoritative; a client can join from -anywhere Emacs can open a network connection. - -@node Credits -@chapter Credits - -@cindex credits -@cindex acknowledgements -Card Games for Emacs was written by Corwin Brust. - -The rules of the trickier games were checked against public references, -chiefly Wikipedia and John McLeod's card-game site, Pagat -(@url{https://www.pagat.com/}). Thanks are due to the playtesters whose -feedback shaped the controls and the display. - -@cindex Claude -Much of the code, and this manual, were written by Corwin Brust in -collaboration with Claude, an AI assistant made by Anthropic, over a -series of pair-programming sessions. - -@node License -@appendix License - -This manual is licensed under the Creative Commons Attribution 4.0 -International License. To view a copy of this license, visit -@url{https://creativecommons.org/licenses/by/4.0/}. - -@node Index -@unnumbered Index - -@printindex cp - -@bye diff --git a/doc/images/hearts.png b/doc/images/hearts.png deleted file mode 100644 index e9f7c18..0000000 Binary files a/doc/images/hearts.png and /dev/null differ diff --git a/doc/images/klondike.png b/doc/images/klondike.png deleted file mode 100644 index fdfb195..0000000 Binary files a/doc/images/klondike.png and /dev/null differ diff --git a/doc/version.texi b/doc/version.texi deleted file mode 100644 index 0380b8a..0000000 --- a/doc/version.texi +++ /dev/null @@ -1,3 +0,0 @@ -@set VERSION 1.0.91 -@set UPDATED 1 July 2026 -@set YEAR 2026 diff --git a/hooks/pre-commit b/hooks/pre-commit deleted file mode 100644 index bf5662b..0000000 --- a/hooks/pre-commit +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env sh -# hooks/pre-commit -- regenerate README.md from README.org on commit. -# -# When README.org (or build.el) is part of the commit, re-run the batch -# Org -> Markdown export and stage the refreshed README.md so it travels -# in the same commit. GitHub and MELPA render Markdown better than Org. -# -# Lightweight by design: no sentinel file, no post-commit --amend. If -# nothing relevant changed, the hook exits immediately. -# -# Install: make hooks (or: ln -s ../../hooks/pre-commit .git/hooks/pre-commit) -# -# Set EMACS in the environment if the Emacs binary is not named "emacs" -# on the PATH git sees (common on Windows/MSYS2). - -set -e - -REPO_ROOT="$(git rev-parse --show-toplevel)" -cd "$REPO_ROOT" - -# Only act when README.org or the exporter itself is staged. -if ! git diff --cached --name-only | grep -Eq '^(README\.org|build\.el)$'; then - exit 0 -fi - -EMACS_BIN="${EMACS:-emacs}" -if ! command -v "$EMACS_BIN" >/dev/null 2>&1; then - echo "pre-commit: WARNING: '$EMACS_BIN' not found; README.md not regenerated." >&2 - echo "pre-commit: set EMACS to your Emacs binary, or run 'make readme'." >&2 - exit 0 -fi - -echo "pre-commit: regenerating README.md from README.org" -"$EMACS_BIN" -Q --batch -l build.el - -git add README.md -exit 0 diff --git a/test/card-games-tests.el b/test/card-games-tests.el index ccb113b..e0a9165 100644 --- a/test/card-games-tests.el +++ b/test/card-games-tests.el @@ -760,48 +760,6 @@ (let ((cg-crap--recording nil)) (cg-crap--ai-play g)) (should (eq (cg-get g :winner) 1)))) ; plays its last card, wins -(ert-deftest cgt-crap-ai-enabling () - ;; reserve 5D cannot be placed until the AI shifts 6H off 6S (crafty rearrange) - (let ((g (cg-crap--deal (cg-crapette-game)))) - (aset (cg-get g :reserve) 1 (list '(2 . 4))) ; 5 of diamonds - (aset (cg-get g :waste) 1 nil) (aset (cg-get g :hand) 1 nil) - (aset (cg-get g :reserve) 0 nil) (aset (cg-get g :waste) 0 nil) - (aset (cg-get g :found) 0 nil) - (aset (cg-get g :houses) 0 (list '(0 . 5) '(3 . 5))) ; 6S (bottom), 6H (top) - (aset (cg-get g :houses) 1 (list '(1 . 6))) ; 7C - (dotimes (k 6) (aset (cg-get g :houses) (+ 2 k) (list '(2 . 6)))) ; 7D fillers, none empty - (cg-put g :turn 1) - (let ((cg-ai-level 'hard) (cg-crap--recording nil)) (cg-crap--ai-play g)) - (should (null (cg-crap--reserve g 1))))) ; hard unstuck and unloaded - -(ert-deftest cgt-crap-ai-loads-you () - ;; reserve 6D loads onto your reserve top 7D; the AI should prefer that - (let ((g (cg-crap--deal (cg-crapette-game)))) - (aset (cg-get g :reserve) 1 (list '(2 . 5))) ; 6D - (aset (cg-get g :waste) 1 nil) (aset (cg-get g :hand) 1 nil) - (aset (cg-get g :reserve) 0 (list '(2 . 6))) ; your reserve top 7D - (aset (cg-get g :waste) 0 nil) (aset (cg-get g :found) 0 nil) - (dotimes (k 8) (aset (cg-get g :houses) k (list '(1 . 10)))) ; nowhere on a house, none empty - (cg-put g :turn 1) - (let ((cg-crap--recording nil)) (cg-crap--ai-play g)) - (should (null (cg-crap--reserve g 1))) - (should (member '(2 . 5) (cg-crap--reserve g 0))))) ; loaded onto you - -(ert-deftest cgt-svg-logo-smoke () - (dolist (choice '(modern classic gnu splash drawn none nonexistent)) - (let ((cg-svg-emacs-logo choice) (svg (svg-create 200 120))) - (cg-svg-draw-logo svg 100 60 1.0) - (should (imagep (svg-image svg)))))) - -(ert-deftest cgt-svg-card-back-smoke () - (dolist (b '(dots rings solid lattice waves diamond - emacs emacs-classic gnu splash random)) - (let ((cg-svg-card-back b) (svg (svg-create 80 100))) - (cg-svg-card svg 12 10 :down t) - (should (imagep (svg-image svg))))) - (cg-svg--roll-back) - (should (memq cg-svg--random-back cg-svg--card-backs))) - (ert-deftest cgt-crap-house-run () (let ((g (cg-crap--deal (cg-crapette-game)))) (aset (cg-get g :houses) 0 (list '(2 . 8) '(0 . 7) '(3 . 6))) ; 9D 8S 7H @@ -936,65 +894,6 @@ (should (rassoc '(hand . 0) (get-text-property 0 'cg-regions (cg-fish--svg g)))))) -(ert-deftest cgt-eights-svg-smoke () - (let ((g (cg-eights-game))) - (cg-put g :nplayers 3) (cg-put g :scores (make-vector 3 0)) - (cg-eights--deal g) (cg-put g :cursor 0) (cg-put g :message "x") - (should (stringp (cg-eights--render-text g))) - (let ((regs (get-text-property 0 'cg-regions (cg-eights--board-svg g)))) - (should (rassoc '(hand . 0) regs)) - (should (cl-every (lambda (r) (= 4 (length (car r)))) regs))))) - -(ert-deftest cgt-spite-svg-smoke () - (let ((g (cg-spite-game))) - (cg-put g :nplayers 2) (cg-put g :scores (make-vector 2 0)) - (cg-spite--deal g) (cg-put g :cursor 0) (cg-put g :message "x") - (aset (cg-get g :center) 0 (cons 3 (list '(1 . 3)))) - (should (stringp (cg-spite--render-text g))) - (should (rassoc '(hand . 0) - (get-text-property 0 'cg-regions (cg-spite--board-svg g)))))) - -(ert-deftest cgt-om-svg-smoke () - (let ((g (cg-old-maid-game))) - (cg-om--deal g) - (cg-put g :phase 'play) (cg-put g :turn 0) (cg-put g :message "x") - (should (stringp (cg-om--render-text g))) - (should (stringp (cg-om--svg g))))) - -(ert-deftest cgt-crib-svg-smoke () - (let ((g (cg-cribbage-game))) - (cg-put g :scores (make-vector 2 0)) - (cg-crib--deal g) - (cg-put g :cursor 0) (cg-put g :message "x") - (should (stringp (cg-crib--render-text g))) - (let ((regs (get-text-property 0 'cg-regions (cg-crib--svg g)))) - (should (rassoc '(hand . 0) regs)) - (should (cl-every (lambda (r) (= 4 (length (car r)))) regs)))) - (let ((g (cg-cribbage-game))) - (cg-put g :scores (vector 60 90)) - (cg-crib--deal g) - (cg-put g :phase 'play) (cg-put g :starter '(2 . 4)) (cg-put g :total 15) - (cg-put g :play (vector (cg-crib--hand g 0) (cg-crib--hand g 1))) - (cg-put g :seq (list '(0 . 6) '(3 . 8))) (cg-put g :message "x") - (should (stringp (cg-crib--svg g))))) - -(ert-deftest cgt-bridge-svg-smoke () - (let ((g (cg-bridge-game))) - (cg-bridge--deal g) - (cg-put g :cursor 0) (cg-put g :turn 0) (cg-put g :bid-level 1) (cg-put g :bid-strain 0) - (cg-put g :message "x") - (should (stringp (cg-bridge--render-text g))) - (should (rassoc '(hand . 0) - (get-text-property 0 'cg-regions (cg-bridge--svg g))))) - (let ((g (cg-bridge-game))) - (cg-bridge--deal g) - (cg-put g :phase 'play) (cg-put g :turn 0) (cg-put g :cursor 0) - (cg-put g :declarer 0) (cg-put g :dummy 2) (cg-put g :exposed t) (cg-put g :tricks 0) - (cg-put g :contract '(3 . 3)) (cg-put g :doubled nil) - (cg-put g :trick (list (cons 1 (car (cg-bridge--hand g 1))))) - (cg-put g :message "x") - (should (stringp (cg-bridge--svg g))))) - (ert-deftest cgt-pat-golf-deal () (let ((g (cg-pat--deal (cg-golf-game)))) (should (= 35 (length (cg-get g :cards)))) @@ -1553,46 +1452,3 @@ (should (plist-get rg :help-close)) (should (plist-get rg :help-classic)) (should (plist-get rg :help-quit))))) - - -(ert-deftest cgt-red-suit-boolean () - "Two red suits compare equal by colour (the diamond/heart eq bug)." - (should (eq (cg-red-suit-p 2) (cg-red-suit-p 3))) ; both red -> eq - (should (eq (cg-red-suit-p 0) (cg-red-suit-p 1))) ; both black -> eq - (should-not (eq (cg-red-suit-p 2) (cg-red-suit-p 0))) ; red vs black - ;; a diamond may NOT sit on a heart in a house (both red) - (let ((g (cg-crap--deal (cg-crapette-game)))) - (aset (cg-get g :houses) 0 (list '(3 . 5))) ; 6 of hearts - (should-not (cg-crap--house-accepts g 0 '(2 . 4))) ; 5 of diamonds (both red) - (aset (cg-get g :houses) 0 (list '(0 . 5))) ; 6 of spades (black) - (should (cg-crap--house-accepts g 0 '(2 . 4))))) ; 5 of diamonds ok - -(ert-deftest cgt-ai-level-easy-crapette () - "The easy Crapette AI does not rearrange houses, so a stuck card stays." - (let ((g (cg-crap--deal (cg-crapette-game)))) - (aset (cg-get g :reserve) 1 (list '(2 . 4))) - (aset (cg-get g :waste) 1 nil) (aset (cg-get g :hand) 1 nil) - (aset (cg-get g :reserve) 0 nil) (aset (cg-get g :waste) 0 nil) - (aset (cg-get g :found) 0 nil) - (aset (cg-get g :houses) 0 (list '(0 . 5) '(3 . 5))) - (aset (cg-get g :houses) 1 (list '(1 . 6))) - (dotimes (k 6) (aset (cg-get g :houses) (+ 2 k) (list '(2 . 6)))) - (cg-put g :turn 1) - (let ((cg-ai-level 'easy) (cg-crap--recording nil)) (cg-crap--ai-play g)) - (should (cg-crap--reserve g 1)))) - - -(ert-deftest cgt-treatment-set () - (let ((saved (mapcar (lambda (v) (cons v (symbol-value v))) - (append card-games--svg-card-vars card-games--full-svg-vars))) - (savedt card-games-treatment)) - (unwind-protect - (progn - (card-games-set-treatment 'text) - (should-not cg-sol-svg-cards) (should-not cg-crapette-svg-cards) - (card-games-set-treatment 'full) - (should cg-sol-svg-cards) (should cg-bid-svg-ui) (should cg-gaps-svg-ui) - (card-games-set-treatment 'svg) - (should cg-sol-svg-cards) (should-not cg-bid-svg-ui)) - (dolist (pr saved) (set (car pr) (cdr pr))) - (setq card-games-treatment savedt))))