1
0
Fork 0
mirror of https://github.com/shlomif/PySolFC.git synced 2025-03-12 04:07:01 -04:00
PySolFC/pysollib/games/braid.py
2025-02-20 18:59:01 -05:00

656 lines
21 KiB
Python

#!/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 <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,
altnames=("Der grose Zopf",)))
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))