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:
+
+- 15 - 2 points are added to each hand for every combination of cards that
+adds up to 15.
+
- Pair - 2 points are added for each set of two cards of the same rank.
+
- Pair Royal - For sets of the same rank of more than two cards, they
+are scored by the number of individual pairs they contain.
+
- Run - Each combination of three or more cards of consecutive ranks
+scores 1 point per card. Only the largest possible run is considered.
+
- Flush - Four cards of the same suit are worth 4 points. The starter
+is not consiered when scoring flushes, but if it does match the suit of a
+flush, that is an extra point.
+
- His Nobs - A jack of the same suit as the starter adds 1 point to
+the hand.
+
- His Heels - If the starter is a Jack, 2 points are added to the
+entire game.
+
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:
+
+- 15 - 2 points are added to each hand for every combination of cards that
+adds up to 15.
+
- Pair - 2 points are added for each set of two cards of the same rank.
+
- Pair Royal - For sets of the same rank of more than two cards, they
+are scored by the number of individual pairs they contain.
+
- Run - Each combination of three or more cards of consecutive ranks
+scores 1 point per card. Only the largest possible run is considered.
+
- Flush - Four cards of the same suit are worth 4 points. The starter
+is not consiered when scoring flushes, but if it does match the suit of a
+flush, that is an extra point.
+
- His Nobs - A jack of the same suit as the starter adds 1 point to
+the hand.
+
- His Heels - If the starter is a Jack, 2 points are added to the
+entire game.
+
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:
+
+- One Pair - Two cards of the same rank.
+
- Two Pair - Two sets of two cards of the same rank.
+
- 3 of a Kind - Three cards of the same rank.
+
- Straight - Five cards of sequential rank.
+
- Flush - Five cards of the same suit.
+
- Full House - A three of a kind plus a pair.
+
- 4 of a Kind - Four cards of the same rank.
+
- Straight Flush - Five cards of sequential rank of the same suit.
+
- Royal Flush - The ten, jack, queen, king and ace of the same suit.
+
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:
+
+- One Pair - Two cards of the same rank.
+
- Two Pair - Two sets of two cards of the same rank.
+
- 3 of a Kind - Three cards of the same rank.
+
- Straight - Five cards of sequential rank.
+
- Flush - Five cards of the same suit.
+
- Full House - A three of a kind plus a pair.
+
- 4 of a Kind - Four cards of the same rank.
+
- Straight Flush - Five cards of sequential rank of the same suit.
+
- Royal Flush - The ten, jack, queen, king and ace of the same suit.
+
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",
}