1
0
Fork 0
mirror of https://github.com/shlomif/PySolFC.git synced 2025-04-05 00:02:29 -04:00
PySolFC/pysollib/games/braid.py
Alexey Radkov 5402486d1c Well: show the height of stacks of the rows
Without this hint, tracking the height of the rows stacks gets very hard and
uncomfortable. Note that this information doesn't expose any undesirable
secrets because the player knows the base card of each row right after the
dealing the cards. This change will let the player track all the 4 rows base
cards easily during the game.

(cherry picked from commit a04f60833c)
2023-04-29 11:29:51 -04:00

654 lines
21 KiB
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 <http://www.gnu.org/licenses/>.
#
# ---------------------------------------------------------------------------
import math
from pysollib.game import Game
from pysollib.gamedb import GI, GameInfo, registerGame
from pysollib.hint import CautiousDefaultHint, DefaultHint
from pysollib.layout import Layout
from pysollib.mfxutil import kwdefault
from pysollib.mygettext import _
from pysollib.pysoltk import MfxCanvasText
from pysollib.stack import \
AbstractFoundationStack, \
BasicRowStack, \
DealRowRedealTalonStack, \
OpenStack, \
ReserveStack, \
SS_FoundationStack, \
SS_RowStack, \
Stack, \
StackWrapper, \
WasteStack, \
WasteTalonStack
from pysollib.util import ACE, KING, NO_RANK, RANKS, UNLIMITED_CARDS
class Braid_Hint(DefaultHint):
# FIXME: demo is not too clever in this game
pass
# ************************************************************************
# *
# ************************************************************************
class Braid_Foundation(AbstractFoundationStack):
def __init__(self, x, y, game, suit, **cap):
kwdefault(cap, mod=13, dir=0, base_rank=NO_RANK, max_move=0)
AbstractFoundationStack.__init__(self, x, y, game, suit, **cap)
def acceptsCards(self, from_stack, cards):
if not AbstractFoundationStack.acceptsCards(self, from_stack, cards):
return False
if not self.cards:
return True
stack_dir = self.game.getFoundationDir()
if stack_dir == 0:
card_dir = self.getRankDir(cards=(self.cards[-1], cards[0]))
return card_dir in (1, -1)
else:
return ((self.cards[-1].rank + stack_dir) %
self.cap.mod == cards[0].rank)
class Braid_BraidStack(OpenStack):
def __init__(self, x, y, game, sine=0):
OpenStack.__init__(self, x, y, game)
self.CARD_YOFFSET = self.game.app.images.CARD_YOFFSET
CW = self.game.app.images.CARDW
if sine:
# use a sine wave for the x offsets
self.CARD_XOFFSET = []
n = 9
dx = 0.4 * CW * (2*math.pi/n)
last_x = 0
for i in range(n):
x = int(round(dx * math.sin(i + 1)))
# print x, x - last_x
self.CARD_XOFFSET.append(x - last_x)
last_x = x
else:
self.CARD_XOFFSET = (-0.45*CW, 0.35*CW, 0.55*CW, -0.45*CW)
class Braid_RowStack(ReserveStack):
def fillStack(self):
if not self.cards and self.game.s.braid.cards:
self.game.moveMove(1, self.game.s.braid, self)
getBottomImage = Stack._getBraidBottomImage
class Braid_ReserveStack(ReserveStack):
def acceptsCards(self, from_stack, cards):
if from_stack is self.game.s.braid or from_stack in self.game.s.rows:
return False
return ReserveStack.acceptsCards(self, from_stack, cards)
getBottomImage = Stack._getTalonBottomImage
# ************************************************************************
# * Braid
# ************************************************************************
class Braid(Game):
Hint_Class = Braid_Hint
Foundation_Classes = [Braid_Foundation, Braid_Foundation]
BRAID_CARDS = 20
RANKS = RANKS # pull into class Braid
#
# game layout
#
def createGame(self):
# create layout
l, s = Layout(self), self.s
font = self.app.getFont("canvas_default")
# set window
# (piles up to 20 cards are playable - needed for Braid_BraidStack)
decks = self.gameinfo.decks
h = max(4*l.YS + 30, l.YS+(self.BRAID_CARDS-1)*l.YOFFSET)
self.setSize(l.XM+(8+decks)*l.XS, l.YM+h)
# extra settings
self.base_card = None
# create stacks
s.addattr(braid=None) # register extra stack variable
x, y = l.XM, l.YM
for i in range(2):
s.rows.append(Braid_RowStack(x + 0.5*l.XS, y, self))
s.rows.append(Braid_RowStack(x + 4.5*l.XS, y, self))
y = y + 3 * l.YS
y = l.YM + l.YS
for i in range(2):
s.rows.append(Braid_ReserveStack(x, y, self))
s.rows.append(Braid_ReserveStack(x + l.XS, y, self))
s.rows.append(Braid_ReserveStack(x, y + l.YS, self))
s.rows.append(Braid_ReserveStack(x + l.XS, y + l.YS, self))
x = x + 4 * l.XS
x, y = l.XM + l.XS * 5//2, l.YM
s.braid = Braid_BraidStack(x, y, self)
x, y = l.XM + 7 * l.XS, l.YM + l.YS * 3//2
s.talon = WasteTalonStack(x, y, self, max_rounds=3)
l.createText(s.talon, "s")
l.createRoundText(s.talon, 'nn')
x -= l.XS
s.waste = WasteStack(x, y, self)
l.createText(s.waste, "s")
y = l.YM
for i in range(4):
x = l.XM+8*l.XS
for cl in self.Foundation_Classes:
s.foundations.append(cl(x, y, self, suit=i))
x += l.XS
y = y + l.YS
x = l.XM+8*l.XS+decks*l.XS//2
self.texts.info = MfxCanvasText(self.canvas,
x, y, anchor="n", font=font)
# define stack-groups
self.sg.talonstacks = [s.talon] + [s.waste]
self.sg.openstacks = s.foundations + s.rows
self.sg.dropstacks = [s.braid] + s.rows + [s.waste]
#
# game overrides
#
def _shuffleHook(self, cards):
# do not play a trump as the base_card
n = m = -1 - self.BRAID_CARDS - len(self.s.rows)
while cards[n].suit >= len(self.gameinfo.suits):
n = n - 1
cards[n], cards[m] = cards[m], cards[n]
return cards
def startGame(self):
self.base_card = None
self.updateText()
self.startDealSample()
for i in range(self.BRAID_CARDS):
self.s.talon.dealRow(rows=[self.s.braid], frames=4)
self.s.talon.dealRow(frames=4)
# deal base_card to foundations
self.base_card = self.s.talon.cards[-1]
to_stack = self.s.foundations[self.gameinfo.decks*self.base_card.suit]
self.flipMove(self.s.talon)
self.moveMove(1, self.s.talon, to_stack)
self.updateText()
for s in self.s.foundations:
s.cap.base_rank = self.base_card.rank
# deal first card to WasteStack
self.s.talon.dealCards()
shallHighlightMatch = Game._shallHighlightMatch_SSW
def getHighlightPilesStacks(self):
return ()
def _restoreGameHook(self, game):
self.base_card = self.cards[game.loadinfo.base_card_id]
for s in self.s.foundations:
s.cap.base_rank = self.base_card.rank
def _loadGameHook(self, p):
self.loadinfo.addattr(base_card_id=None) # register extra load var.
self.loadinfo.base_card_id = p.load()
def _saveGameHook(self, p):
p.dump(self.base_card.id)
#
# game extras
#
def updateText(self):
if self.preview > 1 or not self.texts.info:
return
if not self.base_card:
t = ""
else:
t = self.RANKS[self.base_card.rank]
dir = self.getFoundationDir()
if dir == 1:
t = t + _(" Ascending")
elif dir == -1:
t = t + _(" Descending")
self.texts.info.config(text=t)
class LongBraid(Braid):
BRAID_CARDS = 24
# ************************************************************************
# * Fort
# ************************************************************************
class Fort(Braid):
Foundation_Classes = [SS_FoundationStack,
StackWrapper(SS_FoundationStack, base_rank=KING,
dir=-1)]
BRAID_CARDS = 21
def _shuffleHook(self, cards):
# move 4 Kings and 4 Aces to top of the Talon
# (i.e. first cards to be dealt)
return self._shuffleHookMoveToTop(
cards,
lambda c: (c.rank in (ACE, KING) and c.deck == 0,
(c.suit, c.rank)))
def _restoreGameHook(self, game):
pass
def _loadGameHook(self, p):
pass
def _saveGameHook(self, p):
pass
def startGame(self):
self.s.talon.dealRow(rows=self.s.foundations, frames=0)
self.startDealSample()
for i in range(self.BRAID_CARDS):
self.s.talon.dealRow(rows=[self.s.braid], frames=4)
self.s.talon.dealRow(frames=4)
self.s.talon.dealCards()
# ************************************************************************
# * Backbone
# ************************************************************************
class Backbone_BraidStack(OpenStack):
def __init__(self, x, y, game, **cap):
OpenStack.__init__(self, x, y, game, **cap)
self.CARD_YOFFSET = self.game.app.images.CARD_YOFFSET
def basicIsBlocked(self):
return len(self.game.s.reserves[2].cards) != 0
class Backbone(Game):
RESERVE_CARDS = 11
Hint_Class = CautiousDefaultHint
def createGame(self, rows=8):
# create layout
l, s = Layout(self), self.s
# set window
w, h = l.XM+(rows+2)*l.XS, max(
l.YM + 3 * l.XS + 10 * l.YOFFSET,
l.YM + 2 * l.YS + self.RESERVE_CARDS * l.YOFFSET + l.TEXT_HEIGHT)
self.setSize(w, h)
# create stacks
y = l.YM
for i in range(4):
x = l.XM+(rows-8)*l.XS//2 + i*l.XS
s.foundations.append(SS_FoundationStack(x, y, self, suit=i))
x = l.XM+(rows//2+2)*l.XS + i*l.XS
s.foundations.append(SS_FoundationStack(x, y, self, suit=i))
x, y = l.XM+rows*l.XS//2, l.YM
s.reserves.append(Backbone_BraidStack(x, y, self, max_accept=0))
x += l.XS
s.reserves.append(Backbone_BraidStack(x, y, self, max_accept=0))
x, y = l.XM+(rows+1)*l.XS//2, l.YM+self.RESERVE_CARDS*l.YOFFSET
s.reserves.append(BasicRowStack(x, y, self, max_accept=0))
x, y = l.XM, l.YM+l.YS
for i in range(rows//2):
s.rows.append(SS_RowStack(x, y, self, max_move=1))
x += l.XS
x, y = l.XM+(rows//2+2)*l.XS, l.YM+l.YS
for i in range(rows//2):
s.rows.append(SS_RowStack(x, y, self, max_move=1))
x += l.XS
x, y = l.XM+rows*l.XS//2, h-l.YS
s.talon = WasteTalonStack(x, y, self, max_rounds=1)
l.createText(s.talon, "n")
x += l.XS
s.waste = WasteStack(x, y, self)
l.createText(s.waste, "n")
# define stack-groups
l.defaultStackGroups()
def startGame(self):
for i in range(self.RESERVE_CARDS - 1):
self.s.talon.dealRow(rows=self.s.reserves[:2], frames=0)
self.s.talon.dealRow(rows=self.s.reserves, frames=0)
self.startDealSample()
self.s.talon.dealRow()
self.s.talon.dealCards()
shallHighlightMatch = Game._shallHighlightMatch_SS
class BackbonePlus(Backbone):
def createGame(self):
Backbone.createGame(self, rows=10)
class SmallBackbone(Backbone):
RESERVE_CARDS = 9
# ************************************************************************
# * Big Braid
# ************************************************************************
class BigBraid(Braid):
Foundation_Classes = [Braid_Foundation, Braid_Foundation, Braid_Foundation]
# ************************************************************************
# * Casket
# ************************************************************************
class Casket_Hint(CautiousDefaultHint):
def computeHints(self):
CautiousDefaultHint.computeHints(self)
if self.hints:
return
if not self.game.s.waste.cards:
return
r = self.game.s.waste.cards[-1].rank
if 0 <= r <= 3:
to_stack = self.game.s.reserves[0]
elif 4 <= r <= 7:
to_stack = self.game.s.reserves[1]
else:
to_stack = self.game.s.reserves[2]
self.addHint(5000, 1, self.game.s.waste, to_stack)
class JewelsStack(OpenStack):
def canFlipCard(self):
return False
class Casket_RowStack(SS_RowStack):
getBottomImage = Stack._getReserveBottomImage
def acceptsCards(self, from_stack, cards):
if not SS_RowStack.acceptsCards(self, from_stack, cards):
return False
if not self.cards:
# don't accepts from lid
return from_stack not in self.game.s.lid
return True
class Casket_Reserve(ReserveStack):
def acceptsCards(self, from_stack, cards):
if not ReserveStack.acceptsCards(self, from_stack, cards):
return False
return from_stack is self.game.s.waste
class Casket(Game):
Hint_Class = Casket_Hint
def createGame(self):
# create layout
l, s = Layout(self), self.s
# set window
self.setSize(l.XM+10*l.XS, l.YM+4.5*l.YS)
# register extra stack variables
s.addattr(jewels=None)
s.addattr(lid=[])
# create stacks
# Lid
x0, y0 = l.XM+2.5*l.XS, l.YM
for xx, yy in ((0, 0.5),
(1, 0.25),
(2, 0),
(3, 0.25),
(4, 0.5),
):
x, y = x0+xx*l.XS, y0+yy*l.YS
s.lid.append(BasicRowStack(x, y, self, max_accept=0))
# Casket
x0, y0 = l.XM+3*l.XS, l.YM+1.5*l.YS
for xx, yy in ((0, 0), (3, 0),
(0, 1), (3, 1),
(0, 2), (1, 2), (2, 2), (3, 2),
):
x, y = x0+xx*l.XS, y0+yy*l.YS
stack = Casket_RowStack(x, y, self, max_move=1)
stack.CARD_YOFFSET = 0
s.rows.append(stack)
# Reserves
x, y = l.XM, l.YM+1.5*l.YS
for i in range(3):
stack = Casket_Reserve(x, y, self, max_cards=UNLIMITED_CARDS)
l.createText(stack, "ne")
s.reserves.append(stack)
y += l.YS
# Foundations
x = l.XM+8*l.XS
for i in range(2):
y = l.YM
for j in range(4):
s.foundations.append(SS_FoundationStack(x, y, self, suit=j))
y += l.YS
x += l.XS
# Jewels
x, y = l.XM+4.5*l.XS, l.YM+2*l.YS
s.jewels = JewelsStack(x, y, self)
l.createText(s.jewels, "s")
# waste & talon
x, y = l.XM, l.YM
s.talon = WasteTalonStack(x, y, self, max_rounds=1)
l.createText(s.talon, "s")
x += l.XS
s.waste = WasteStack(x, y, self, max_cards=1)
# define stack-groups
self.sg.talonstacks = [s.talon] + [s.waste]
self.sg.openstacks = s.foundations + s.rows + s.reserves
self.sg.dropstacks = s.lid + s.rows + [s.waste] + s.reserves
def startGame(self):
for i in range(13):
self.s.talon.dealRow(rows=[self.s.jewels], frames=0, flip=0)
self.startDealSample()
self.s.talon.dealToStacksOrFoundations(stacks=self.s.lid)
self.s.talon.dealToStacksOrFoundations(stacks=self.s.rows)
self.s.talon.dealCards()
def fillStack(self, stack):
if not stack.cards and stack in self.s.lid:
if self.s.jewels.cards:
old_state = self.enterState(self.S_FILL)
self.s.jewels.flipMove()
self.s.jewels.moveMove(1, stack)
self.leaveState(old_state)
shallHighlightMatch = Game._shallHighlightMatch_SS
# ************************************************************************
# * Well
# ************************************************************************
class Well_TalonStack(DealRowRedealTalonStack):
def canDealCards(self):
return DealRowRedealTalonStack.canDealCards(
self, rows=self.game.s.wastes)
def dealCards(self, sound=False):
num_cards = 0
if sound and self.game.app.opt.animations:
self.game.startDealSample()
if not self.cards:
# move all cards to talon
num_cards = self._redeal(rows=self.game.s.wastes, frames=3)
self.game.nextRoundMove(self)
wastes = self.game.s.wastes[:(6-self.round)]
num_cards += self.dealRowAvail(rows=wastes, frames=4, sound=False)
if sound:
self.game.stopSamples()
return num_cards
class Well(Game):
Hint_Class = CautiousDefaultHint
def createGame(self):
# create layout
l, s = Layout(self), self.s
# set window
self.setSize(3*l.XM+6*l.XS, l.YM+6*l.YS+3*l.TEXT_HEIGHT)
# register extra stack variables
s.addattr(wastes=[])
# foundations
suit = 0
x0, y0 = l.XM+1.5*l.XS, l.YM+1.5*l.YS+2*l.TEXT_HEIGHT
for xx, yy in ((3, 0),
(0, 3),
(3, 3),
(0, 0)):
x, y = x0+xx*l.XS, y0+yy*l.YS
s.foundations.append(SS_FoundationStack(x, y, self, suit=suit,
base_rank=KING, mod=13, max_cards=26,
dir=-1, max_move=0))
suit += 1
# rows
x0, y0 = l.XM+l.XS, l.YM+l.YS+2*l.TEXT_HEIGHT
for xx, yy, anchor in ((0, 2, 'w'),
(2, 0, 'n'),
(4, 2, 'e'),
(2, 4, 's')):
x, y = x0+xx*l.XS, y0+yy*l.YS
stack = SS_RowStack(x, y, self, dir=1, mod=13, max_move=1)
stack.getBottomImage = stack._getReserveBottomImage
stack.CARD_YOFFSET = 0
l.createText(stack, anchor)
s.rows.append(stack)
# left stack
x, y = l.XM, l.YM+l.YS+2*l.TEXT_HEIGHT
stack = SS_RowStack(
x, y, self, base_rank=ACE, dir=1, mod=13, max_move=1)
stack.getBottomImage = stack._getReserveBottomImage
stack.CARD_YOFFSET = 0
s.rows.append(stack)
# reserves
x0, y0 = l.XM+2*l.XS, l.YM+2*l.YS+2*l.TEXT_HEIGHT
for xx, yy, anchor in ((0, 1, 'e'),
(1, 0, 's'),
(2, 1, 'w'),
(1, 2, 'n')):
x, y = x0+xx*l.XS, y0+yy*l.YS
stack = OpenStack(x, y, self)
l.createText(stack, anchor)
s.reserves.append(stack)
# wastes
x, y = l.XM+l.XS, l.YM
for i in range(5):
stack = WasteStack(x, y, self)
l.createText(stack, 's', text_format='%D')
s.wastes.append(stack)
x += l.XS
# talon
x, y = l.XM, l.YM
s.talon = Well_TalonStack(x, y, self, max_rounds=5)
l.createText(s.talon, "s")
# define stack-groups
self.sg.talonstacks = [s.talon] + s.wastes
self.sg.openstacks = s.foundations + s.rows
self.sg.dropstacks = s.rows + s.wastes + s.reserves
def startGame(self):
for i in range(10):
self.s.talon.dealRow(rows=self.s.reserves, frames=0)
self.startDealSample()
self.s.talon.dealRow(rows=self.s.rows[:4])
self.s.talon.dealCards()
def fillStack(self, stack):
if not stack.cards and stack in self.s.rows[:4]:
indx = list(self.s.rows).index(stack)
r = self.s.reserves[indx]
if r.cards:
old_state = self.enterState(self.S_FILL)
r.moveMove(1, stack)
self.leaveState(old_state)
shallHighlightMatch = Game._shallHighlightMatch_SSW
# register the game
registerGame(GameInfo(12, Braid, "Braid",
GI.GT_NAPOLEON, 2, 2, GI.SL_BALANCED,
altnames=("Der Zopf", "Plait", "Pigtail")))
registerGame(GameInfo(175, LongBraid, "Long Braid",
GI.GT_NAPOLEON, 2, 2, GI.SL_BALANCED,
altnames=("Der lange Zopf",)))
registerGame(GameInfo(358, Fort, "Fort",
GI.GT_NAPOLEON, 2, 2, GI.SL_BALANCED))
registerGame(GameInfo(376, Backbone, "Backbone",
GI.GT_NAPOLEON, 2, 0, GI.SL_BALANCED))
registerGame(GameInfo(377, BackbonePlus, "Backbone +",
GI.GT_NAPOLEON, 2, 0, GI.SL_BALANCED))
registerGame(GameInfo(510, BigBraid, "Big Braid",
GI.GT_NAPOLEON | GI.GT_ORIGINAL, 3, 2, GI.SL_BALANCED))
registerGame(GameInfo(694, Casket, "Casket",
GI.GT_2DECK_TYPE, 2, 0, GI.SL_BALANCED))
registerGame(GameInfo(717, Well, "Well",
GI.GT_2DECK_TYPE, 2, 4, GI.SL_BALANCED))
registerGame(GameInfo(824, SmallBackbone, "Small Backbone",
GI.GT_NAPOLEON, 2, 0, GI.SL_BALANCED))