mirror of
https://github.com/shlomif/PySolFC.git
synced 2025-04-05 00:02:29 -04:00
Tested on ci. See https://github.com/PyCQA/flake8-import-order . In the process did some other cleanups and https://en.wikipedia.org/wiki/Code_refactoring .
425 lines
14 KiB
Python
425 lines
14 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/>.
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
|
|
from pysollib.game import Game
|
|
from pysollib.gamedb import GI, GameInfo, registerGame
|
|
from pysollib.games.braid import Braid_Foundation
|
|
from pysollib.hint import CautiousDefaultHint
|
|
from pysollib.layout import Layout
|
|
from pysollib.mfxutil import kwdefault
|
|
from pysollib.mygettext import _
|
|
from pysollib.pysoltk import MfxCanvasText
|
|
from pysollib.stack import \
|
|
BasicRowStack, \
|
|
InitialDealTalonStack, \
|
|
ReserveStack, \
|
|
SS_FoundationStack, \
|
|
Stack, \
|
|
StackWrapper, \
|
|
UD_SS_RowStack
|
|
from pysollib.util import ACE, KING, RANKS, UNLIMITED_CARDS
|
|
|
|
|
|
# ************************************************************************
|
|
# * stacks
|
|
# ************************************************************************
|
|
|
|
|
|
class Napoleon_RowStack(UD_SS_RowStack):
|
|
getBottomImage = Stack._getReserveBottomImage
|
|
|
|
|
|
class Napoleon_ReserveStack(BasicRowStack):
|
|
def __init__(self, x, y, game, **cap):
|
|
kwdefault(cap, max_move=1, max_accept=0)
|
|
BasicRowStack.__init__(self, x, y, game, **cap)
|
|
|
|
|
|
class Napoleon_SingleFreeCell(ReserveStack):
|
|
def acceptsCards(self, from_stack, cards):
|
|
# if from_stack.id >= 8:
|
|
# # from_stack must be a Napoleon_RowStack
|
|
# return False
|
|
return ReserveStack.acceptsCards(self, from_stack, cards)
|
|
|
|
def canMoveCards(self, cards):
|
|
if self.game.s.rows[8].cards and self.game.s.rows[9].cards:
|
|
return False
|
|
return ReserveStack.canMoveCards(self, cards)
|
|
|
|
|
|
class Napoleon_FreeCell(ReserveStack):
|
|
def canMoveCards(self, cards):
|
|
if self.game.s.rows[self.id-2].cards:
|
|
return False
|
|
return ReserveStack.canMoveCards(self, cards)
|
|
|
|
|
|
# ************************************************************************
|
|
# * Der kleine Napoleon
|
|
# ************************************************************************
|
|
|
|
class DerKleineNapoleon(Game):
|
|
|
|
Hint_Class = CautiousDefaultHint
|
|
Foundation_Class = Braid_Foundation
|
|
RowStack_Class = StackWrapper(Napoleon_RowStack, mod=13)
|
|
|
|
#
|
|
# game layout
|
|
#
|
|
|
|
def createGame(self, cells=1):
|
|
# create layout
|
|
l, s = Layout(self), self.s
|
|
|
|
# set window
|
|
self.setSize(l.XM + 2*24 + 2*l.XM + 11*l.XS, l.YM + 5*l.YS + 2*l.XM)
|
|
x0 = l.XM + 24 + 4*l.XS
|
|
x1 = x0 + l.XS + l.XM
|
|
x2 = x1 + l.XS + l.XM
|
|
|
|
# create stacks
|
|
y = l.YM
|
|
for i in range(4):
|
|
s.rows.append(self.RowStack_Class(x0, y, self))
|
|
s.rows.append(self.RowStack_Class(x2, y, self))
|
|
y = y + l.YS
|
|
y = self.height - l.YS
|
|
if cells == 1:
|
|
s.rows.append(Napoleon_ReserveStack(x0, y, self))
|
|
s.rows.append(Napoleon_ReserveStack(x2, y, self))
|
|
s.reserves.append(Napoleon_SingleFreeCell(x1, y, self))
|
|
else:
|
|
s.rows.append(Napoleon_ReserveStack(x0 - l.XS, y, self))
|
|
s.rows.append(Napoleon_ReserveStack(x2 + l.XS, y, self))
|
|
s.reserves.append(Napoleon_FreeCell(x0, y, self))
|
|
s.reserves.append(Napoleon_FreeCell(x2, y, self))
|
|
# foundations
|
|
x, y = x1, l.YM
|
|
for i in range(4):
|
|
s.foundations.append(self.Foundation_Class(x, y, self, i))
|
|
y = y + l.YS
|
|
# talon
|
|
if cells == 1:
|
|
# x, y = l.XM, self.height - l.YS
|
|
y = self.height + l.YS
|
|
else:
|
|
y = self.height - l.YS
|
|
s.talon = InitialDealTalonStack(x, y, self)
|
|
|
|
# update stack building direction
|
|
for r in s.rows:
|
|
if r.id & 1 == 0:
|
|
r.CARD_XOFFSET = 4*[-l.XS] + 13*[-2]
|
|
else:
|
|
r.CARD_XOFFSET = 4*[l.XS] + 13*[2]
|
|
r.CARD_YOFFSET = 0
|
|
|
|
# define stack-groups
|
|
l.defaultStackGroups()
|
|
|
|
#
|
|
# game overrides
|
|
#
|
|
|
|
def _shuffleHook(self, cards):
|
|
# move 4 cards of the same rank to bottom of the Talon (i.e.
|
|
# last cards to be dealt)
|
|
rank = cards[-1].rank
|
|
return self._shuffleHookMoveToBottom(
|
|
cards, lambda c, rank=rank: (c.rank == rank, c.suit))
|
|
|
|
def startGame(self):
|
|
for i in range(4):
|
|
self.s.talon.dealRow(rows=self.s.rows[:8], frames=0)
|
|
self.startDealSample()
|
|
self.s.talon.dealRow(rows=self.s.rows[:8])
|
|
for i in range(4):
|
|
self.s.talon.dealRow(rows=self.s.rows[8:])
|
|
self.s.talon.dealBaseCards(ncards=4)
|
|
|
|
shallHighlightMatch = Game._shallHighlightMatch_SSW
|
|
|
|
#
|
|
# game extras
|
|
#
|
|
|
|
def updateText(self):
|
|
if self.preview > 1 or not self.texts.info:
|
|
return
|
|
t = ""
|
|
f = self.s.foundations[0]
|
|
if f.cards:
|
|
t = RANKS[f.cards[0].rank]
|
|
dir = self.getFoundationDir()
|
|
if dir == 1:
|
|
t = t + _(" Ascending")
|
|
elif dir == -1:
|
|
t = t + _(" Descending")
|
|
self.texts.info.config(text=t)
|
|
|
|
|
|
# ************************************************************************
|
|
# * Der freie Napoleon (completely equivalent to Der kleine Napoleon,
|
|
# * just a different layout)
|
|
# ************************************************************************
|
|
|
|
class DerFreieNapoleon(DerKleineNapoleon):
|
|
|
|
Foundation_Class = Braid_Foundation
|
|
RowStack_Class = StackWrapper(Napoleon_RowStack, mod=13)
|
|
ReserveStack_Class = Napoleon_ReserveStack
|
|
FreeCell_Class = Napoleon_SingleFreeCell
|
|
|
|
#
|
|
# game layout
|
|
#
|
|
|
|
def createGame(self, cells=1, reserves=2, texts=True):
|
|
# create layout
|
|
l, s = Layout(self), self.s
|
|
|
|
# set window
|
|
# set size so that at least 2/3 of a card is visible with 15 cards
|
|
h = l.CH*2//3 + (15-1)*l.YOFFSET
|
|
h = l.YS + max(h, 3*l.YS)
|
|
max_rows = 8+max(cells, reserves)
|
|
self.setSize(l.XM + 2*l.XM + max_rows*l.XS, l.YM + h)
|
|
x1 = l.XM + 8*l.XS + 2*l.XM
|
|
|
|
# create stacks
|
|
y = l.YM + l.YS
|
|
for j in range(8):
|
|
x = l.XM + j*l.XS
|
|
s.rows.append(self.RowStack_Class(x, y, self))
|
|
for j in range(reserves):
|
|
x = x1 + j*l.XS
|
|
s.rows.append(self.ReserveStack_Class(x, y, self))
|
|
self.setRegion(s.rows, (-999, y - l.CH//2, 999999, 999999))
|
|
y = l.YM
|
|
x = x1+(max(cells, reserves)-cells)*l.XS//2
|
|
for i in range(cells):
|
|
s.reserves.append(self.FreeCell_Class(x, y, self))
|
|
x += l.XS
|
|
# foundations
|
|
x = l.XM + 2*l.XS
|
|
for i in range(4):
|
|
s.foundations.append(self.Foundation_Class(x, y, self, i))
|
|
x = x + l.XS
|
|
if texts:
|
|
tx, ty, ta, tf = l.getTextAttr(s.foundations[-1], "se")
|
|
font = self.app.getFont("canvas_default")
|
|
self.texts.info = MfxCanvasText(self.canvas, tx, ty, anchor=ta,
|
|
font=font)
|
|
# talon
|
|
x, y = l.XM, self.height - l.YS
|
|
s.talon = InitialDealTalonStack(x, y, self)
|
|
|
|
# define stack-groups
|
|
l.defaultStackGroups()
|
|
|
|
|
|
# ************************************************************************
|
|
# * Napoleon (two FreeCells instead of one SingleFreeCell)
|
|
# ************************************************************************
|
|
|
|
class Napoleon(DerKleineNapoleon):
|
|
def createGame(self):
|
|
DerKleineNapoleon.createGame(self, cells=2)
|
|
|
|
|
|
class FreeNapoleon(DerFreieNapoleon):
|
|
FreeCell_Class = Napoleon_FreeCell
|
|
|
|
def createGame(self):
|
|
DerFreieNapoleon.createGame(self, cells=2)
|
|
|
|
|
|
# ************************************************************************
|
|
# * Master
|
|
# ************************************************************************
|
|
|
|
class Master(DerFreieNapoleon):
|
|
|
|
Foundation_Class = SS_FoundationStack
|
|
|
|
def createGame(self):
|
|
DerFreieNapoleon.createGame(self, cells=2, texts=False)
|
|
|
|
def _shuffleHook(self, cards):
|
|
return self._shuffleHookMoveToBottom(cards,
|
|
lambda c: (c.rank == ACE, c.suit))
|
|
|
|
|
|
# ************************************************************************
|
|
# * The Little Corporal
|
|
# * Bonaparte
|
|
# ************************************************************************
|
|
|
|
class TheLittleCorporal_RowStack(UD_SS_RowStack):
|
|
def acceptsCards(self, from_stack, cards):
|
|
if not UD_SS_RowStack.acceptsCards(self, from_stack, cards):
|
|
return False
|
|
if from_stack in self.game.s.reserves:
|
|
return not self.cards
|
|
return True
|
|
|
|
|
|
class TheLittleCorporal(DerFreieNapoleon):
|
|
|
|
def createGame(self, rows=10):
|
|
l, s = Layout(self), self.s
|
|
# set size so that at least 2/3 of a card is visible with 15 cards
|
|
h = l.CH*2//3 + (15-1)*l.YOFFSET
|
|
h = l.YS + max(h, 3*l.YS)
|
|
self.setSize(l.XM+rows*l.XS, l.YM + h)
|
|
|
|
x, y = l.XM+(rows-8)*l.XS, l.YM
|
|
for i in range(4):
|
|
s.foundations.append(Braid_Foundation(x, y, self, suit=i))
|
|
x += l.XS
|
|
tx, ty, ta, tf = l.getTextAttr(s.foundations[-1], "se")
|
|
font = self.app.getFont("canvas_default")
|
|
self.texts.info = MfxCanvasText(
|
|
self.canvas, tx, ty, anchor=ta, font=font)
|
|
x += 2*l.XS
|
|
stack = ReserveStack(x, y, self, max_cards=UNLIMITED_CARDS)
|
|
s.reserves.append(stack)
|
|
l.createText(stack, 'se')
|
|
x, y = l.XM, l.YM+l.YS
|
|
for i in range(rows):
|
|
s.rows.append(TheLittleCorporal_RowStack(x, y, self, mod=13))
|
|
x += l.XS
|
|
|
|
# talon
|
|
x, y = l.XM, self.height - l.YS
|
|
s.talon = InitialDealTalonStack(x, y, self)
|
|
|
|
# define stack-groups
|
|
l.defaultStackGroups()
|
|
|
|
def startGame(self):
|
|
for i in range(4):
|
|
self.s.talon.dealRow(rows=self.s.rows, frames=0)
|
|
self.startDealSample()
|
|
self.s.talon.dealRow(rows=self.s.rows[1:-1])
|
|
self.s.talon.dealBaseCards(ncards=4)
|
|
|
|
def getQuickPlayScore(self, ncards, from_stack, to_stack):
|
|
if to_stack in self.s.reserves:
|
|
return 0
|
|
return int(len(to_stack.cards) != 0)+1
|
|
|
|
|
|
class Bonaparte(TheLittleCorporal):
|
|
|
|
def createGame(self):
|
|
TheLittleCorporal.createGame(self, rows=8)
|
|
|
|
def startGame(self):
|
|
self._startDealNumRows(5)
|
|
self.s.talon.dealRow()
|
|
self.s.talon.dealBaseCards(ncards=4)
|
|
|
|
|
|
# ************************************************************************
|
|
# * Busy Cards
|
|
# ************************************************************************
|
|
|
|
class BusyCards_FreeCell(ReserveStack):
|
|
def canMoveCards(self, cards):
|
|
if not ReserveStack.canMoveCards(self, cards):
|
|
return False
|
|
rows = self.game.s.rows
|
|
index = list(self.game.s.reserves).index(self)
|
|
if rows[2*index].cards or rows[2*index+1].cards:
|
|
return False
|
|
return True
|
|
|
|
|
|
class BusyCards(Game):
|
|
Hint_Class = CautiousDefaultHint
|
|
|
|
def createGame(self):
|
|
rows = 12
|
|
|
|
l, s = Layout(self), self.s
|
|
self.setSize(l.XM+rows*l.XS, l.YM + 3*l.YS+16*l.YOFFSET)
|
|
|
|
x, y = l.XM+(rows-8)*l.XS//2, l.YM
|
|
for i in range(4):
|
|
s.foundations.append(SS_FoundationStack(x, y, self, suit=i))
|
|
x += l.XS
|
|
for i in range(4):
|
|
s.foundations.append(SS_FoundationStack(x, y, self, suit=i,
|
|
base_rank=KING, dir=-1))
|
|
x += l.XS
|
|
|
|
x, y = l.XM+l.XS//2, l.YM+l.YS
|
|
for i in range(rows//2):
|
|
s.reserves.append(BusyCards_FreeCell(x, y, self))
|
|
x += 2*l.XS
|
|
|
|
x, y = l.XM, l.YM+2*l.YS
|
|
for i in range(rows):
|
|
s.rows.append(UD_SS_RowStack(x, y, self))
|
|
x += l.XS
|
|
|
|
x, y = l.XM, self.height - l.YS
|
|
s.talon = InitialDealTalonStack(x, y, self)
|
|
|
|
l.defaultStackGroups()
|
|
|
|
def _shuffleHook(self, cards):
|
|
return self._shuffleHookMoveToTop(
|
|
cards,
|
|
lambda c: ((c.rank in (ACE, KING) and c.deck == 0),
|
|
(c.rank, c.suit)))
|
|
|
|
def startGame(self):
|
|
self.s.talon.dealRow(rows=self.s.foundations, frames=0)
|
|
self._startDealNumRowsAndDealSingleRow(7)
|
|
|
|
shallHighlightMatch = Game._shallHighlightMatch_SS
|
|
|
|
|
|
# register the game
|
|
registerGame(GameInfo(167, DerKleineNapoleon, "Der kleine Napoleon",
|
|
GI.GT_NAPOLEON | GI.GT_OPEN, 1, 0, GI.SL_MOSTLY_SKILL))
|
|
registerGame(GameInfo(168, DerFreieNapoleon, "Der freie Napoleon",
|
|
GI.GT_NAPOLEON | GI.GT_OPEN, 1, 0, GI.SL_MOSTLY_SKILL))
|
|
registerGame(GameInfo(169, Napoleon, "Napoleon",
|
|
GI.GT_NAPOLEON | GI.GT_OPEN, 1, 0, GI.SL_MOSTLY_SKILL))
|
|
registerGame(GameInfo(170, FreeNapoleon, "Free Napoleon",
|
|
GI.GT_NAPOLEON | GI.GT_OPEN, 1, 0, GI.SL_MOSTLY_SKILL))
|
|
registerGame(GameInfo(536, Master, "Master",
|
|
GI.GT_NAPOLEON | GI.GT_OPEN, 1, 0, GI.SL_MOSTLY_SKILL))
|
|
registerGame(GameInfo(537, TheLittleCorporal, "The Little Corporal",
|
|
GI.GT_NAPOLEON | GI.GT_OPEN, 1, 0, GI.SL_MOSTLY_SKILL))
|
|
registerGame(GameInfo(538, Bonaparte, "Bonaparte",
|
|
GI.GT_NAPOLEON | GI.GT_OPEN | GI.GT_ORIGINAL, 1, 0,
|
|
GI.SL_MOSTLY_SKILL))
|
|
registerGame(GameInfo(705, BusyCards, "Busy Cards",
|
|
GI.GT_NAPOLEON | GI.GT_OPEN | GI.GT_ORIGINAL, 2, 0,
|
|
GI.SL_MOSTLY_SKILL))
|