From 6ab53a4a621170abe9e71bf0c18472071d8f708e Mon Sep 17 00:00:00 2001 From: Joe R Date: Sun, 4 Jul 2021 14:48:59 -0400 Subject: [PATCH] Added Cribbage Square game, and variations. --- html-src/rules/cribbageshuffle.html | 41 ++++ html-src/rules/cribbagesquare.html | 46 ++++ html-src/rules/pokershuffle.html | 15 ++ html-src/rules/pokersquare.html | 15 ++ pysollib/gamedb.py | 3 + pysollib/games/special/__init__.py | 1 + pysollib/games/special/cribbage.py | 357 ++++++++++++++++++++++++++++ pysollib/games/special/poker.py | 24 +- scripts/all_games.py | 2 +- 9 files changed, 494 insertions(+), 10 deletions(-) create mode 100644 html-src/rules/cribbageshuffle.html create mode 100644 html-src/rules/cribbagesquare.html create mode 100644 pysollib/games/special/cribbage.py diff --git a/html-src/rules/cribbageshuffle.html b/html-src/rules/cribbageshuffle.html new file mode 100644 index 00000000..0a9a229c --- /dev/null +++ b/html-src/rules/cribbageshuffle.html @@ -0,0 +1,41 @@ +

Cribbage Shuffle

+

+Cribbage type. 1 deck. No redeal. + +

Object

+

+Arrange the 10 Cribbage hands for a total score of 61 points or more. + +

Rules

+

+At game start 16 cards are dealt to the tableau piles and one card is +flipped face up to be used as the starter, which for most purposes, is treated +as a fifth card for all hands. +

+Swap any 2 cards on the tableau to maximize your score. The starter cannot +be moved. +

+Points are awarded for the 4 Cribbage hands from left to right and for +the 4 hands from top to bottom. +

+You win if your score reaches 61 points. +

Cribbage Scoring

+

+Cribbage hands are scored as follows - each hand is worth the total value +of all of the following: +

diff --git a/html-src/rules/cribbagesquare.html b/html-src/rules/cribbagesquare.html new file mode 100644 index 00000000..2cef3526 --- /dev/null +++ b/html-src/rules/cribbagesquare.html @@ -0,0 +1,46 @@ +

Cribbage Square

+

+Cribbage type. 1 deck. No redeal. + +

Object

+

+Arrange the 10 Poker hands for a total score of 61 points or more. + +

Rules

+

+Place the 16 cards on the tableau to get a score of 61 points or more. +

+Once on a stack, a card cannot be moved. +

+If you are playing a variant with reserve or waste stacks, cards may +be moved onto the top of one or two five-card reserve/waste stacks. The +top card of a reserve stack may be moved to the grid at any time, but if +a card is moved to a waste stack, it cannot be moved. +

+Once all of the cards have been moved into the grid, another card +from the deck is flipped up to be used as the starter. The starter is +mostly treated as a fifth card for each hand. The starter cannot be moved +to reserve or waste stacks. +

+Points are awarded for the 4 Cribbage hands from left to right and for +the 4 hands from top to bottom. +

Cribbage Scoring

+

+Cribbage hands are scored as follows - each hand is worth the total value +of all of the following: +

diff --git a/html-src/rules/pokershuffle.html b/html-src/rules/pokershuffle.html index 8c822b75..cd756603 100644 --- a/html-src/rules/pokershuffle.html +++ b/html-src/rules/pokershuffle.html @@ -16,3 +16,18 @@ Points are awarded for the 5 Poker hands from left to right and for the 5 hands from top to bottom.

You win if your score reaches 200 points. +

Poker Hands

+

+Poker hands are as follows, ordered from least valuable to most valuable. +Each hand is scored based as most valuable type: +

diff --git a/html-src/rules/pokersquare.html b/html-src/rules/pokersquare.html index a9a3b7a0..665eb704 100644 --- a/html-src/rules/pokersquare.html +++ b/html-src/rules/pokersquare.html @@ -19,3 +19,18 @@ a card is moved to a waste stack, it cannot be moved.

Points are awarded for the 5 Poker hands from left to right and for the 5 hands from top to bottom. +

Poker Hands

+

+Poker hands are as follows, ordered from least valuable to most valuable. +Each hand is scored based as most valuable type: +

diff --git a/pysollib/gamedb.py b/pysollib/gamedb.py index 91a19656..7f473185 100644 --- a/pysollib/gamedb.py +++ b/pysollib/gamedb.py @@ -87,6 +87,7 @@ class GI: GT_SHISEN_SHO = 34 GT_HANOI = 35 GT_PEGGED = 36 + GT_CRIBBAGE_TYPE = 37 GT_CUSTOM = 40 # extra flags GT_BETA = 1 << 12 # beta version of game driver @@ -224,6 +225,8 @@ class GI: ) SELECT_SPECIAL_GAME_BY_TYPE = ( + (n_("Cribbage type"), + lambda gi, gt=GT_CRIBBAGE_TYPE: gi.si.game_type == gt), (n_("Hex A Deck type"), lambda gi, gt=GT_HEXADECK: gi.si.game_type == gt), (n_("Matrix type"), lambda gi, gt=GT_MATRIX: gi.si.game_type == gt), diff --git a/pysollib/games/special/__init__.py b/pysollib/games/special/__init__.py index 06c32274..da8e0144 100644 --- a/pysollib/games/special/__init__.py +++ b/pysollib/games/special/__init__.py @@ -20,6 +20,7 @@ # along with this program. If not, see . # # ---------------------------------------------------------------------------## +from . import cribbage # noqa: F401 from . import hanoi # noqa: F401 from . import memory # noqa: F401 from . import pegged # noqa: F401 diff --git a/pysollib/games/special/cribbage.py b/pysollib/games/special/cribbage.py new file mode 100644 index 00000000..928b76c3 --- /dev/null +++ b/pysollib/games/special/cribbage.py @@ -0,0 +1,357 @@ +#!/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.mygettext import _ +from pysollib.pysoltk import MfxCanvasText +from pysollib.stack import \ + InitialDealTalonStack, \ + InvisibleStack, \ + OpenTalonStack, \ + ReserveStack, \ + StackWrapper + +# ************************************************************************ +# * Cribbage Square +# ************************************************************************ + + +class CribbageSquare_RowStack(ReserveStack): + def clickHandler(self, event): + if not self.cards: + self.game.s.talon.playMoveMove(1, self) + return 1 + return ReserveStack.clickHandler(self, event) + + rightclickHandler = clickHandler + + +class CribbageSquare_Talon(OpenTalonStack): + def canMoveCards(self, cards): + if self.game.isBoardFull(): + return False + return OpenTalonStack.canMoveCards(self, cards) + + +class CribbageSquare(Game): + Talon_Class = CribbageSquare_Talon + RowStack_Class = StackWrapper(CribbageSquare_RowStack, max_move=0) + Hint_Class = None + + WIN_SCORE = 61 + NUM_RESERVE = 0 + RESERVE_STACK = StackWrapper(ReserveStack, max_cards=5) + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # create texts 1) + ta = "ss" + x, y = l.XM, l.YM + 2 * l.YS + + # set window + w = max(2 * l.XS, x, ((self.NUM_RESERVE + 1) * l.XS) + (4 * l.XM)) + self.setSize(l.XM + w + 4 * l.XS + 50, l.YM + 4 * l.YS + 30) + + # create stacks + for i in range(4): + for j in range(4): + x, y = l.XM + w + j * l.XS, l.YM + i * l.YS + s.rows.append(self.RowStack_Class(x, y, self)) + x, y = l.XM, l.YM + s.talon = self.Talon_Class(x, y, self) + l.createText(s.talon, anchor=ta) + s.internals.append(InvisibleStack(self)) # for _swapPairMove() + + for i in range(self.NUM_RESERVE): + x, y = ((i + 1) * l.XS) + (2 * l.XM), l.YM + s.reserves.append(self.RESERVE_STACK(x, y, self)) + + # create texts 2) + if self.preview <= 1: + for i in (3, 7, 11, 15): + tx, ty, ta, tf = l.getTextAttr(s.rows[i], anchor="e") + t = MfxCanvasText(self.canvas, tx + 8, ty, + anchor=ta, + font=self.app.getFont("canvas_default")) + self.texts.list.append(t) + for i in range(12, 16): + tx, ty, ta, tf = l.getTextAttr(s.rows[i], anchor="ss") + t = MfxCanvasText(self.canvas, tx, ty, anchor=ta, + font=self.app.getFont("canvas_default")) + self.texts.list.append(t) + self.texts.score = MfxCanvasText( + self.canvas, l.XM, 4 * l.YS, anchor="sw", + font=self.app.getFont("canvas_large")) + + # define hands for scoring + r = s.rows + self.cribbage_hands = [ + r[0:4], r[4:8], r[8:12], r[12:16], + (r[0], r[0+4], r[0+8], r[0+12]), + (r[1], r[1+4], r[1+8], r[1+12]), + (r[2], r[2+4], r[2+8], r[2+12]), + (r[3], r[3+4], r[3+8], r[3+12]) + ] + self.cribbage_hands = list(map(tuple, self.cribbage_hands)) + + # define stack-groups + l.defaultStackGroups() + return l + + # + # game overrides + # + + def startGame(self): + self.moveMove(35 - (5 * self.NUM_RESERVE), self.s.talon, + self.s.internals[0], frames=0) + self.s.talon.fillStack() + + def isBoardFull(self): + for i in range(16): + if len(self.s.rows[i].cards) == 0: + return False + return True + + def isGameWon(self): + if self.isBoardFull(): + return self.getGameScore() >= self.WIN_SCORE + + return False + + def getAutoStacks(self, event=None): + return ((), (), ()) + + # + # scoring + # + + def updateText(self): + if self.preview > 1: + return + score = 0 + for i in range(8): + value = self.getHandScore(self.cribbage_hands[i]) + + self.texts.list[i].config(text=str(value)) + score += value + # + score = self.checkHisHeels(score) + t = "" + if score >= self.WIN_SCORE: + t = _("WON\n\n") + if not self.isBoardFull(): + t += _("Points: %d") % score + else: + t += _("Total: %d") % score + self.texts.score.config(text=t) + + def getGameScore(self): + score = 0 + for hand in self.cribbage_hands: + value = self.getHandScore(hand) + score += value + + score = self.checkHisHeels(score) + return score + + def getAllCombinations(self, hand): + if hand == (): + return ((),) + + x = self.getAllCombinations(hand[1:]) + + return x + tuple([(hand[0],) + y for y in x]) + + def getHandScore(self, hand): + same_suit = [0] * 4 + hand_score = 0 + + upcard = None + upcard_talon = None + if self.isBoardFull(): + upcard = self.s.talon.cards[0] + upcard_talon = self.s.talon + + # First get flushes and his nobs, as these can only be + # scored once per hand. + for s in hand: + if s.cards: + suit = s.cards[0].suit + same_suit[suit] = same_suit[suit] + 1 + if upcard is not None and s.cards[0].rank == 10 \ + and s.cards[0].suit == upcard.suit: + hand_score += 1 # His nobs + # + if max(same_suit) == 4: + hand_score += 4 # Flush + if upcard is not None and upcard.suit == hand[0].cards[0].suit: + hand_score += 1 # Flush of five + + if upcard is not None: + hand = hand + (upcard_talon,) + combos = self.getAllCombinations(hand) + + longest_run = 3 + run_score = 0 + # The remaining hands can be scored for every combination. + for c in combos: + c_same_rank = [0] * 13 + c_ranks = [] + total = 0 + incomplete = False + + for s in c: + if s.cards: + rank = s.cards[0].rank + c_same_rank[rank] = c_same_rank[rank] + 1 + c_ranks.append(rank) + + if rank < 10: + total += (rank + 1) + else: + total += 10 + else: + incomplete = True + break + + if incomplete: + continue + + if total == 15: + hand_score += 2 # Fifteen + + if len(c) == 2 and max(c_same_rank) == 2: + hand_score += 2 # Pair + + # For runs, we only want to consider the longest run + if len(c) >= longest_run: + if c_same_rank.count(1) == len(c): + d = max(c_ranks) - min(c_ranks) + if d == len(c) - 1: + if len(c) > longest_run: + run_score = 0 + longest_run = len(c) + run_score += longest_run # Runs + + hand_score += run_score + + return hand_score + + def checkHisHeels(self, score): + if self.isBoardFull() and self.s.talon.cards[0].rank == 10: + return score + 2 + return score + + +# ************************************************************************ +# * Cribbage Shuffle +# ************************************************************************ + +class CribbageShuffle_RowStack(ReserveStack): + def moveMove(self, ncards, to_stack, frames=-1, shadow=-1): + assert ncards == 1 and to_stack in self.game.s.rows + assert len(to_stack.cards) == 1 + self._swapPairMove(ncards, to_stack, frames=-1, shadow=0) + + def _swapPairMove(self, n, other_stack, frames=-1, shadow=-1): + game = self.game + old_state = game.enterState(game.S_FILL) + swap = game.s.internals[0] + game.moveMove(n, self, swap, frames=0) + game.moveMove(n, other_stack, self, frames=frames, shadow=shadow) + game.moveMove(n, swap, other_stack, frames=0) + game.leaveState(old_state) + + +class CribbageShuffle(CribbageSquare): + Talon_Class = InitialDealTalonStack + RowStack_Class = StackWrapper( + CribbageShuffle_RowStack, max_accept=1, max_cards=2) + + WIN_SCORE = 61 + + def createGame(self): + CribbageSquare.createGame(self) + if self.s.talon.texts.ncards: + self.s.talon.texts.ncards.text_format = "%D" + + def startGame(self): + self.moveMove(35, self.s.talon, self.s.internals[0], frames=0) + self._startAndDealRow() + self.s.talon.flipMove() + + def checkForWin(self): + return 0 + +# ************************************************************************ +# * Cribbage Square (Waste) +# * Cribbage Square (1 Reserve) +# * Cribbage Square (2 Reserves) +# ************************************************************************ + + +class CribbageSquareWaste(CribbageSquare): + NUM_RESERVE = 1 + RESERVE_STACK = StackWrapper(ReserveStack, max_cards=5, max_move=0) + + +class CribbageSquare1Reserve(CribbageSquare): + NUM_RESERVE = 1 + + +class CribbageSquare2Reserves(CribbageSquare): + NUM_RESERVE = 2 + + +# register the game +registerGame(GameInfo(805, CribbageSquare, "Cribbage Square", + GI.GT_CRIBBAGE_TYPE | GI.GT_SCORE, 1, 0, + GI.SL_MOSTLY_SKILL, si={"ncards": 17})) +registerGame(GameInfo(806, CribbageSquareWaste, "Cribbage Square (Waste)", + GI.GT_CRIBBAGE_TYPE | GI.GT_SCORE, 1, 0, + GI.SL_MOSTLY_SKILL, si={"ncards": 22}, + rules_filename="cribbagesquare.html")) +registerGame(GameInfo(807, CribbageSquare1Reserve, + "Cribbage Square (1 Reserve)", + GI.GT_CRIBBAGE_TYPE | GI.GT_SCORE, 1, 0, + GI.SL_MOSTLY_SKILL, si={"ncards": 22}, + rules_filename="cribbagesquare.html")) +registerGame(GameInfo(808, CribbageSquare2Reserves, + "Cribbage Square (2 Reserves)", + GI.GT_CRIBBAGE_TYPE | GI.GT_SCORE, 1, 0, + GI.SL_MOSTLY_SKILL, si={"ncards": 27}, + rules_filename="cribbagesquare.html")) +registerGame(GameInfo(809, CribbageShuffle, "Cribbage Shuffle", + GI.GT_CRIBBAGE_TYPE | GI.GT_SCORE | GI.GT_OPEN, 1, 0, + GI.SL_MOSTLY_SKILL, + si={"ncards": 17})) diff --git a/pysollib/games/special/poker.py b/pysollib/games/special/poker.py index 11fb9a15..cdf07f5d 100644 --- a/pysollib/games/special/poker.py +++ b/pysollib/games/special/poker.py @@ -101,8 +101,8 @@ One Pair''')) x = self.texts.misc.bbox()[1][0] + 32 # set window - w = max(2*l.XS, x, ((self.NUM_RESERVE + 1) * l.XS) + (4 * l.XM)) - self.setSize(l.XM + w + 5*l.XS + 50, l.YM + 5*l.YS + 30) + w = max(2 * l.XS, x, ((self.NUM_RESERVE + 1) * l.XS) + (4 * l.XM)) + self.setSize(l.XM + w + 5 * l.XS + 50, l.YM + 5 * l.YS + 30) # create stacks for i in range(5): @@ -160,11 +160,17 @@ One Pair''')) self.s.internals[0], frames=0) self.s.talon.fillStack() - def isGameWon(self): + def isBoardFull(self): for i in range(25): if len(self.s.rows[i].cards) == 0: return False - return self.getGameScore() >= self.WIN_SCORE + return True + + def isGameWon(self): + if self.isBoardFull(): + return self.getGameScore() >= self.WIN_SCORE + + return False def getAutoStacks(self, event=None): return ((), (), ()) @@ -181,19 +187,19 @@ One Pair''')) for i in range(10): type, value = self.getHandScore(self.poker_hands[i]) if 0 <= type <= 8: - count[type] = count[type] + 1 + count[type] += 1 self.texts.list[i+2].config(text=str(value)) - score = score + value + score += value t = '\n'.join(map(str, count)) self.texts.misc.config(text=t) # t = "" if score >= self.WIN_SCORE: t = _("WON\n\n") - if self.s.talon.cards: - t = t + _("Points: %d") % score + if not self.isBoardFull(): + t += _("Points: %d") % score else: - t = t + _("Total: %d") % score + t += _("Total: %d") % score self.texts.score.config(text=t) def getGameScore(self): diff --git a/scripts/all_games.py b/scripts/all_games.py index a664263a..18e0fe09 100755 --- a/scripts/all_games.py +++ b/scripts/all_games.py @@ -75,7 +75,7 @@ GAME_BY_TYPE = { GI.GT_SHISEN_SHO: "Shisen-Sho", GI.GT_HANOI: "Tower of Hanoi", GI.GT_PEGGED: "Pegged", - + GI.GT_CRIBBAGE_TYPE: "Cribbage", }