From 54178a10e53f2914dc253d9bdc41f83645c7897d Mon Sep 17 00:00:00 2001 From: Joe R Date: Sun, 20 Aug 2023 12:24:46 -0400 Subject: [PATCH] Added Clear the Dungeon game. --- html-src/rules/clearthedungeon.html | 44 ++++++++ pysollib/gamedb.py | 3 +- pysollib/games/__init__.py | 1 + pysollib/games/clearthedungeon.py | 164 ++++++++++++++++++++++++++++ 4 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 html-src/rules/clearthedungeon.html create mode 100644 pysollib/games/clearthedungeon.py diff --git a/html-src/rules/clearthedungeon.html b/html-src/rules/clearthedungeon.html new file mode 100644 index 0000000000..248905bfef --- /dev/null +++ b/html-src/rules/clearthedungeon.html @@ -0,0 +1,44 @@ +

Clear the Dungeon

+

+One-Deck game type. 1 joker deck. No redeal. + +

Object

+

+Clear the dungeon by removing all the "monster cards" (face cards) +from the tableau. + +

Rules

+

+The face cards are dealt in four rows of three cards each, with only the top +card of each pile face-up. These represent the dungeon monsters. The remaining +cards are set aside in the talon, called the "power deck". +

+Cards are dealt from the talon three at a time. These cards can be moved to +the monsters on the tableau, or moved to a single reserve pile. The top card +of this reserve pile can also be moved to the tableau. Once all three cards +have been moved to either the tableau or the reserve, three additional cards +are dealt. +

+To slay a monster and remove it from the tableau, three additional cards must +be placed on it. +

+

+Jokers are wild. They are treated as having a value of 10, and can be of any +suit. +

+Once all three cards have been played correctly, the monster is slain, and all +four cards are immediately removed (to the foundation). Cards in the tableau +cannot be moved or removed otherwise. +

+The game is won if you're able to discard all the monster cards in the tableau. + +

Notes

+

+Clear the Dungeon was invented by Mark S. Ball. More details can be found +on his website. diff --git a/pysollib/gamedb.py b/pysollib/gamedb.py index 9073bb9f99..ab6da86787 100644 --- a/pysollib/gamedb.py +++ b/pysollib/gamedb.py @@ -451,6 +451,7 @@ def _callback(gi, gt=game_type): GAMES_BY_INVENTORS = ( ("Paul Alfille", (8,)), ("C.L. Baker", (45,)), + ("Mark S. Ball", (909,)), ("David Bernazzani", (314, 830,)), ("Gordon Bower", (763, 783, 852,)), ("Art Cabral", (9,)), @@ -565,7 +566,7 @@ def _callback(gi, gt=game_type): ('fc-2.20', tuple(range(855, 897))), ('fc-2.21', tuple(range(897, 900)) + tuple(range(11014, 11017)) + tuple(range(13160, 13163)) + (16682,)), - ('dev', tuple(range(906, 909)) + tuple(range(11017, 11020))), + ('dev', tuple(range(906, 910)) + tuple(range(11017, 11020))), ) # deprecated - the correct way is to or a GI.GT_XXX flag diff --git a/pysollib/games/__init__.py b/pysollib/games/__init__.py index 59d0149040..ae65163a34 100644 --- a/pysollib/games/__init__.py +++ b/pysollib/games/__init__.py @@ -36,6 +36,7 @@ from . import camelot # noqa: F401 from . import canfield # noqa: F401 from . import capricieuse # noqa: F401 +from . import clearthedungeon # noqa: F401 from . import crossword # noqa: F401 from . import curdsandwhey # noqa: F401 from . import daddylonglegs # noqa: F401 diff --git a/pysollib/games/clearthedungeon.py b/pysollib/games/clearthedungeon.py new file mode 100644 index 0000000000..08b944a03e --- /dev/null +++ b/pysollib/games/clearthedungeon.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python +# -*- mode: python; coding: utf-8; -*- +# ---------------------------------------------------------------------------## +# +# Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer +# Copyright (C) 2003 Mt. Hood Playing Card Co. +# Copyright (C) 2005-2009 Skomoroh +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# ---------------------------------------------------------------------------## + +from pysollib.game import Game +from pysollib.gamedb import GI, GameInfo, registerGame +from pysollib.layout import Layout +from pysollib.stack import \ + AbstractFoundationStack, \ + BasicRowStack, \ + OpenStack, \ + ReserveStack, \ + TalonStack +from pysollib.util import ANY_SUIT, JACK, KING, NO_RANK, QUEEN + +# ************************************************************************ +# * Clear the Dungeon +# ************************************************************************ + + +class ClearTheDungeon_RowStack(BasicRowStack): + def acceptsCards(self, from_stack, cards): + cardnum = 0 + goal_rank = 0 + goal_suit = 0 + total = 0 + for card in self.cards: + if card.face_up: + if cardnum == 0: + goal_rank = card.rank + 1 + goal_suit = card.suit + elif cardnum == 1: + if card.suit == 4: + total += 10 + else: + total += (card.rank + 1) + cardnum += 1 + if cards[0].suit == 4: + new_val = 10 + else: + new_val = cards[0].rank + 1 + if cardnum == 1: + if new_val + 10 < goal_rank: + return False + if cardnum == 2: + if total + new_val < goal_rank: + return False + elif cardnum == 3: + if cards[0].suit not in (goal_suit, 4): + return False + + return BasicRowStack.acceptsCards(self, from_stack, cards) + + +class ClearTheDungeon(Game): + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 5 * l.XS, + l.YM + 2 * l.YS + 12 * l.YOFFSET) + + # create stacks + x, y = l.XM, l.YM + for i in range(4): + s.rows.append(ClearTheDungeon_RowStack(x, y, self, + max_move=0, max_accept=1, + dir=0, base_rank=NO_RANK)) + x += l.XS + s.foundations.append(AbstractFoundationStack(x, y, self, suit=ANY_SUIT, + max_move=0, max_accept=0, + max_cards=52)) + x, y = l.XM, self.height - l.YS + for i in range(3): + s.reserves.append(OpenStack(x, y, self, max_cards=1, max_accept=0)) + x += l.XS + + x += l.XS + s.talon = TalonStack(x, y, self) + l.createText(s.talon, "sw") + + y -= l.YS + s.reserves.append(ReserveStack(x, y, self, max_accept=1, max_move=1, + max_cards=52)) + + # define stack-groups + l.defaultStackGroups() + + def _shuffleHook(self, cards): + topcards = [] + for c in cards[:]: + if c.rank in (JACK, QUEEN, KING): + topcards.append(c) + cards.remove(c) + topcards.reverse() + return cards + topcards + + def startGame(self): + for r in self.s.rows: + for j in range(2): + self.s.talon.dealRow(rows=[r], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows) + self.s.talon.dealRow(rows=self.s.reserves[:3]) + + def fillStack(self, stack): + old_state = self.enterState(self.S_FILL) + + for s in self.s.rows: + num_cards = 0 + for c in s.cards: + if c.face_up: + num_cards += 1 + if num_cards == 4: + s.moveMove(4, self.s.foundations[0]) + if len(s.cards) > 0: + s.flipMove() + + if stack in self.s.reserves[:3]: + for stack in self.s.reserves[:3]: + if stack.cards: + self.leaveState(old_state) + return + self.s.talon.dealRow(rows=self.s.reserves[:3], sound=1) + self.leaveState(old_state) + + def isGameWon(self): + for s in self.s.rows: + if len(s.cards) > 0: + return False + return True + + def getAutoStacks(self, event=None): + return ((), (), self.sg.dropstacks) + + +# register the game +registerGame(GameInfo(909, ClearTheDungeon, "Clear the Dungeon", + GI.GT_1DECK_TYPE, 1, 0, GI.SL_MOSTLY_SKILL, + subcategory=GI.GS_JOKER_DECK, trumps=list(range(2))))