Shenzhen
Solver for Shenzen Solitaire
Install / Use
/learn @pushcx/ShenzhenREADME
shenzhen
Solves games of Shenzhen Solitaire, a minigame available standalone or as part of Shenzhen I/O from Zachtronics. Here's an video intro to the gameplay, but if you know FreeCell it's basically that with three suits and "dragon" cards that block gameplay. And this glossary of Solitaire terms may be useful.

Written for practice with Haskell and HaskellStack.
TODO
- [x] create Stack project
- [x] types for cards
- [x] create standard deck
- [x] Layout type
- [x] custom Show instances for Card, Layout
- [ ] enter game Layout
- [x] shuffle deck to create Layout
- [x] Game type
- [x] Use standard terms
- [x] Move type for card moves, collecting dragons
- [x]
move :: Game -> Move -> Game- [x]
movewithMoveFromColumnToCell - [x]
movewithMoveFromCellToColumn - [x]
movewithBuildFromColumn - [x]
movewithBuildFromCell - [x]
movewithPack - [x]
movewithCollectDragons
- [x]
- [x] replicate automatic build of released
Cards - [x] automatically build
Flower - [x] automatically build after player
Moveapplied - [x] automatically build at game start and between moves
- [x] Detect game win
- [x] Detect game loss
- [x] Generate list of possible Moves for a position
- [x] Filter possible Moves against Game history to avoid loops
- [x] Bug: solver loops infinitely at losing states
- [x] Take moves until game win/loss
- [x] QuickCheck that the number + distribution of cards in the game is constant
- [x] Bug:
mayTakeTotakes to last instance of aDragon, not first - [x] Bug:
Prelude.head: empty list - [x] Bug:
Exception: shouldn't be trying to build Flower - [x] Bug: automatic building isn't always building the
Flower - [x]
Movesmart constructors should error onFlower - [x] Bug:
Exception: Non-Suited card on Foundation - [x] Bug:
Exception: element not in list, I warned you I was unsafe - [x]
showcolsdoes not print empty space or__for empty columns so columns shift left - [x] ~~
[DragonCell]should be a type that exposes only one empty cell at a time, cut down on solution space~~ obviated bycanonicalize - [x] Some kind of memoization to avoid solver re-attempting from known-losing positions
- [x] Run a hundred times, report statistics
- [x] Bug: trying to move Flower to cell, add tests
Cleanups and open questions:
- [x] Solver should filter to only pack to leftmost empty column
- [ ] Never use
head- maybe move toclassy-prelude? - [ ] Organize code into modules, don't export constructors or the many unsafe utility functions for
Moves - [x] there must be a nicer way to express
lastCardsOfRuns - [x] Silence
-Wincomplete-patternsand-Wincomplete-uni-patternson util funcitons - [x] Encode that
Tableauhas exactly oneFoundationperSuit - [ ] Encode that
Tableauhas exactly oneDragonCellperSuit - [x] Encode that
FlowerCellcan only hold aFlower - [x] Look at
BoundorEnumforRank - [ ]
mayTakeToandmkRunTowant some kind of help - [x] The
Moveconstructors must enforce validity to avoid passing around brokenMovedata, but thenmovehas none. Does this makemoveclear or unsafe? What if this was more mature withMovein its own module not exporting the default constructor? - [x] This ties into
mayTakeToandunsafeTakeTo.mkMovemust use the former butmovereally wants unsafe to avoid unwrappingMaybe. I can't even see how to unwrap it, really. - [x] And
novelPossibleMoves, which is almost justmove now (possibleMoves now) \\ previous game where now = current game - [x] Require cells be used left-to-right to cut down state space of possible moves.
- [ ] DragonCell could model explicitly that it's
Card | CollectedDragons | Nothing - [x] Is
lostcorrect? It might be only correct in the context of a depth-first search, where apreviousTableauwould've already been searched for a win. - [ ] Can I enforce that a
Gameonly includesMovethat apply to priorTableau? - [ ] Can I ensure
moveis only givenMoves generated from theTableauthey're being applied to? - [x]
nextRankForFoundationshould callnextCardForFoundation - [ ]
automaticBuildcould be functor application - [ ] Would limiting to 100 moves cut off winning games? (Needs a second data structure; if I put timeouts in
Lossesit won't find shorter routes toTableaus it happened to time out on. - [x] Use some clever extension to derive
Foundations's three stacks in data constructor from the three data constructors forSuit - [ ] On loss, try from
Timeouts(carrying in existingLosses) - [ ] Record stats about the time it takes to solve games
Related Skills
node-connect
349.7kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
109.7kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
349.7kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
349.7kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
