Compare commits
15 commits
2ee802f52d
...
7918daf7ef
| Author | SHA1 | Date | |
|---|---|---|---|
| 7918daf7ef | |||
| 69a4de7538 | |||
| 556485cb6b | |||
| 79db6b19db | |||
| 8f8c44b47b | |||
| f2ca3ba635 | |||
| 5b8f68bdf5 | |||
| 010b45f314 | |||
| b2a8d6ad93 | |||
| e816cc0551 | |||
| 9af486526d | |||
| 4b4a7fc52f | |||
| b06ca3362b | |||
| fdccde0634 | |||
| 32fb9f0c6d |
37 changed files with 2663 additions and 89 deletions
15
.gitignore
vendored
15
.gitignore
vendored
|
|
@ -4,3 +4,18 @@
|
||||||
/card-games-*-src.tar.gz
|
/card-games-*-src.tar.gz
|
||||||
*.sketch
|
*.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
|
||||||
|
|
|
||||||
77
Makefile
77
Makefile
|
|
@ -1,7 +1,7 @@
|
||||||
# Makefile for card-games -- byte-compile, test, and package.
|
# Makefile for card-games -- byte-compile, test, and package.
|
||||||
EMACS ?= emacs
|
EMACS ?= emacs
|
||||||
PKG = card-games
|
PKG = card-games
|
||||||
VERSION = 1.0.90
|
VERSION = 1.0.91
|
||||||
# Source files in dependency order (cg-core first).
|
# 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
|
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)
|
ELC = $(EL:.el=.elc)
|
||||||
|
|
@ -11,9 +11,17 @@ TAR = $(TARDIR).tar
|
||||||
SRCTAR = $(PKG)-$(VERSION)-src.tar.gz
|
SRCTAR = $(PKG)-$(VERSION)-src.tar.gz
|
||||||
DIST = dist
|
DIST = dist
|
||||||
BATCH = $(EMACS) -Q --batch -L .
|
BATCH = $(EMACS) -Q --batch -L .
|
||||||
EXTRA = README.org $(PKGDESC)
|
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)
|
||||||
|
|
||||||
.PHONY: all compile test clean distclean checkdoc lint package tarball elpa release help
|
.PHONY: all compile test clean distclean checkdoc lint package tarball elpa release help \
|
||||||
|
info info-emacs html pdf docclean version readme hooks
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "card-games $(VERSION) -- make targets:"
|
@echo "card-games $(VERSION) -- make targets:"
|
||||||
|
|
@ -21,6 +29,13 @@ help:
|
||||||
@echo " test run the ERT test suite"
|
@echo " test run the ERT test suite"
|
||||||
@echo " checkdoc run checkdoc on all sources"
|
@echo " checkdoc run checkdoc on all sources"
|
||||||
@echo " lint run package-lint (if installed)"
|
@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 " package build the installable package tarball ($(TAR))"
|
||||||
@echo " elpa build a one-package ELPA archive in $(DIST)/"
|
@echo " elpa build a one-package ELPA archive in $(DIST)/"
|
||||||
@echo " release clean + test + package + source tarball"
|
@echo " release clean + test + package + source tarball"
|
||||||
|
|
@ -66,8 +81,8 @@ elpa: tarball
|
||||||
# Source snapshot for a GitHub release. Archive an explicit file list
|
# Source snapshot for a GitHub release. Archive an explicit file list
|
||||||
# (not ".") so the growing output tarball and editor lock files are never
|
# (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".
|
# read mid-write -- which is what caused "tar: .: file changed as we read it".
|
||||||
SRCFILES = $(EL) $(EXTRA) Makefile .gitignore test
|
SRCFILES = $(EL) $(EXTRA) build.el hooks Makefile .gitignore test
|
||||||
release: distclean test tarball
|
release: distclean version readme test tarball
|
||||||
rm -f $(SRCTAR)
|
rm -f $(SRCTAR)
|
||||||
tar --transform 's,^,$(TARDIR)/,' \
|
tar --transform 's,^,$(TARDIR)/,' \
|
||||||
--exclude='*.elc' --exclude='*.tar' --exclude='*.tar.gz' \
|
--exclude='*.elc' --exclude='*.tar' --exclude='*.tar.gz' \
|
||||||
|
|
@ -75,8 +90,58 @@ release: distclean test tarball
|
||||||
-czf $(SRCTAR) $(SRCFILES)
|
-czf $(SRCTAR) $(SRCFILES)
|
||||||
@echo "Built $(SRCTAR) and $(TAR) for release $(VERSION)"
|
@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:
|
clean:
|
||||||
rm -f $(ELC)
|
rm -f $(ELC)
|
||||||
|
|
||||||
distclean: clean
|
distclean: clean docclean
|
||||||
rm -rf $(DIST) $(TARDIR) $(TAR) $(SRCTAR)
|
rm -rf $(DIST) $(TARDIR) $(TAR) $(SRCTAR)
|
||||||
|
|
|
||||||
55
NEWS
55
NEWS
|
|
@ -1,6 +1,18 @@
|
||||||
card-games NEWS -- user-visible changes
|
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)
|
* Version 1.0.90 (pre-test snapshot)
|
||||||
|
|
||||||
This release grows the package from the five-game 1.0.60 candidate to a
|
This release grows the package from the five-game 1.0.60 candidate to a
|
||||||
|
|
@ -39,6 +51,16 @@ the mouse as well as the keyboard.
|
||||||
the middle, and your fanned hand, with the legal cards you may play
|
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
|
ringed. Click a card to play it. Set ~cg-trick-svg-cards~ to nil
|
||||||
for the plain-text board.
|
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):
|
- 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
|
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
|
fanned hand with cursor, marks, and lay-off hints; click a card to
|
||||||
|
|
@ -46,6 +68,15 @@ the mouse as well as the keyboard.
|
||||||
- The same board now covers Hand & Foot (each team's books, and your
|
- 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
|
hand or foot) and the fishing games Scopa and Casino (the loose
|
||||||
table cards and the deck).
|
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-family rules completed
|
||||||
- Rummy 500: take a card from anywhere in the discard pile (key ~T~) --
|
- Rummy 500: take a card from anywhere in the discard pile (key ~T~) --
|
||||||
|
|
@ -62,6 +93,30 @@ the mouse as well as the keyboard.
|
||||||
"Next hand" button and the hand fan.
|
"Next hand" button and the hand fan.
|
||||||
- Live multiplayer 500 over TCP (~M-x cg-bid-host~ / ~M-x cg-bid-join~).
|
- 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
|
** Playtest fixes
|
||||||
- ~q~ now returns to the ~M-x card-game~ menu from any game, instead of
|
- ~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.
|
burying the buffer and leaving the previous buffer on screen.
|
||||||
|
|
|
||||||
306
README.md
Normal file
306
README.md
Normal file
|
|
@ -0,0 +1,306 @@
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
76
README.org
76
README.org
|
|
@ -1,16 +1,28 @@
|
||||||
#+TITLE: card-games -- Play card games in Emacs
|
#+TITLE: card-games -- Play card games in Emacs
|
||||||
#+AUTHOR: Corwin Brust
|
#+AUTHOR: Corwin Brust
|
||||||
|
#+OPTIONS: toc:nil
|
||||||
|
|
||||||
Card games for Emacs.
|
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.
|
||||||
|
|
||||||
Renders SVG by default when ~display-graphic-p~ is t and rsvg is
|
[[file:doc/images/klondike.png]]
|
||||||
available. The default (UNICODE) symbols maybe customized by
|
|
||||||
configuring ~card-game-symbols~.
|
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
|
* Games
|
||||||
|
|
||||||
To open the game menu type ~M-x card-game~, or start a game directly
|
To open the game menu type ~M-x card-game~, or start a game directly
|
||||||
with its command.
|
with its command. From the menu you can also switch the card treatment
|
||||||
|
(text / SVG / full-window) and the AI difficulty.
|
||||||
|
|
||||||
** Trick-taking
|
** Trick-taking
|
||||||
- ~cg-bid~ -- 500 (Bid). Win the auction, name the trump suit, then take
|
- ~cg-bid~ -- 500 (Bid). Win the auction, name the trump suit, then take
|
||||||
|
|
@ -116,9 +128,19 @@ with its command.
|
||||||
vulnerability. You are South; when you declare you play the dummy too.
|
vulnerability. You are South; when you declare you play the dummy too.
|
||||||
The bidding AI is a small natural system, sensible but no expert.
|
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
|
* TODO
|
||||||
- [X] make the suit symbols customizable (~cg-symbols~) and obey them
|
- [X] make the suit symbols customizable (~cg-symbols~) and obey them
|
||||||
- [ ] a Texinfo manual
|
- [X] a Texinfo manual
|
||||||
- [ ] finish ~checkdoc~ docstrings across the per-game files
|
- [ ] finish ~checkdoc~ docstrings across the per-game files
|
||||||
(the shared engine files are clean; ~make compile~ is warning-free)
|
(the shared engine files are clean; ~make compile~ is warning-free)
|
||||||
- [ ] renderer "skins": let games subclass the display components (text,
|
- [ ] renderer "skins": let games subclass the display components (text,
|
||||||
|
|
@ -129,9 +151,19 @@ with its command.
|
||||||
* Install
|
* Install
|
||||||
** From the package tarball
|
** From the package tarball
|
||||||
#+begin_src
|
#+begin_src
|
||||||
make package # builds card-games-1.0.90.tar
|
make package # builds card-games-1.0.91.tar
|
||||||
#+end_src
|
#+end_src
|
||||||
Then in Emacs: ~M-x package-install-file RET card-games-1.0.90.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:
|
||||||
|
#+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.
|
||||||
|
|
||||||
** From a local ELPA archive
|
** From a local ELPA archive
|
||||||
#+begin_src
|
#+begin_src
|
||||||
|
|
@ -147,8 +179,17 @@ make elpa # builds dist/ (archive-contents + tar)
|
||||||
Put the ~cg-*.el~ and ~card-games.el~ files on your ~load-path~ and
|
Put the ~cg-*.el~ and ~card-games.el~ files on your ~load-path~ and
|
||||||
~(require 'card-games)~.
|
~(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
|
* Playing
|
||||||
Both games work with the keyboard everywhere and with the mouse on a
|
Every game works with the keyboard everywhere and with the mouse on a
|
||||||
graphical display.
|
graphical display.
|
||||||
|
|
||||||
- 500: ~b~ bid, ~p~ pass, arrows + ~RET~ to play (or click a card),
|
- 500: ~b~ bid, ~p~ pass, arrows + ~RET~ to play (or click a card),
|
||||||
|
|
@ -169,8 +210,10 @@ On a graphical display, ~v~ toggles the full-window SVG table. A
|
||||||
in 500 the ~? Help / Rules~ button explains play. Every game accepts the
|
in 500 the ~? Help / Rules~ button explains play. Every game accepts the
|
||||||
mouse: click cards, board slots, buttons, and the slider.
|
mouse: click cards, board slots, buttons, and the slider.
|
||||||
|
|
||||||
|
[[file:doc/images/hearts.png]]
|
||||||
|
|
||||||
* Testing
|
* Testing
|
||||||
This is a 1.0.90 pre-test snapshot. To try it:
|
This is a 1.0.91 pre-test snapshot. To try it:
|
||||||
1. ~make compile && make test~ -- should be warning-free and all green.
|
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.
|
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~.
|
~M-x cg-klondike~, ~M-x cg-bid~ (500), ~M-x cg-gin~, ~M-x cg-handfoot~.
|
||||||
|
|
@ -184,12 +227,23 @@ finds confusing or unreachable, rules bugs, and rendering glitches.
|
||||||
|
|
||||||
* Customization
|
* Customization
|
||||||
~M-x customize-group RET cg-svg~ and ~RET card-games~:
|
~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
|
- ~cg-svg-theme-colors~ -- derive the highlight ring and card backs
|
||||||
from your theme (on by default).
|
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-bid-felt-color~ -- the 500 table felt.
|
||||||
- ~cg-svg-card-width~, ~cg-svg-card-height~, ~cg-svg-card-shadow~,
|
- ~cg-svg-card-width~, ~cg-svg-card-height~, ~cg-svg-card-shadow~,
|
||||||
~cg-svg-font-family~ -- card appearance.
|
~cg-svg-font-family~ -- card appearance.
|
||||||
- ~cg-svg-card-back~ -- card-back pattern: dots, rings, or solid.
|
- ~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-symbols~ -- the Unicode suit glyphs (and the joker) drawn on cards.
|
||||||
- ~cg-svg-four-color~ -- draw a four-colour deck (clubs green, diamonds
|
- ~cg-svg-four-color~ -- draw a four-colour deck (clubs green, diamonds
|
||||||
blue-purple).
|
blue-purple).
|
||||||
|
|
|
||||||
77
build.el
Normal file
77
build.el
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
;;; 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
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
;;; card-games-pkg.el --- Package metadata -*- no-byte-compile: t; -*-
|
;;; card-games-pkg.el --- Package metadata -*- no-byte-compile: t; -*-
|
||||||
(define-package "card-games" "1.0.90"
|
(define-package "card-games" "1.0.91"
|
||||||
"Play card games in Emacs (console UNICODE and graphical SVG)."
|
"Play card games in Emacs (console UNICODE and graphical SVG)."
|
||||||
'((emacs "26.1"))
|
'((emacs "26.1"))
|
||||||
:keywords '("games")
|
:keywords '("games")
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
@ -134,6 +134,53 @@
|
||||||
"Registry of playable games.
|
"Registry of playable games.
|
||||||
Each entry is (NAME COMMAND DESCRIPTION); `card-game' lists them.")
|
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)
|
(defun card-game--launch (button)
|
||||||
"Start the game whose command is stored on BUTTON."
|
"Start the game whose command is stored on BUTTON."
|
||||||
(let ((cmd (button-get button 'card-game-command)))
|
(let ((cmd (button-get button 'card-game-command)))
|
||||||
|
|
@ -159,6 +206,9 @@ Each entry is (NAME COMMAND DESCRIPTION); `card-game' lists them.")
|
||||||
"Open a chooser listing the available card games.
|
"Open a chooser listing the available card games.
|
||||||
Press RET (or click) on a game to start it."
|
Press RET (or click) on a game to start it."
|
||||||
(interactive)
|
(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*")))
|
(let ((buf (get-buffer-create "*Card Games*")))
|
||||||
(with-current-buffer buf
|
(with-current-buffer buf
|
||||||
(card-game-mode)
|
(card-game-mode)
|
||||||
|
|
@ -166,8 +216,22 @@ Press RET (or click) on a game to start it."
|
||||||
(erase-buffer)
|
(erase-buffer)
|
||||||
(insert (propertize " Card Games for Emacs\n" 'face 'bold))
|
(insert (propertize " Card Games for Emacs\n" 'face 'bold))
|
||||||
(insert (propertize
|
(insert (propertize
|
||||||
" Choose a game with RET or the mouse. q to quit.\n\n"
|
" Choose a game with RET or the mouse. q to quit.\n"
|
||||||
'face 'shadow))
|
'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)
|
(dolist (g card-games-list)
|
||||||
(insert " ")
|
(insert " ")
|
||||||
(insert-text-button
|
(insert-text-button
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
|
||||||
13
cg-bid-ui.el
13
cg-bid-ui.el
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
@ -842,14 +842,9 @@ FS scales the N/S/E/W label fonts."
|
||||||
(svg-circle svg cx cy 3 :fill "#cfeccf")))
|
(svg-circle svg cx cy 3 :fill "#cfeccf")))
|
||||||
|
|
||||||
(defun cg-bid--draw-logo (svg cx cy &optional fs)
|
(defun cg-bid--draw-logo (svg cx cy &optional fs)
|
||||||
"Draw a GNU Emacs emblem centred at CX, CY on SVG, scaled by FS."
|
"Draw the configured Emacs emblem centred at CX, CY on SVG, scaled by FS.
|
||||||
(let ((fs (or fs 1.0)))
|
The emblem is chosen with `cg-svg-emacs-logo'."
|
||||||
(svg-gradient svg "cg-logo" 'linear '((0 . "#8056c8") (100 . "#3f1f9e")))
|
(cg-svg-draw-logo svg cx cy fs))
|
||||||
(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)
|
(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.
|
"Return (X Y W H) for BID in a grid at GX,GY with cells CW by CH, gutter G.
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
|
||||||
104
cg-bridge.el
104
cg-bridge.el
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
@ -498,8 +498,108 @@ vulnerability, and TRICKS the declarer side's trick count. Keys:
|
||||||
(max 0 (- cg-svg-card-width 26)) 0)
|
(max 0 (- cg-svg-card-width 26)) 0)
|
||||||
:region-tag region-tag))
|
: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))
|
(cl-defmethod cg-render ((game cg-bridge-game))
|
||||||
"Return a propertized depiction of the 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."
|
||||||
(let* ((out '()) (phase (cg-get game :phase)) (cursor (cg-get game :cursor)))
|
(let* ((out '()) (phase (cg-get game :phase)) (cursor (cg-get game :cursor)))
|
||||||
(push " Bridge\n" out)
|
(push " Bridge\n" out)
|
||||||
(push (format " Rubber: You/North games %d East/West games %d%s\n"
|
(push (format " Rubber: You/North games %d East/West games %d%s\n"
|
||||||
|
|
|
||||||
19
cg-core.el
19
cg-core.el
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
@ -73,6 +73,17 @@ and SPC as an action key. Takes effect the next time a game starts."
|
||||||
(const :tag "Classic (adds hjkl, SPC)" classic))
|
(const :tag "Classic (adds hjkl, SPC)" classic))
|
||||||
:group 'card-games)
|
: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 ()
|
(defclass cg-game ()
|
||||||
((name :initarg :name :initform "game" :type string
|
((name :initarg :name :initform "game" :type string
|
||||||
:documentation "Human-readable game name.")
|
:documentation "Human-readable game name.")
|
||||||
|
|
@ -196,8 +207,10 @@ The glyphs are taken from `cg-symbols'."
|
||||||
"?"))
|
"?"))
|
||||||
|
|
||||||
(defsubst cg-red-suit-p (suit)
|
(defsubst cg-red-suit-p (suit)
|
||||||
"Return non-nil when SUIT index denotes a red suit."
|
"Return t when SUIT index denotes a red suit, else nil.
|
||||||
(memq suit '(2 3)))
|
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))
|
||||||
|
|
||||||
(defsubst cg-sister-suit (suit)
|
(defsubst cg-sister-suit (suit)
|
||||||
"Return the other suit index of the same colour as SUIT."
|
"Return the other suit index of the same colour as SUIT."
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
@ -56,9 +56,11 @@
|
||||||
;; somewhere you keep going, otherwise it goes to your waste and your turn
|
;; somewhere you keep going, otherwise it goes to your waste and your turn
|
||||||
;; ends.
|
;; ends.
|
||||||
;;
|
;;
|
||||||
;; SIMPLIFICATION (documented): the AI is a straightforward greedy player
|
;; The AI observes foundation priority, empties its reserve first (the
|
||||||
;; and always observes foundation priority, so in practice only you can be
|
;; bottleneck), prefers loading its cards onto you, and looks one move
|
||||||
;; "stopped".
|
;; 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".
|
||||||
|
|
||||||
;;; Code:
|
;;; Code:
|
||||||
|
|
||||||
|
|
@ -433,11 +435,56 @@ Return non-nil when the offending action must be abandoned by its caller."
|
||||||
for fi = (and card (cg-crap--found-for game card))
|
for fi = (and card (cg-crap--found-for game card))
|
||||||
when fi return (cons spot (cons 'found fi))))
|
when fi return (cons spot (cons 'found fi))))
|
||||||
|
|
||||||
(defun cg-crap--ai-useful-move (game)
|
(defun cg-crap--ai-unload-move (game)
|
||||||
"Return an AI (SOURCE . DEST) move that empties its reserve or waste, or nil.
|
"Return the best AI (SOURCE . DEST) move that empties its reserve or waste.
|
||||||
The AI only moves its own reserve/waste tops -- onto your piles (loading)
|
Emptying the RESERVE is the goal of the game, so it outscores the waste;
|
||||||
or onto a house -- so every such move reduces the AI's cards and its turn
|
LOADING a card onto you (which also burdens you) outscores building a
|
||||||
is guaranteed to end."
|
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."
|
||||||
(catch 'm
|
(catch 'm
|
||||||
(dolist (spot (list (cons 'res 1) (cons 'was 1)))
|
(dolist (spot (list (cons 'res 1) (cons 'was 1)))
|
||||||
(let ((card (cg-crap--spot-top game spot)))
|
(let ((card (cg-crap--spot-top game spot)))
|
||||||
|
|
@ -451,14 +498,17 @@ is guaranteed to end."
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
(defun cg-crap--ai-play (game)
|
(defun cg-crap--ai-play (game)
|
||||||
"Play the AI opponent's whole turn on GAME."
|
"Play the AI opponent's whole turn on GAME, per `cg-ai-level'."
|
||||||
(let ((guard 0))
|
(let ((guard 0) (level cg-ai-level))
|
||||||
(catch 'done
|
(catch 'done
|
||||||
(while t
|
(while t
|
||||||
(when (> (setq guard (1+ guard)) 400) (throw 'done nil))
|
(when (> (setq guard (1+ guard)) 800) (throw 'done nil))
|
||||||
(when (cg-crap--won-p game 1) (throw 'done nil))
|
(when (cg-crap--won-p game 1) (throw 'done nil))
|
||||||
(let ((mv (or (cg-crap--ai-found-move game)
|
(let ((mv (or (cg-crap--ai-found-move game)
|
||||||
(cg-crap--ai-useful-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)))))
|
||||||
(cond
|
(cond
|
||||||
(mv (cg-crap--move game (car mv) (cdr mv) 1))
|
(mv (cg-crap--move game (car mv) (cdr mv) 1))
|
||||||
((cg-crap--hand game 1)
|
((cg-crap--hand game 1)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
@ -297,8 +297,83 @@ TOTAL is the running count after the play."
|
||||||
|
|
||||||
(defvar-local cg-crib--game nil "The Cribbage game in the current buffer.")
|
(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))
|
(cl-defmethod cg-render ((game cg-cribbage-game))
|
||||||
"Return a propertized depiction of the 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."
|
||||||
(let* ((out '()) (scores (cg-get game :scores)) (phase (cg-get game :phase))
|
(let* ((out '()) (scores (cg-get game :scores)) (phase (cg-get game :phase))
|
||||||
(cursor (cg-get game :cursor)))
|
(cursor (cg-get game :cursor)))
|
||||||
(push (format " Cribbage to %d\n\n" cg-cribbage-target) out)
|
(push (format " Cribbage to %d\n\n" cg-cribbage-target) out)
|
||||||
|
|
|
||||||
61
cg-eights.el
61
cg-eights.el
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
@ -213,8 +213,65 @@ Return the drawn card, or nil when none is available."
|
||||||
"Return the cg-svg display spec (RANK-STRING . SUIT) for CARD."
|
"Return the cg-svg display spec (RANK-STRING . SUIT) for CARD."
|
||||||
(cons (aref cg-eights-ranks (cdr card)) (car 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))
|
(cl-defmethod cg-render ((game cg-eights-game))
|
||||||
"Return a propertized string depicting GAME for a text display."
|
"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."
|
||||||
(let* ((out (list)) (top (cg-eights--top game))
|
(let* ((out (list)) (top (cg-eights--top game))
|
||||||
(hand (cg-eights--hand game 0)) (cursor (cg-get game :cursor)))
|
(hand (cg-eights--hand game 0)) (cursor (cg-get game :cursor)))
|
||||||
(push (format " Crazy Eights\n\n") out)
|
(push (format " Crazy Eights\n\n") out)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
|
||||||
71
cg-match.el
71
cg-match.el
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
@ -452,8 +452,69 @@ 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.")
|
(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))
|
(cl-defmethod cg-render ((game cg-old-maid-game))
|
||||||
"Return a propertized depiction of the 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."
|
||||||
(let* ((out '()) (target (cg-om--target game 0)))
|
(let* ((out '()) (target (cg-om--target game 0)))
|
||||||
(push " Old Maid\n\n" out)
|
(push " Old Maid\n\n" out)
|
||||||
(dotimes (s (cg-get game :nplayers))
|
(dotimes (s (cg-get game :nplayers))
|
||||||
|
|
@ -472,6 +533,7 @@ instead of hunting for one overlapped card in a big hand."
|
||||||
|
|
||||||
(defun cg-om--redisplay ()
|
(defun cg-om--redisplay ()
|
||||||
(let ((game cg-om--game) (inhibit-read-only t))
|
(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)))
|
(setq-local mode-line-process (format " [%s]" (cg-get game :phase)))
|
||||||
(erase-buffer) (insert (cg-render game)) (goto-char (point-min))))
|
(erase-buffer) (insert (cg-render game)) (goto-char (point-min))))
|
||||||
|
|
||||||
|
|
@ -510,6 +572,11 @@ instead of hunting for one overlapped card in a big hand."
|
||||||
|
|
||||||
(defvar cg-old-maid-mode-map
|
(defvar cg-old-maid-mode-map
|
||||||
(let ((map (make-sparse-keymap)))
|
(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 "<left>") #'cg-om-left)
|
(define-key map (kbd "<left>") #'cg-om-left)
|
||||||
(define-key map (kbd "<right>") #'cg-om-right)
|
(define-key map (kbd "<right>") #'cg-om-right)
|
||||||
(define-key map (kbd "RET") #'cg-om-draw)
|
(define-key map (kbd "RET") #'cg-om-draw)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
|
||||||
70
cg-spite.el
70
cg-spite.el
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
@ -273,8 +273,74 @@
|
||||||
(push (format "%d:%s" (1+ d) (if top (cg-rummy-card-string top) "--")) parts)))
|
(push (format "%d:%s" (1+ d) (if top (cg-rummy-card-string top) "--")) parts)))
|
||||||
(mapconcat #'identity (nreverse 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))
|
(cl-defmethod cg-render ((game cg-spite-game))
|
||||||
"Return a propertized depiction of the Spite & Malice 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."
|
||||||
(let* ((out '()) (cursor (cg-get game :cursor)))
|
(let* ((out '()) (cursor (cg-get game :cursor)))
|
||||||
(push " Spite & Malice\n\n" out)
|
(push " Spite & Malice\n\n" out)
|
||||||
(push (format " Computer goal: %d left hand: %d discards: %s\n\n"
|
(push (format " Computer goal: %d left hand: %d discards: %s\n\n"
|
||||||
|
|
|
||||||
171
cg-svg.el
171
cg-svg.el
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
@ -86,9 +86,47 @@ not themed this way -- see `cg-svg--highlight' -- so it never picks up a
|
||||||
theme's `region' colour."
|
theme's `region' colour."
|
||||||
:type 'boolean :group 'cg-svg)
|
:type 'boolean :group 'cg-svg)
|
||||||
|
|
||||||
(defcustom cg-svg-card-back 'dots
|
(defcustom cg-svg-card-back 'random
|
||||||
"Pattern drawn on a face-down card back."
|
"Pattern drawn on a face-down card back.
|
||||||
:type '(choice (const dots) (const rings) (const solid)) :group 'cg-svg)
|
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-four-color nil
|
(defcustom cg-svg-four-color nil
|
||||||
"Use a four-colour deck when non-nil.
|
"Use a four-colour deck when non-nil.
|
||||||
|
|
@ -255,6 +293,60 @@ 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 "★" (+ 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))
|
(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)
|
(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).
|
"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'."
|
The pattern is controlled by `cg-svg-card-back'."
|
||||||
|
|
@ -262,21 +354,20 @@ The pattern is controlled by `cg-svg-card-back'."
|
||||||
:stroke cg-svg-border-color :stroke-width 1)
|
:stroke cg-svg-border-color :stroke-width 1)
|
||||||
(svg-rectangle svg (+ x 4) (+ y 4) (- w 8) (- h 8) :rx 4 :fill "none"
|
(svg-rectangle svg (+ x 4) (+ y 4) (- w 8) (- h 8) :rx 4 :fill "none"
|
||||||
:stroke cg-svg-back-trim :stroke-width 1)
|
:stroke cg-svg-back-trim :stroke-width 1)
|
||||||
(pcase cg-svg-card-back
|
(let ((back (cg-svg--effective-back)))
|
||||||
|
(pcase back
|
||||||
('solid nil)
|
('solid nil)
|
||||||
('rings
|
('rings
|
||||||
(svg-rectangle svg (+ x 8) (+ y 8) (- w 16) (- h 16) :rx 5 :fill "none"
|
(svg-rectangle svg (+ x 8) (+ y 8) (- w 16) (- h 16) :rx 5 :fill "none"
|
||||||
:stroke cg-svg-back-trim :stroke-width 1)
|
:stroke cg-svg-back-trim :stroke-width 1)
|
||||||
(svg-rectangle svg (+ x 12) (+ y 12) (- w 24) (- h 24) :rx 4 :fill "none"
|
(svg-rectangle svg (+ x 12) (+ y 12) (- w 24) (- h 24) :rx 4 :fill "none"
|
||||||
:stroke cg-svg-back-trim :stroke-width 1))
|
:stroke cg-svg-back-trim :stroke-width 1))
|
||||||
(_
|
('lattice (cg-svg--back-lattice svg x y w h))
|
||||||
(let ((gy (+ y 10)))
|
('waves (cg-svg--back-waves svg x y w h))
|
||||||
(while (< gy (- (+ y h) 8))
|
('diamond (cg-svg--back-diamond svg x y w h))
|
||||||
(let ((gx (+ x 10)))
|
((or 'emacs 'emacs-classic 'gnu 'splash)
|
||||||
(while (< gx (- (+ x w) 8))
|
(cg-svg--back-logo svg x y w h back))
|
||||||
(svg-circle svg gx gy 1.1 :fill cg-svg-back-trim)
|
(_ (cg-svg--back-dots svg x y w h)))))
|
||||||
(setq gx (+ gx 9))))
|
|
||||||
(setq gy (+ gy 9)))))))
|
|
||||||
|
|
||||||
(defun cg-svg--draw-face (svg x y w h r rank suit)
|
(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)."
|
"Draw a face-up card (RANK of SUIT) on SVG at X, Y (W by H, corner R)."
|
||||||
|
|
@ -471,5 +562,59 @@ card-size slider beneath the row."
|
||||||
(cg-svg-slider-draw svg pad (+ pad h 8) cg-card-scale)))
|
(cg-svg-slider-draw svg pad (+ pad h 8) cg-card-scale)))
|
||||||
(propertize "*" 'display (cg-svg-image svg (cg-scale)) 'cg-regions regions))))
|
(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)
|
(provide 'cg-svg)
|
||||||
;;; cg-svg.el ends here
|
;;; cg-svg.el ends here
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Corwin Brust <corwin@bru.st>
|
;; Author: Corwin Brust <corwin@bru.st>
|
||||||
;; Maintainer: Corwin Brust <corwin@bru.st>
|
;; Maintainer: Corwin Brust <corwin@bru.st>
|
||||||
;; Version: 1.0.90
|
;; Version: 1.0.91
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
;; Package-Requires: ((emacs "26.1"))
|
||||||
;; Keywords: games
|
;; Keywords: games
|
||||||
;; URL: https://code.bru.st/corwin/card-game.el
|
;; URL: https://code.bru.st/corwin/card-game.el
|
||||||
|
|
@ -394,7 +394,11 @@
|
||||||
"Play a whole hand with AI for every seat (used by tests)."
|
"Play a whole hand with AI for every seat (used by tests)."
|
||||||
(while (not (cg-trick--hand-over-p game))
|
(while (not (cg-trick--hand-over-p game))
|
||||||
(let ((seat (cg-get game :turn)))
|
(let ((seat (cg-get game :turn)))
|
||||||
(cg-trick--play game seat (cg-trick--ai-play game seat))))
|
(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--score-hand game))
|
(cg-trick--score-hand game))
|
||||||
|
|
||||||
;;;; New-game / hand lifecycle
|
;;;; New-game / hand lifecycle
|
||||||
|
|
|
||||||
1182
doc/card-games.texi
Normal file
1182
doc/card-games.texi
Normal file
File diff suppressed because it is too large
Load diff
BIN
doc/images/hearts.png
Normal file
BIN
doc/images/hearts.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
BIN
doc/images/klondike.png
Normal file
BIN
doc/images/klondike.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
3
doc/version.texi
Normal file
3
doc/version.texi
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
@set VERSION 1.0.91
|
||||||
|
@set UPDATED 1 July 2026
|
||||||
|
@set YEAR 2026
|
||||||
37
hooks/pre-commit
Normal file
37
hooks/pre-commit
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#!/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
|
||||||
|
|
@ -760,6 +760,48 @@
|
||||||
(let ((cg-crap--recording nil)) (cg-crap--ai-play g))
|
(let ((cg-crap--recording nil)) (cg-crap--ai-play g))
|
||||||
(should (eq (cg-get g :winner) 1)))) ; plays its last card, wins
|
(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 ()
|
(ert-deftest cgt-crap-house-run ()
|
||||||
(let ((g (cg-crap--deal (cg-crapette-game))))
|
(let ((g (cg-crap--deal (cg-crapette-game))))
|
||||||
(aset (cg-get g :houses) 0 (list '(2 . 8) '(0 . 7) '(3 . 6))) ; 9D 8S 7H
|
(aset (cg-get g :houses) 0 (list '(2 . 8) '(0 . 7) '(3 . 6))) ; 9D 8S 7H
|
||||||
|
|
@ -894,6 +936,65 @@
|
||||||
(should (rassoc '(hand . 0)
|
(should (rassoc '(hand . 0)
|
||||||
(get-text-property 0 'cg-regions (cg-fish--svg g))))))
|
(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 ()
|
(ert-deftest cgt-pat-golf-deal ()
|
||||||
(let ((g (cg-pat--deal (cg-golf-game))))
|
(let ((g (cg-pat--deal (cg-golf-game))))
|
||||||
(should (= 35 (length (cg-get g :cards))))
|
(should (= 35 (length (cg-get g :cards))))
|
||||||
|
|
@ -1452,3 +1553,46 @@
|
||||||
(should (plist-get rg :help-close))
|
(should (plist-get rg :help-close))
|
||||||
(should (plist-get rg :help-classic))
|
(should (plist-get rg :help-classic))
|
||||||
(should (plist-get rg :help-quit)))))
|
(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))))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue