mirror of
https://github.com/shlomif/PySolFC.git
synced 2025-04-05 00:02:29 -04:00
3355 lines
118 KiB
Python
3355 lines
118 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.mfxutil import Image, ImageTk, USE_PIL
|
|
from pysollib.mfxutil import Struct, SubclassResponsibility, kwdefault
|
|
from pysollib.mygettext import _
|
|
from pysollib.pysoltk import ANCHOR_NW, ANCHOR_SE
|
|
from pysollib.pysoltk import CURSOR_DOWN_ARROW, CURSOR_DRAG
|
|
from pysollib.pysoltk import EVENT_HANDLED, EVENT_PROPAGATE
|
|
from pysollib.pysoltk import MfxCanvasGroup, MfxCanvasImage
|
|
from pysollib.pysoltk import MfxCanvasRectangle, MfxCanvasText
|
|
from pysollib.pysoltk import after_cancel, after_idle
|
|
from pysollib.pysoltk import bind, unbind_destroy
|
|
from pysollib.pysoltk import get_text_width
|
|
from pysollib.pysoltk import markImage
|
|
from pysollib.settings import DEBUG
|
|
from pysollib.settings import TOOLKIT
|
|
from pysollib.util import ACE, KING
|
|
from pysollib.util import ANY_RANK, ANY_SUIT, NO_RANK
|
|
|
|
# ************************************************************************
|
|
# * Let's start with some test methods for cards.
|
|
# * Empty card-lists return false.
|
|
# ************************************************************************
|
|
|
|
|
|
# check that all cards are face-up
|
|
def cardsFaceUp(cards):
|
|
if not cards:
|
|
return False
|
|
for c in cards:
|
|
if not c.face_up:
|
|
return False
|
|
return True
|
|
|
|
|
|
# check that all cards are face-down
|
|
def cardsFaceDown(cards):
|
|
if not cards:
|
|
return False
|
|
for c in cards:
|
|
if c.face_up:
|
|
return False
|
|
return True
|
|
|
|
|
|
# check that cards are face-up and build down by rank
|
|
def isRankSequence(cards, mod=8192, dir=-1):
|
|
if not cardsFaceUp(cards):
|
|
return False
|
|
c1 = cards[0]
|
|
for c2 in cards[1:]:
|
|
if (c1.rank + dir) % mod != c2.rank:
|
|
return False
|
|
c1 = c2
|
|
return True
|
|
|
|
|
|
# check that cards are face-up and build down by alternate color
|
|
def isAlternateColorSequence(cards, mod=8192, dir=-1):
|
|
if not cardsFaceUp(cards):
|
|
return False
|
|
c1 = cards[0]
|
|
for c2 in cards[1:]:
|
|
if (c1.rank + dir) % mod != c2.rank or c1.color == c2.color:
|
|
return False
|
|
c1 = c2
|
|
return True
|
|
|
|
|
|
# check that cards are face-up and build down by same color
|
|
def isSameColorSequence(cards, mod=8192, dir=-1):
|
|
if not cardsFaceUp(cards):
|
|
return False
|
|
c1 = cards[0]
|
|
for c2 in cards[1:]:
|
|
if (c1.rank + dir) % mod != c2.rank or c1.color != c2.color:
|
|
return False
|
|
c1 = c2
|
|
return True
|
|
|
|
|
|
# check that cards are face-up and build down by same suit
|
|
def isSameSuitSequence(cards, mod=8192, dir=-1):
|
|
if not cardsFaceUp(cards):
|
|
return False
|
|
c1 = cards[0]
|
|
for c2 in cards[1:]:
|
|
if (c1.rank + dir) % mod != c2.rank or c1.suit != c2.suit:
|
|
return False
|
|
c1 = c2
|
|
return True
|
|
|
|
|
|
# check that cards are face-up and build down by any suit but own
|
|
def isAnySuitButOwnSequence(cards, mod=8192, dir=-1):
|
|
if not cardsFaceUp(cards):
|
|
return False
|
|
c1 = cards[0]
|
|
for c2 in cards[1:]:
|
|
if (c1.rank + dir) % mod != c2.rank or c1.suit == c2.suit:
|
|
return False
|
|
c1 = c2
|
|
return True
|
|
|
|
|
|
def getNumberOfFreeStacks(stacks):
|
|
return len([s for s in stacks if not s.cards])
|
|
|
|
|
|
# collect the top cards of several stacks into a pile
|
|
def getPileFromStacks(stacks, reverse=False):
|
|
cards = []
|
|
for s in stacks:
|
|
if not s.cards or not s.cards[-1].face_up:
|
|
return None
|
|
cards.append(s.cards[-1])
|
|
return (reversed(cards) if reverse else cards)
|
|
|
|
|
|
class Stack:
|
|
# A generic stack of cards.
|
|
#
|
|
# This is used as a base class for all other stacks (e.g. the talon,
|
|
# the foundations and the row stacks).
|
|
#
|
|
# The default event handlers turn the top card of the stack with
|
|
# its face up on a (single or double) click, and also support
|
|
# moving a subpile around.
|
|
|
|
# constants
|
|
MIN_VISIBLE_XOFFSET = 3
|
|
MIN_VISIBLE_YOFFSET = 3
|
|
SHRINK_FACTOR = 2.
|
|
|
|
def __init__(self, x, y, game, cap={}):
|
|
# Arguments are the stack's nominal x and y position (the top
|
|
# left corner of the first card placed in the stack), and the
|
|
# game object (which is used to get the canvas; subclasses use
|
|
# the game object to find other stacks).
|
|
|
|
#
|
|
# link back to game
|
|
#
|
|
id = len(game.allstacks)
|
|
game.allstacks.append(self)
|
|
x = int(round(x))
|
|
y = int(round(y))
|
|
mapkey = (x, y)
|
|
# assert not game.stackmap.has_key(mapkey) ## can happen in PyJonngg
|
|
game.stackmap[mapkey] = id
|
|
|
|
#
|
|
# setup our pseudo MVC scheme
|
|
#
|
|
model, view = self, self
|
|
|
|
#
|
|
# model
|
|
#
|
|
model.id = id
|
|
model.game = game
|
|
model.cards = []
|
|
#
|
|
model.is_filled = False
|
|
|
|
# capabilites - the game logic
|
|
model.cap = Struct(
|
|
suit=-1, # required suit for this stack (-1 is ANY_SUIT)
|
|
color=-1, # required color for this stack (-1 is ANY_COLOR)
|
|
rank=-1, # required rank for this stack (-1 is ANY_RANK)
|
|
base_suit=-1, # base suit for this stack (-1 is ANY_SUIT)
|
|
base_color=-1, # base color for this stack (-1 is ANY_COLOR)
|
|
base_rank=-1, # base rank for this stack (-1 is ANY_RANK)
|
|
dir=0, # direction - stack builds up/down
|
|
mod=8192, # modulo for wrap around (typically 13 or 8192)
|
|
max_move=0, # can move at most # cards at a time
|
|
max_accept=0, # can accept at most # cards at a time
|
|
max_cards=999999, # total number of cards may not exceed this
|
|
# not commonly used:
|
|
min_move=1, # must move at least # cards at a time
|
|
min_accept=1, # must accept at least # cards at a time
|
|
# total number of cards this stack at least requires
|
|
min_cards=0,
|
|
)
|
|
model.cap.update(cap)
|
|
assert isinstance(model.cap.suit, int)
|
|
assert isinstance(model.cap.color, int)
|
|
assert isinstance(model.cap.rank, int)
|
|
assert isinstance(model.cap.base_suit, int)
|
|
assert isinstance(model.cap.base_color, int)
|
|
assert isinstance(model.cap.base_rank, int)
|
|
#
|
|
# view
|
|
#
|
|
self.init_coord = (x, y)
|
|
view.x = x
|
|
view.y = y
|
|
view.canvas = game.canvas
|
|
view.CARD_XOFFSET = 0
|
|
view.CARD_YOFFSET = 0
|
|
view.INIT_CARD_OFFSETS = (0, 0)
|
|
view.INIT_CARD_YOFFSET = 0 # for reallocateCards
|
|
view.group = MfxCanvasGroup(view.canvas)
|
|
|
|
if (TOOLKIT == 'kivy'):
|
|
if hasattr(view.group, 'stack'):
|
|
view.group.stack = self
|
|
|
|
view.shrink_face_down = 1
|
|
# image items
|
|
view.images = Struct(
|
|
bottom=None, # canvas item
|
|
redeal=None, # canvas item
|
|
redeal_img=None, # the corresponding PhotoImage
|
|
shade_img=None,
|
|
)
|
|
# other canvas items
|
|
view.items = Struct(
|
|
bottom=None, # dummy canvas item
|
|
shade_item=None,
|
|
)
|
|
# text items
|
|
view.texts = Struct(
|
|
ncards=None, # canvas item
|
|
# by default only used by Talon:
|
|
rounds=None, # canvas item
|
|
redeal=None, # canvas item
|
|
redeal_str=None, # the corresponding string
|
|
# for use by derived stacks:
|
|
misc=None, # canvas item
|
|
)
|
|
view.top_bottom = None # the highest of all bottom items
|
|
cardw, cardh = game.app.images.CARDW, game.app.images.CARDH
|
|
dx, dy = cardw+view.canvas.xmargin, cardh+view.canvas.ymargin
|
|
view.is_visible = view.x >= -dx and view.y >= -dy
|
|
view.is_open = -1
|
|
view.can_hide_cards = -1
|
|
view.max_shadow_cards = -1
|
|
view.current_cursor = ''
|
|
view.cursor_changed = False
|
|
|
|
def destruct(self):
|
|
# help breaking circular references
|
|
unbind_destroy(self.group)
|
|
|
|
def prepareStack(self):
|
|
self.prepareView()
|
|
if self.is_visible:
|
|
self.initBindings()
|
|
|
|
def _calcMouseBind(self, binding_format):
|
|
return self.game.app.opt.calcCustomMouseButtonsBinding(binding_format)
|
|
|
|
# bindings {view widgets bind to controller}
|
|
def initBindings(self):
|
|
group = self.group
|
|
bind(group, self._calcMouseBind("<{mouse_button1}>"),
|
|
self.__clickEventHandler)
|
|
# bind(group, "<B1-Motion>", self.__motionEventHandler)
|
|
bind(group, "<Motion>", self.__motionEventHandler)
|
|
bind(group, self._calcMouseBind("<ButtonRelease-{mouse_button1}>"),
|
|
self.__releaseEventHandler)
|
|
bind(group, self._calcMouseBind("<Control-{mouse_button1}>"),
|
|
self.__controlclickEventHandler)
|
|
bind(group, self._calcMouseBind("<Shift-{mouse_button1}>"),
|
|
self.__shiftclickEventHandler)
|
|
bind(group, self._calcMouseBind("<Double-{mouse_button1}>"),
|
|
self.__doubleclickEventHandler)
|
|
bind(group, self._calcMouseBind("<{mouse_button3}>"),
|
|
self.__rightclickEventHandler)
|
|
bind(group, self._calcMouseBind("<{mouse_button2}>"),
|
|
self.__middleclickEventHandler)
|
|
bind(group, self._calcMouseBind("<Control-{mouse_button3}>"),
|
|
self.__middleclickEventHandler)
|
|
# bind(group, self._calcMouseBind(
|
|
# "<Control-{mouse_button2}>"), self.__controlmiddleclickEventHandler)
|
|
# bind(group, self._calcMouseBind("<Shift-{mouse_button3}>"),
|
|
# self.__shiftrightclickEventHandler)
|
|
# bind(group, self._calcMouseBind("<Double-{mouse_button2}>"), "")
|
|
bind(group, "<Enter>", self.__enterEventHandler)
|
|
bind(group, "<Leave>", self.__leaveEventHandler)
|
|
|
|
def prepareView(self):
|
|
# assertView(self)
|
|
if (self.CARD_XOFFSET == 0 and self.CARD_YOFFSET == 0):
|
|
assert self.cap.max_move <= 1
|
|
# prepare some variables
|
|
ox, oy = self.CARD_XOFFSET, self.CARD_YOFFSET
|
|
if isinstance(ox, (int, float)):
|
|
self.CARD_XOFFSET = (ox,)
|
|
else:
|
|
self.CARD_XOFFSET = tuple([int(round(x)) for x in ox])
|
|
if isinstance(oy, (int, float)):
|
|
self.CARD_YOFFSET = (oy,)
|
|
else:
|
|
self.CARD_YOFFSET = tuple([int(round(y)) for y in oy])
|
|
|
|
# preserve offsets
|
|
# for resize()
|
|
self.INIT_CARD_OFFSETS = (self.CARD_XOFFSET, self.CARD_YOFFSET)
|
|
self.INIT_CARD_YOFFSET = self.CARD_YOFFSET # for reallocateCards
|
|
|
|
if self.can_hide_cards < 0:
|
|
self.can_hide_cards = self.is_visible
|
|
if self.cap.max_cards < 3:
|
|
self.can_hide_cards = 0
|
|
elif [_f for _f in self.CARD_XOFFSET if _f]:
|
|
self.can_hide_cards = 0
|
|
elif [_f for _f in self.CARD_YOFFSET if _f]:
|
|
self.can_hide_cards = 0
|
|
elif self.canvas.preview:
|
|
self.can_hide_cards = 0
|
|
if self.is_open < 0:
|
|
self.is_open = False
|
|
if (self.is_visible and
|
|
(abs(self.CARD_XOFFSET[0]) >= self.MIN_VISIBLE_XOFFSET or
|
|
abs(self.CARD_YOFFSET[0]) >= self.MIN_VISIBLE_YOFFSET)):
|
|
self.is_open = True
|
|
if self.max_shadow_cards < 0:
|
|
self.max_shadow_cards = 999999
|
|
# if abs(self.CARD_YOFFSET[0])
|
|
# != self.game.app.images.CARD_YOFFSET:
|
|
# # don't display a shadow if the YOFFSET of the stack
|
|
# # and the images don't match
|
|
# self.max_shadow_cards = 1
|
|
if (self.game.app.opt.shrink_face_down and
|
|
isinstance(ox, (int, float)) and
|
|
isinstance(oy, (int, float))):
|
|
# no shrink if xoffset/yoffset too small
|
|
f = self.SHRINK_FACTOR
|
|
if ((ox == 0 and oy >= self.game.app.images.CARD_YOFFSET//f) or
|
|
(oy == 0 and
|
|
ox >= self.game.app.images.CARD_XOFFSET//f)):
|
|
self.shrink_face_down = f
|
|
# bottom image
|
|
if self.is_visible:
|
|
self.prepareBottom()
|
|
|
|
# stack bottom image
|
|
def prepareBottom(self):
|
|
assert self.is_visible and self.images.bottom is None
|
|
img = self.getBottomImage()
|
|
if img is not None:
|
|
self.images.bottom = MfxCanvasImage(self.canvas, self.x, self.y,
|
|
image=img, anchor=ANCHOR_NW,
|
|
group=self.group)
|
|
self.top_bottom = self.images.bottom
|
|
|
|
# invisible stack bottom
|
|
# We need this if we want to get any events for an empty stack (which
|
|
# is needed by the quickPlayHandler in some games like Montana)
|
|
def prepareInvisibleBottom(self):
|
|
assert self.is_visible and self.items.bottom is None
|
|
images = self.game.app.images
|
|
self.items.bottom = MfxCanvasRectangle(self.canvas, self.x, self.y,
|
|
self.x + images.CARDW,
|
|
self.y + images.CARDH,
|
|
fill="", outline="", width=0,
|
|
group=self.group)
|
|
self.top_bottom = self.items.bottom
|
|
|
|
# sanity checks
|
|
def assertStack(self):
|
|
assert self.cap.min_move > 0
|
|
assert self.cap.min_accept > 0
|
|
assert not hasattr(self, "suit")
|
|
|
|
#
|
|
# Core access methods {model -> view}
|
|
#
|
|
|
|
# Add a card add the top of a stack. Also update display. {model -> view}
|
|
def addCard(self, card, unhide=1, update=1):
|
|
model, view = self, self
|
|
model.cards.append(card)
|
|
card.tkraise(unhide=unhide)
|
|
if view.can_hide_cards and len(model.cards) >= 3:
|
|
# we only need to display the 2 top cards
|
|
model.cards[-3].hide(self)
|
|
card.item.addtag(view.group)
|
|
view._position(card)
|
|
if update:
|
|
view.updateText()
|
|
self.closeStack()
|
|
return card
|
|
|
|
def insertCard(self, card, position, unhide=1, update=1):
|
|
model, view = self, self
|
|
model.cards.insert(position, card)
|
|
for c in model.cards[position:]:
|
|
c.tkraise(unhide=unhide)
|
|
if (view.can_hide_cards and len(model.cards) >= 3 and
|
|
len(model.cards)-position <= 2):
|
|
# we only need to display the 2 top cards
|
|
model.cards[-3].hide(self)
|
|
card.item.addtag(view.group)
|
|
for c in model.cards[position:]:
|
|
view._position(c)
|
|
if update:
|
|
view.updateText()
|
|
self.closeStack()
|
|
return card
|
|
|
|
# Remove a card from the stack. Also update display. {model -> view}
|
|
def removeCard(self, card=None, unhide=1, update=1, update_positions=0):
|
|
model, view = self, self
|
|
assert len(model.cards) > 0
|
|
if card is None:
|
|
card = model.cards[-1]
|
|
# optimized a little bit (compare with the else below)
|
|
card.item.dtag(view.group)
|
|
if unhide and self.can_hide_cards:
|
|
card.unhide()
|
|
if len(self.cards) >= 3:
|
|
model.cards[-3].unhide()
|
|
del model.cards[-1]
|
|
else:
|
|
card.item.dtag(view.group)
|
|
if unhide and view.can_hide_cards:
|
|
# Note: the 2 top cards ([-1] and [-2]) are already unhidden.
|
|
card.unhide()
|
|
if len(model.cards) >= 3:
|
|
if card is model.cards[-1] or model is self.cards[-2]:
|
|
# Make sure that 2 top cards will be un-hidden.
|
|
model.cards[-3].unhide()
|
|
card_index = model.cards.index(card)
|
|
model.cards.remove(card)
|
|
if update_positions:
|
|
for c in model.cards[card_index:]:
|
|
view._position(c)
|
|
|
|
if update:
|
|
view.updateText()
|
|
self.unshadeStack()
|
|
self.is_filled = False
|
|
return card
|
|
|
|
# Get the top card {model}
|
|
def getCard(self):
|
|
if self.cards:
|
|
return self.cards[-1]
|
|
return None
|
|
|
|
# get the largest moveable pile {model} - uses canMoveCards()
|
|
def getPile(self):
|
|
if self.cap.max_move > 0:
|
|
cards = self.cards[-self.cap.max_move:]
|
|
while len(cards) >= self.cap.min_move:
|
|
if self.canMoveCards(cards):
|
|
return cards
|
|
del cards[0]
|
|
return None
|
|
|
|
# Position the card on the canvas {view}
|
|
def _position(self, card):
|
|
x, y = self.getPositionFor(card)
|
|
card.moveTo(x, y)
|
|
|
|
# find card
|
|
def _findCard(self, event):
|
|
model, view = self, self
|
|
if event is not None and model.cards:
|
|
# ask the canvas
|
|
return view.canvas.findCard(self, event)
|
|
return -1
|
|
|
|
# find card
|
|
def _findCardXY(self, x, y, cards=None):
|
|
model = self
|
|
if cards is None:
|
|
cards = model.cards
|
|
images = self.game.app.images
|
|
cw, ch = images.getSize()
|
|
index = -1
|
|
for i in range(len(cards)):
|
|
c = cards[i]
|
|
r = (c.x, c.y, c.x + cw, c.y + ch)
|
|
if r[0] <= x < r[2] and r[1] <= y < r[3]:
|
|
index = i
|
|
return index
|
|
|
|
# generic model update (can be used for undo/redo - see move.py)
|
|
def updateModel(self, undo, flags):
|
|
pass
|
|
|
|
# copy model data - see Hint.AClonedStack
|
|
def copyModel(self, clone):
|
|
clone.id = self.id
|
|
clone.game = self.game
|
|
clone.cap = self.cap
|
|
|
|
def getRankDir(self, cards=None):
|
|
if cards is None:
|
|
cards = self.cards[-2:]
|
|
if len(cards) < 2:
|
|
return 0
|
|
dir = (cards[-1].rank - cards[-2].rank) % self.cap.mod
|
|
if dir > self.cap.mod // 2:
|
|
return dir - self.cap.mod
|
|
return dir
|
|
|
|
#
|
|
# Basic capabilities {model}
|
|
# Used by various subclasses.
|
|
#
|
|
|
|
def basicIsBlocked(self):
|
|
# Check if the stack is blocked (e.g. Pyramid or Mahjongg)
|
|
return False
|
|
|
|
def basicAcceptsCards(self, from_stack, cards):
|
|
# Check that the limits are ok and that the cards are face up
|
|
if from_stack is self or self.basicIsBlocked():
|
|
return False
|
|
cap = self.cap
|
|
mylen = len(cards)
|
|
if mylen < cap.min_accept or mylen > cap.max_accept:
|
|
return False
|
|
mylen += len(self.cards)
|
|
# note: we don't check cap.min_cards here
|
|
if mylen > cap.max_cards:
|
|
return False
|
|
|
|
def _check(c, suit, color, rank):
|
|
return ((suit >= 0 and c.suit != suit) or
|
|
(color >= 0 and c.color != color) or
|
|
(rank >= 0 and c.rank != rank))
|
|
for c in cards:
|
|
if not c.face_up or _check(c, cap.suit, cap.color, cap.rank):
|
|
return False
|
|
if self.cards:
|
|
# top card of our stack must be face up
|
|
return self.cards[-1].face_up
|
|
# check required base
|
|
return not _check(cards[0], cap.base_suit, cap.base_color,
|
|
cap.base_rank)
|
|
|
|
def basicCanMoveCards(self, cards):
|
|
# Check that the limits are ok and the cards are face up
|
|
if self.basicIsBlocked():
|
|
return False
|
|
cap = self.cap
|
|
mylen = len(cards)
|
|
if mylen < cap.min_move or mylen > cap.max_move:
|
|
return False
|
|
mylen = len(self.cards) - mylen
|
|
# note: we don't check cap.max_cards here
|
|
if mylen < cap.min_cards:
|
|
return False
|
|
return cardsFaceUp(cards)
|
|
|
|
#
|
|
# Capabilities - important for game logic {model}
|
|
#
|
|
|
|
def acceptsCards(self, from_stack, cards):
|
|
# Do we accept receiving `cards' from `from_stack' ?
|
|
return False
|
|
|
|
def canMoveCards(self, cards):
|
|
# Can we move these cards when assuming they are our top-cards ?
|
|
return False
|
|
|
|
def canFlipCard(self):
|
|
# Can we flip our top card ?
|
|
return False
|
|
|
|
def canDropCards(self, stacks):
|
|
# Can we drop the top cards onto one of the foundation stacks ?
|
|
return (None, 0) # return the stack and the number of cards
|
|
|
|
#
|
|
# State {model}
|
|
#
|
|
|
|
def resetGame(self):
|
|
# Called when starting a new game.
|
|
self.CARD_YOFFSET = self.INIT_CARD_YOFFSET
|
|
self.items.shade_item = None
|
|
self.images.shade_img = None
|
|
# self.items.bottom = None
|
|
# self.images.bottom = None
|
|
|
|
def __repr__(self):
|
|
# Return a string for debug print statements.
|
|
return "%s(%d)" % (self.__class__.__name__, self.id)
|
|
|
|
#
|
|
# Atomic move actions {model -> view}
|
|
#
|
|
|
|
def flipMove(self, animation=False):
|
|
# Flip the top card.
|
|
if animation:
|
|
self.game.singleFlipMove(self)
|
|
else:
|
|
self.game.flipMove(self)
|
|
|
|
def moveMove(self, ncards, to_stack, frames=-1, shadow=-1):
|
|
# Move the top n cards.
|
|
self.game.moveMove(
|
|
ncards, self, to_stack, frames=frames, shadow=shadow)
|
|
self.fillStack()
|
|
|
|
def fillStack(self):
|
|
self.game.fillStack(self)
|
|
|
|
def closeStack(self):
|
|
pass
|
|
|
|
#
|
|
# Playing move actions. Better not override.
|
|
#
|
|
|
|
def playFlipMove(self, sound=True, animation=False):
|
|
if sound:
|
|
self.game.playSample("flip", 5)
|
|
self.flipMove(animation=animation)
|
|
if not self.game.checkForWin():
|
|
self.game.autoPlay()
|
|
self.game.finishMove()
|
|
|
|
def playMoveMove(self, ncards, to_stack, frames=-1, shadow=-1, sound=True):
|
|
if sound:
|
|
if to_stack in self.game.s.foundations:
|
|
self.game.playSample("drop", priority=30)
|
|
else:
|
|
self.game.playSample("move", priority=10)
|
|
self.moveMove(ncards, to_stack, frames=frames, shadow=shadow)
|
|
if not self.game.checkForWin():
|
|
# let the player put cards back from the foundations
|
|
if self not in self.game.s.foundations:
|
|
self.game.autoPlay()
|
|
self.game.finishMove()
|
|
|
|
#
|
|
# Appearance {view}
|
|
#
|
|
|
|
def _getBlankBottomImage(self):
|
|
return self.game.app.images.getBlankBottom()
|
|
|
|
def _getReserveBottomImage(self):
|
|
return self.game.app.images.getReserveBottom()
|
|
|
|
def _getSuitBottomImage(self):
|
|
return self.game.app.images.getSuitBottom(self.cap.base_suit)
|
|
|
|
def _getNoneBottomImage(self):
|
|
return None
|
|
|
|
def _getTalonBottomImage(self):
|
|
return self.game.app.images.getTalonBottom()
|
|
|
|
def _getBraidBottomImage(self):
|
|
return self.game.app.images.getBraidBottom()
|
|
|
|
def _getLetterImage(self):
|
|
return self.game.app.images.getLetter(self.cap.base_rank)
|
|
|
|
getBottomImage = _getBlankBottomImage
|
|
|
|
def getPositionFor(self, card):
|
|
model, view = self, self
|
|
x, y = view.x, view.y
|
|
if view.can_hide_cards:
|
|
return x, y
|
|
ix, iy, lx, ly = 0, 0, len(view.CARD_XOFFSET), len(view.CARD_YOFFSET)
|
|
d = self.shrink_face_down
|
|
for c in model.cards:
|
|
if c is card:
|
|
break
|
|
if c.face_up:
|
|
x += self.CARD_XOFFSET[ix]
|
|
y += self.CARD_YOFFSET[iy]
|
|
else:
|
|
x += self.CARD_XOFFSET[ix]//d
|
|
y += self.CARD_YOFFSET[iy]//d
|
|
ix = (ix + 1) % lx
|
|
iy = (iy + 1) % ly
|
|
return int(x), int(y)
|
|
|
|
def getPositionForNextCard(self):
|
|
model, view = self, self
|
|
x, y = view.x, view.y
|
|
if view.can_hide_cards:
|
|
return x, y
|
|
if not self.cards:
|
|
return x, y
|
|
ix, iy, lx, ly = 0, 0, len(view.CARD_XOFFSET), len(view.CARD_YOFFSET)
|
|
d = self.shrink_face_down
|
|
for c in model.cards:
|
|
if c.face_up:
|
|
x += self.CARD_XOFFSET[ix]
|
|
y += self.CARD_YOFFSET[iy]
|
|
else:
|
|
x += self.CARD_XOFFSET[ix]//d
|
|
y += self.CARD_YOFFSET[iy]//d
|
|
ix = (ix + 1) % lx
|
|
iy = (iy + 1) % ly
|
|
return int(x), int(y)
|
|
|
|
def getOffsetFor(self, card):
|
|
model, view = self, self
|
|
if view.can_hide_cards:
|
|
return 0, 0
|
|
lx, ly = len(view.CARD_XOFFSET), len(view.CARD_YOFFSET)
|
|
i = list(model.cards).index(card)
|
|
return view.CARD_XOFFSET[i % lx], view.CARD_YOFFSET[i % ly]
|
|
|
|
# Fully update the view of a stack - updates
|
|
# hiding, card positions and stacking order.
|
|
# Avoid calling this as it is rather slow.
|
|
def refreshView(self):
|
|
model, view = self, self
|
|
cards = model.cards
|
|
if not view.is_visible or len(cards) < 2:
|
|
return
|
|
if view.can_hide_cards:
|
|
# hide all lower cards
|
|
for c in cards[:-2]:
|
|
# print "refresh hide", c, c.hide_stack
|
|
c.hide(self)
|
|
# unhide the 2 top cards
|
|
for c in cards[-2:]:
|
|
# print "refresh unhide 1", c, c.hide_stack
|
|
c.unhide()
|
|
# print "refresh unhide 1", c, c.hide_stack
|
|
# update the card postions and stacking order
|
|
item = cards[0].item
|
|
x, y = view.x, view.y
|
|
ix, iy, lx, ly = 0, 0, len(view.CARD_XOFFSET), len(view.CARD_YOFFSET)
|
|
for c in cards[1:]:
|
|
c.item.tkraise(item)
|
|
item = c.item
|
|
if not view.can_hide_cards:
|
|
d = self.shrink_face_down
|
|
if c.face_up:
|
|
x += self.CARD_XOFFSET[ix]
|
|
y += self.CARD_YOFFSET[iy]
|
|
else:
|
|
x += int(self.CARD_XOFFSET[ix]/d)
|
|
y += int(self.CARD_YOFFSET[iy]/d)
|
|
ix = (ix + 1) % lx
|
|
iy = (iy + 1) % ly
|
|
c.moveTo(x, y)
|
|
|
|
def updateText(self):
|
|
if self.game.preview > 1 or self.texts.ncards is None:
|
|
return
|
|
t = ""
|
|
format = "%d"
|
|
if self.texts.ncards.text_format is not None:
|
|
format = self.texts.ncards.text_format
|
|
if format == "%D":
|
|
format = ""
|
|
if self.cards:
|
|
format = "%d"
|
|
if format:
|
|
t = format % len(self.cards)
|
|
# if 0:
|
|
# visible = 0
|
|
# for c in self.cards:
|
|
# if c.isHidden():
|
|
# assert c.hide_stack is not None
|
|
# else:
|
|
# visible = visible + 1
|
|
# assert c.hide_stack is None
|
|
# t = t + " (%d)" % visible
|
|
self.texts.ncards.config(text=t)
|
|
|
|
def updatePositions(self):
|
|
# compact the stack when a cards goes off screen
|
|
if self.reallocateCards():
|
|
for c in self.cards:
|
|
self._position(c)
|
|
|
|
def reallocateCards(self):
|
|
# change CARD_YOFFSET if a cards is off-screen
|
|
# returned False if CARD_YOFFSET is not changed, otherwise True
|
|
if not self.game.app.opt.compact_stacks:
|
|
return False
|
|
if TOOLKIT != 'tk':
|
|
return False
|
|
if self.CARD_XOFFSET != (0,):
|
|
return False
|
|
if len(self.CARD_YOFFSET) != 1:
|
|
return False
|
|
if self.CARD_YOFFSET[0] <= 0:
|
|
return False
|
|
if len(self.cards) <= 1:
|
|
return False
|
|
if not self.canvas.winfo_ismapped():
|
|
return False
|
|
yoffset = self.CARD_YOFFSET[0]
|
|
# 1/2 of a card is visible
|
|
cardh = self.game.app.images.getSize()[0] // 2
|
|
num_face_up = len([c for c in self.cards if c.face_up])
|
|
num_face_down = len(self.cards) - num_face_up
|
|
stack_height = int(self.y +
|
|
num_face_down * yoffset // self.shrink_face_down +
|
|
num_face_up * yoffset +
|
|
cardh)
|
|
visible_height = self.canvas.winfo_height()
|
|
if USE_PIL and self.game.app.opt.auto_scale:
|
|
# use visible_height only
|
|
game_height = 0
|
|
else:
|
|
game_height = self.game.height + 2*self.canvas.ymargin
|
|
height = max(visible_height, game_height)
|
|
# print 'reallocateCards:', stack_height, height, \
|
|
# visible_height, game_height
|
|
if stack_height > height:
|
|
# compact stack
|
|
n = num_face_down // self.shrink_face_down + num_face_up
|
|
dy = float(height - self.y - cardh) / n
|
|
if dy < yoffset:
|
|
# print 'compact:', dy
|
|
self.CARD_YOFFSET = (dy,)
|
|
return True
|
|
elif stack_height < height:
|
|
# expande stack
|
|
if self.CARD_YOFFSET == self.INIT_CARD_YOFFSET:
|
|
return False
|
|
n = num_face_down // self.shrink_face_down + num_face_up
|
|
dy = float(height - self.y - cardh) / n
|
|
dy = min(dy, self.INIT_CARD_YOFFSET[0])
|
|
# print 'expande:', dy
|
|
self.CARD_YOFFSET = (dy,)
|
|
return True
|
|
return False
|
|
|
|
def resize(self, xf, yf, widthpad=0, heightpad=0):
|
|
# resize and move stack
|
|
# xf, yf - a multiplicative factor (from the original values)
|
|
# print 'Stack.resize:', self, self.is_visible, xf, yf
|
|
x0, y0 = self.init_coord
|
|
if (x0 > 0):
|
|
x0 += widthpad
|
|
if (y0 > 0):
|
|
y0 += heightpad
|
|
x, y = int(round(x0*xf)), int(round(y0*yf))
|
|
self.x, self.y = x, y
|
|
# offsets
|
|
xoffset = tuple(int(round(i*xf)) for i in self.INIT_CARD_OFFSETS[0])
|
|
yoffset = tuple(int(round(i*yf)) for i in self.INIT_CARD_OFFSETS[1])
|
|
self.CARD_XOFFSET = xoffset
|
|
self.CARD_YOFFSET = yoffset
|
|
self.INIT_CARD_YOFFSET = yoffset
|
|
# print '* resize offset:', self.INIT_CARD_XOFFSET,
|
|
# move cards
|
|
for c in self.cards:
|
|
cx, cy = self.getPositionFor(c)
|
|
c.moveTo(cx, cy)
|
|
# ---
|
|
if not self.is_visible:
|
|
return
|
|
# bottom and shade
|
|
if self.images.bottom:
|
|
img = self.getBottomImage()
|
|
self.images.bottom['image'] = img
|
|
self.images.bottom.moveTo(x, y)
|
|
if self.items.bottom:
|
|
c = self.items.bottom.coords()
|
|
c = ((int(round(c[0]*xf)), int(round(c[1]*yf))),
|
|
(int(round(c[2]*xf)), int(round(c[3]*yf))))
|
|
self.items.bottom.coords(c)
|
|
if self.items.shade_item:
|
|
c = self.cards[-1]
|
|
img = self.game.app.images.getHighlightedCard(
|
|
c.deck, c.suit, c.rank)
|
|
if img:
|
|
self.items.shade_item['image'] = img
|
|
self.items.shade_item.moveTo(x, y)
|
|
|
|
# move the items
|
|
def move(item):
|
|
ix, iy = item.init_coord
|
|
x = int(round((ix + widthpad) * xf))
|
|
y = int(round((iy + heightpad) * yf))
|
|
item.moveTo(x, y)
|
|
# images
|
|
if self.images.redeal:
|
|
move(self.images.redeal)
|
|
# texts
|
|
if self.texts.ncards:
|
|
move(self.texts.ncards)
|
|
if self.texts.rounds:
|
|
move(self.texts.rounds)
|
|
if self.texts.redeal:
|
|
move(self.texts.redeal)
|
|
if self.texts.misc:
|
|
move(self.texts.misc)
|
|
|
|
def basicShallHighlightSameRank(self, card):
|
|
# by default all open stacks are available for highlighting
|
|
assert card in self.cards
|
|
if not self.is_visible or not card.face_up:
|
|
return False
|
|
if card is self.cards[-1]:
|
|
return True
|
|
if not self.is_open:
|
|
return False
|
|
# dx, dy = self.getOffsetFor(card)
|
|
# if ((dx == 0 and dy <= self.MIN_VISIBLE_XOFFSET) or
|
|
# (dx <= self.MIN_VISIBLE_YOFFSET and dy == 0)):
|
|
# return False
|
|
return True
|
|
|
|
def basicShallHighlightMatch(self, card):
|
|
# by default all open stacks are available for highlighting
|
|
return self.basicShallHighlightSameRank(card)
|
|
|
|
def highlightSameRank(self, event):
|
|
i = self._findCard(event)
|
|
if i < 0:
|
|
return 0
|
|
card = self.cards[i]
|
|
if not self.basicShallHighlightSameRank(card):
|
|
return 0
|
|
col_1 = self.game.app.opt.colors['samerank_1']
|
|
col_2 = self.game.app.opt.colors['samerank_2']
|
|
info = [(self, card, card, col_1)]
|
|
for s in self.game.allstacks:
|
|
for c in s.cards:
|
|
if c is card:
|
|
continue
|
|
# check the rank
|
|
if c.rank != card.rank:
|
|
continue
|
|
# ask the target stack
|
|
if s.basicShallHighlightSameRank(c):
|
|
info.append((s, c, c, col_2))
|
|
self.game.stats.highlight_samerank += 1
|
|
return self.game._highlightCards(
|
|
info, self.game.app.opt.timeouts['highlight_samerank'])
|
|
|
|
def highlightMatchingCards(self, event):
|
|
i = self._findCard(event)
|
|
if i < 0:
|
|
return 0
|
|
card = self.cards[i]
|
|
if not self.basicShallHighlightMatch(card):
|
|
return 0
|
|
col_1 = self.game.app.opt.colors['cards_1']
|
|
col_2 = self.game.app.opt.colors['cards_2']
|
|
c1 = c2 = card
|
|
info = []
|
|
found = 0
|
|
for s in self.game.allstacks:
|
|
# continue if both stacks are foundations
|
|
if (self in self.game.s.foundations and
|
|
s in self.game.s.foundations):
|
|
continue
|
|
# for all cards
|
|
for c in s.cards:
|
|
if c is card:
|
|
continue
|
|
# ask the target stack
|
|
if not s.basicShallHighlightMatch(c):
|
|
continue
|
|
# ask the game
|
|
if self.game.shallHighlightMatch(self, card, s, c):
|
|
found = 1
|
|
if s is self:
|
|
# enlarge rectangle for neighbours
|
|
j = self.cards.index(c)
|
|
if i - 1 == j:
|
|
c1 = c
|
|
continue
|
|
if i + 1 == j:
|
|
c2 = c
|
|
continue
|
|
info.append((s, c, c, col_1))
|
|
if found:
|
|
if info:
|
|
self.game.stats.highlight_cards += 1
|
|
info.append((self, c1, c2, col_2))
|
|
return self.game._highlightCards(
|
|
info, self.game.app.opt.timeouts['highlight_cards'])
|
|
if not self.basicIsBlocked():
|
|
self.game.highlightNotMatching()
|
|
return 0
|
|
|
|
#
|
|
# Subclass overridable handlers {contoller -> model -> view}
|
|
#
|
|
|
|
def clickHandler(self, event):
|
|
return 0
|
|
|
|
def middleclickHandler(self, event):
|
|
# default action: show the card if it is overlapped by other cards
|
|
if not self.is_open:
|
|
return 0
|
|
i = self._findCard(event)
|
|
positions = len(self.cards) - i - 1
|
|
peeked = False
|
|
if i < 0:
|
|
return 0
|
|
if not self.cards[i].face_up:
|
|
if not self.game.app.opt.peek_facedown:
|
|
return 0
|
|
else:
|
|
self.game.stats.peeks += 1
|
|
self.cards[i].showFace()
|
|
peeked = True
|
|
elif positions <= 0:
|
|
return 0
|
|
# print self.cards[i]
|
|
self.cards[i].item.tkraise()
|
|
self.canvas.update_idletasks()
|
|
self.game.sleep(self.game.app.opt.timeouts['raise_card'])
|
|
if peeked:
|
|
self.cards[i].showBack()
|
|
if positions > 0:
|
|
if TOOLKIT == 'tk':
|
|
self.cards[i].item.lower(self.cards[i+1].item)
|
|
elif TOOLKIT == 'gtk':
|
|
for c in self.cards[i+1:]:
|
|
c.tkraise()
|
|
|
|
self.canvas.update_idletasks()
|
|
return 1
|
|
|
|
def controlmiddleclickHandler(self, event):
|
|
# cheating: show face-down card
|
|
if not self.is_open:
|
|
return 0
|
|
i = self._findCard(event)
|
|
positions = len(self.cards) - i - 1
|
|
if i < 0 or positions < 0:
|
|
return 0
|
|
# print self.cards[i]
|
|
face_up = self.cards[i].face_up
|
|
if not face_up:
|
|
self.cards[i].showFace()
|
|
self.cards[i].item.tkraise()
|
|
self.canvas.update_idletasks()
|
|
self.game.sleep(self.game.app.opt.timeouts['raise_card'])
|
|
if not face_up:
|
|
self.cards[i].showBack()
|
|
if TOOLKIT == 'tk':
|
|
if positions > 0:
|
|
self.cards[i].item.lower(self.cards[i+1].item)
|
|
elif TOOLKIT == 'gtk':
|
|
for c in self.cards[i+1:]:
|
|
c.tkraise()
|
|
self.canvas.update_idletasks()
|
|
return 1
|
|
|
|
def rightclickHandler(self, event):
|
|
return 0
|
|
|
|
def doubleclickHandler(self, event):
|
|
return self.clickHandler(event)
|
|
|
|
def controlclickHandler(self, event):
|
|
return 0
|
|
|
|
def shiftclickHandler(self, event):
|
|
# default action: highlight all cards of the same rank
|
|
if self.game.app.opt.highlight_samerank:
|
|
return self.highlightSameRank(event)
|
|
return 0
|
|
|
|
def shiftrightclickHandler(self, event):
|
|
return 0
|
|
|
|
def releaseHandler(self, event, drag, sound=True):
|
|
# default action: move cards back to their origin position
|
|
if drag.cards:
|
|
if sound:
|
|
self.game.playSample("nomove")
|
|
if self.game.app.opt.mouse_type == 'point-n-click':
|
|
drag.stack.moveCardsBackHandler(event, drag)
|
|
else:
|
|
self.moveCardsBackHandler(event, drag)
|
|
|
|
def moveCardsBackHandler(self, event, drag):
|
|
if self.game.app.opt.animations:
|
|
if drag.cards:
|
|
c = drag.cards[0]
|
|
x0, y0 = drag.stack.getPositionFor(c)
|
|
x1, y1 = c.x, c.y
|
|
dx, dy = abs(x0-x1), abs(y0-y1)
|
|
w, h = self.game.app.images.getSize()
|
|
if dx > 2*w or dy > 2*h:
|
|
self.game.animatedMoveTo(drag.stack, drag.stack,
|
|
drag.cards, x0, y0, frames=-1)
|
|
elif dx > w or dy > h:
|
|
self.game.animatedMoveTo(drag.stack, drag.stack,
|
|
drag.cards, x0, y0, frames=4)
|
|
for card in drag.cards:
|
|
self._position(card)
|
|
if self.is_filled and self.items.shade_item:
|
|
self.items.shade_item.show()
|
|
self.items.shade_item.tkraise()
|
|
|
|
#
|
|
# Event handlers {controller}
|
|
#
|
|
|
|
def __defaultClickEventHandler(self, event, handler,
|
|
start_drag=0, cancel_drag=1):
|
|
self.game.event_handled = True # for Game.undoHandler
|
|
if self.game.demo:
|
|
self.game.stopDemo(event)
|
|
return EVENT_HANDLED
|
|
self.game.interruptSleep()
|
|
if self.game.busy:
|
|
return EVENT_HANDLED
|
|
if self.game.drag.stack and cancel_drag:
|
|
# in case we lost an event
|
|
self.game.drag.stack.cancelDrag(event)
|
|
if start_drag:
|
|
# this handler may start a drag operation
|
|
r = handler(event)
|
|
if r is not None and r <= 0:
|
|
sound = r == 0
|
|
self.startDrag(event, sound=sound)
|
|
else:
|
|
handler(event)
|
|
return EVENT_HANDLED
|
|
|
|
if (TOOLKIT == 'kivy'):
|
|
def _motionEventHandler(self, event):
|
|
return self.__motionEventHandler(event)
|
|
|
|
def __clickEventHandler(self, event):
|
|
if self.game.app.opt.mouse_type == 'drag-n-drop':
|
|
cancel_drag = 1
|
|
start_drag = 1
|
|
handler = self.clickHandler
|
|
else: # sticky-mouse or point-n-click
|
|
cancel_drag = 0
|
|
start_drag = not self.game.drag.stack
|
|
if start_drag:
|
|
handler = self.clickHandler
|
|
else:
|
|
handler = self.finishDrag
|
|
return self.__defaultClickEventHandler(
|
|
event, handler, start_drag, cancel_drag)
|
|
|
|
def __doubleclickEventHandler(self, event):
|
|
return self.__defaultClickEventHandler(event, self.doubleclickHandler)
|
|
|
|
def __middleclickEventHandler(self, event):
|
|
return self.__defaultClickEventHandler(event, self.middleclickHandler)
|
|
|
|
def __controlmiddleclickEventHandler(self, event):
|
|
return self.__defaultClickEventHandler(
|
|
event, self.controlmiddleclickHandler)
|
|
|
|
def __rightclickEventHandler(self, event):
|
|
return self.__defaultClickEventHandler(event, self.rightclickHandler)
|
|
|
|
def __controlclickEventHandler(self, event):
|
|
return self.__defaultClickEventHandler(event, self.controlclickHandler)
|
|
|
|
def __shiftclickEventHandler(self, event):
|
|
return self.__defaultClickEventHandler(event, self.shiftclickHandler)
|
|
|
|
def __shiftrightclickEventHandler(self, event):
|
|
return self.__defaultClickEventHandler(
|
|
event, self.shiftrightclickHandler)
|
|
|
|
def __motionEventHandler(self, event):
|
|
if not self.game.drag.stack or self is not self.game.drag.stack:
|
|
return EVENT_PROPAGATE
|
|
if self.game.demo:
|
|
self.game.stopDemo(event)
|
|
if self.game.busy:
|
|
return EVENT_HANDLED
|
|
if self.game.app.opt.mouse_type == 'point-n-click':
|
|
return EVENT_HANDLED
|
|
self.keepDrag(event)
|
|
# if self.game.app.opt.mouse_type == 'drag-n-drop' \
|
|
# and TOOLKIT == 'tk':
|
|
# # use a timer to update the drag
|
|
# # this allows us to skip redraws on slow machines
|
|
# drag = self.game.drag
|
|
# if drag.timer is None:
|
|
# drag.timer = after_idle(self.canvas, self.keepDragTimer)
|
|
# drag.event = event
|
|
# else:
|
|
# # update now
|
|
# self.keepDrag(event)
|
|
return EVENT_HANDLED
|
|
|
|
def __releaseEventHandler(self, event):
|
|
if self.game.demo:
|
|
self.game.stopDemo(event)
|
|
self.game.interruptSleep()
|
|
if self.game.busy:
|
|
return EVENT_HANDLED
|
|
if self.game.app.opt.mouse_type == 'drag-n-drop':
|
|
|
|
if TOOLKIT == 'kivy':
|
|
drag = self.game.drag
|
|
if drag and drag.stack:
|
|
drag.stack.keepDrag(event)
|
|
drag.stack.finishDrag(event)
|
|
return EVENT_HANDLED
|
|
|
|
self.keepDrag(event)
|
|
self.finishDrag(event)
|
|
return EVENT_HANDLED
|
|
|
|
def __enterEventHandler(self, event):
|
|
if self.game.drag.stack:
|
|
if self.game.app.opt.mouse_type == 'point-n-click':
|
|
if self.acceptsCards(self.game.drag.stack,
|
|
self.game.drag.cards):
|
|
self.canvas.config(cursor=CURSOR_DOWN_ARROW)
|
|
self.current_cursor = CURSOR_DOWN_ARROW
|
|
self.cursor_changed = True
|
|
else:
|
|
help = self.getHelp() # +' '+self.getBaseCard(),
|
|
if DEBUG:
|
|
help = repr(self)
|
|
after_idle(self.canvas, self.game.showHelp,
|
|
'help', help,
|
|
'info', self.getNumCards())
|
|
return EVENT_HANDLED
|
|
|
|
def __leaveEventHandler(self, event):
|
|
if not self.game.drag.stack:
|
|
after_idle(self.canvas, self.game.showHelp)
|
|
if self.game.app.opt.mouse_type == 'drag-n-drop':
|
|
return EVENT_HANDLED
|
|
if self.cursor_changed:
|
|
self.canvas.config(cursor='')
|
|
self.current_cursor = ''
|
|
self.cursor_changed = False
|
|
drag_stack = self.game.drag.stack
|
|
if self is drag_stack:
|
|
x, y = event.x, event.y
|
|
w, h = self.canvas.winfo_width(), self.canvas.winfo_height()
|
|
if x < 0 or y < 0 or x >= w or y >= h:
|
|
# cancel drag if mouse leave canvas
|
|
drag_stack.cancelDrag(event)
|
|
after_idle(self.canvas, self.game.showHelp)
|
|
return EVENT_HANDLED
|
|
else:
|
|
# continue drag
|
|
return self.__motionEventHandler(event)
|
|
else:
|
|
return EVENT_PROPAGATE
|
|
|
|
#
|
|
# Drag internals {controller -> model -> view}
|
|
#
|
|
|
|
def getDragCards(self, index):
|
|
return self.cards[index:]
|
|
|
|
# begin a drag operation
|
|
def startDrag(self, event, sound=True):
|
|
# print event.x, event.y
|
|
assert self.game.drag.stack is None
|
|
# import pdb
|
|
# pdb.set_trace()
|
|
i = self._findCard(event)
|
|
if i < 0 or not self.canMoveCards(self.cards[i:]):
|
|
return
|
|
if self.is_filled and self.items.shade_item:
|
|
self.items.shade_item.hide()
|
|
x_offset, y_offset = self.cards[i].x, self.cards[i].y
|
|
if sound:
|
|
self.game.playSample("startdrag")
|
|
self.lastx = event.x
|
|
self.lasty = event.y
|
|
game = self.game
|
|
drag = game.drag
|
|
drag.start_x = event.x
|
|
drag.start_y = event.y
|
|
drag.stack = self
|
|
drag.noshade_stacks = [self]
|
|
drag.cards = self.getDragCards(i)
|
|
drag.index = i
|
|
if self.game.app.opt.mouse_type == 'point-n-click':
|
|
self._markCards(drag)
|
|
return
|
|
# if TOOLKIT == 'gtk':
|
|
# drag.stack.group.tkraise()
|
|
images = game.app.images
|
|
drag.shadows = self.createShadows(drag.cards)
|
|
# sx, sy = 0, 0
|
|
sx, sy = -images.SHADOW_XOFFSET, -images.SHADOW_YOFFSET
|
|
dx, dy = 0, 0
|
|
cw, ch = images.getSize()
|
|
if game.app.opt.mouse_type == 'sticky-mouse':
|
|
# return cards under mouse
|
|
dx = event.x - (x_offset+cw+sx) - game.canvas.xmargin
|
|
dy = event.y - (y_offset+ch+sy) - game.canvas.ymargin
|
|
if dx < 0:
|
|
dx = 0
|
|
if dy < 0:
|
|
dy = 0
|
|
for s in drag.shadows:
|
|
if dx > 0 or dy > 0:
|
|
s.move(dx, dy)
|
|
if TOOLKIT == 'gtk':
|
|
s.addtag(drag.stack.group)
|
|
s.tkraise()
|
|
for card in drag.cards:
|
|
card.tkraise()
|
|
card.moveBy(sx+dx, sy+dy)
|
|
if game.app.opt.dragcursor:
|
|
game.canvas.config(cursor=CURSOR_DRAG)
|
|
|
|
# continue a drag operation
|
|
def keepDrag(self, event):
|
|
drag = self.game.drag
|
|
if not drag.cards:
|
|
return
|
|
assert self is drag.stack
|
|
dx = event.x - self.lastx
|
|
dy = event.y - self.lasty
|
|
if dx or dy:
|
|
self.lastx = event.x
|
|
self.lasty = event.y
|
|
if self.game.app.opt.shade:
|
|
self._updateShade()
|
|
for s in drag.shadows:
|
|
s.move(dx, dy)
|
|
for card in drag.cards:
|
|
card.moveBy(dx, dy)
|
|
drag.event = None
|
|
|
|
def keepDragTimer(self):
|
|
drag = self.game.drag
|
|
after_cancel(drag.timer)
|
|
drag.timer = None
|
|
if drag.event:
|
|
self.keepDrag(drag.event)
|
|
self.canvas.update_idletasks()
|
|
|
|
# create shadows, return a tuple of MfxCanvasImages
|
|
def createShadows(self, cards, dx=0, dy=0):
|
|
if not self.game.app.opt.shadow or self.canvas.preview > 1:
|
|
return ()
|
|
mylen = len(cards)
|
|
if mylen == 0 or mylen > self.max_shadow_cards:
|
|
return ()
|
|
images = self.game.app.images
|
|
cx, cy = cards[0].x, cards[0].y
|
|
ddx, ddy = cx-cards[-1].x, cy-cards[-1].y
|
|
cw, ch = images.getSize()
|
|
if USE_PIL:
|
|
c0 = cards[-1]
|
|
if self.CARD_XOFFSET[0] < 0:
|
|
c0 = cards[0]
|
|
if self.CARD_YOFFSET[0] < 0:
|
|
c0 = cards[0]
|
|
img = images.getShadowPIL(self, cards)
|
|
cx, cy = c0.x + cw + dx, c0.y + ch + dy
|
|
s = MfxCanvasImage(self.canvas, cx, cy,
|
|
image=img, anchor=ANCHOR_SE)
|
|
s.lower(c0.item)
|
|
return (s,)
|
|
|
|
if ddx == 0: # vertical
|
|
for c in cards[1:]:
|
|
if c.x != cx or abs(c.y - cy) != images.CARD_YOFFSET:
|
|
return ()
|
|
cy = c.y
|
|
img0, img1 = images.getShadow(0), images.getShadow(mylen)
|
|
c0 = cards[-1]
|
|
if self.CARD_YOFFSET[0] < 0:
|
|
c0 = cards[0]
|
|
elif ddy == 0: # horizontal
|
|
for c in cards[1:]:
|
|
if c.y != cy or abs(c.x - cx) != images.CARD_XOFFSET:
|
|
return ()
|
|
cx = c.x
|
|
img0, img1 = images.getShadow(-mylen), images.getShadow(1)
|
|
c0 = cards[-1]
|
|
if self.CARD_XOFFSET[0] < 0:
|
|
c0 = cards[0]
|
|
else:
|
|
return ()
|
|
if img0 and img1:
|
|
cx, cy = c0.x + cw + dx, c0.y + ch + dy
|
|
|
|
if TOOLKIT == 'kivy':
|
|
height0 = img0.getHeight()
|
|
else:
|
|
height0 = img0.height()
|
|
|
|
s1 = MfxCanvasImage(self.game.canvas, cx, cy - height0,
|
|
image=img1, anchor=ANCHOR_SE)
|
|
s2 = MfxCanvasImage(self.canvas, cx, cy,
|
|
image=img0, anchor=ANCHOR_SE)
|
|
if TOOLKIT == 'tk':
|
|
s1.lower(c0.item)
|
|
s2.lower(c0.item)
|
|
# elif TOOLKIT == 'gtk':
|
|
# positions = 2 ## FIXME
|
|
# s1.lower(positions)
|
|
# s2.lower(positions)
|
|
return (s1, s2)
|
|
return ()
|
|
|
|
# handle shade within a drag operation
|
|
def _deleteShade(self):
|
|
if self.game.drag.shade_img:
|
|
self.game.drag.shade_img.delete()
|
|
self.game.drag.shade_img = None
|
|
self.game.drag.shade_stack = None
|
|
|
|
def _updateShade(self):
|
|
# optimized for speed - we use lots of local variables
|
|
game = self.game
|
|
images = game.app.images
|
|
CW, CH = images.CARDW, images.CARDH
|
|
drag = game.drag
|
|
# stacks = game.allstacks
|
|
c = drag.cards[0]
|
|
stacks = (game.getClosestStack(c, drag.stack), )
|
|
r1_0, r1_1, r1_2, r1_3 = c.x, c.y, c.x + CW, c.y + CH
|
|
sstack, sdiff, sx, sy = None, 999999999, 0, 0
|
|
for s in stacks:
|
|
if s is None or s in drag.noshade_stacks:
|
|
continue
|
|
if s.cards:
|
|
c = s.cards[-1]
|
|
r2 = (c.x, c.y, c.x + CW, c.y + CH)
|
|
else:
|
|
r2 = (s.x, s.y, s.x + CW, s.y + CH)
|
|
if (r1_2 <= r2[0] or r1_3 <= r2[1] or
|
|
r2[2] <= r1_0 or r2[3] <= r1_1):
|
|
# rectangles do not intersect
|
|
continue
|
|
if s in drag.canshade_stacks:
|
|
pass
|
|
elif s.acceptsCards(drag.stack, drag.cards):
|
|
drag.canshade_stacks.append(s)
|
|
else:
|
|
drag.noshade_stacks.append(s)
|
|
continue
|
|
diff = (r1_0 - r2[0])**2 + (r1_1 - r2[1])**2
|
|
if diff < sdiff:
|
|
sstack, sdiff, sx, sy = s, diff, r2[0], r2[1]
|
|
if sstack is drag.shade_stack:
|
|
return
|
|
if sstack is None:
|
|
self._deleteShade()
|
|
return
|
|
if drag.shade_img:
|
|
self._deleteShade()
|
|
# create the shade image
|
|
drag.shade_stack = sstack
|
|
if sstack.cards:
|
|
card = sstack.cards[-1]
|
|
if card.face_up:
|
|
img = images.getHighlightedCard(
|
|
card.deck, card.suit, card.rank)
|
|
else:
|
|
img = images.getHighlightedBack()
|
|
else:
|
|
img = images.getShade()
|
|
if not img:
|
|
return
|
|
img = MfxCanvasImage(game.canvas, sx, sy, image=img, anchor=ANCHOR_NW)
|
|
drag.shade_img = img
|
|
# raise/lower the shade image to the correct stacking order
|
|
if TOOLKIT == 'tk':
|
|
if drag.shadows:
|
|
img.lower(drag.shadows[0])
|
|
else:
|
|
img.lower(drag.cards[0].item)
|
|
elif TOOLKIT == 'gtk':
|
|
img.tkraise()
|
|
drag.stack.group.tkraise()
|
|
|
|
# for closeStack
|
|
def _shadeStack(self):
|
|
if not self.game.app.opt.shade_filled_stacks:
|
|
return
|
|
# if (self.CARD_XOFFSET != (0,) or
|
|
# self.CARD_YOFFSET != (0,)):
|
|
# return
|
|
card = self.cards[-1]
|
|
img = self.game.app.images.getHighlightedCard(
|
|
card.deck, card.suit, card.rank)
|
|
if img is None:
|
|
return
|
|
# self.canvas.update_idletasks()
|
|
if TOOLKIT == 'kivy':
|
|
self.game.top.waitAnimation()
|
|
item = MfxCanvasImage(self.canvas, card.x, card.y,
|
|
image=img, anchor=ANCHOR_NW, group=self.group)
|
|
# item.tkraise()
|
|
self.items.shade_item = item
|
|
|
|
def unshadeStack(self):
|
|
if self.items.shade_item:
|
|
self.items.shade_item.delete()
|
|
self.items.shade_item = None
|
|
|
|
def _markCards(self, drag):
|
|
cards = drag.cards
|
|
drag.stack.group.tkraise()
|
|
#
|
|
x0, y0 = self.getPositionFor(cards[0])
|
|
x1, y1 = self.getPositionFor(cards[-1])
|
|
x0, x1 = min(x1, x0), max(x1, x0)
|
|
y0, y1 = min(y1, y0), max(y1, y0)
|
|
cw, ch = self.game.app.images.getSize()
|
|
x1 += cw
|
|
y1 += ch
|
|
xx0, yy0 = x0, y0
|
|
w, h = x1-x0, y1-y0
|
|
#
|
|
if TOOLKIT == 'gtk' or not Image:
|
|
color = self.game.app.opt.colors['cards_1']
|
|
r = MfxCanvasRectangle(self.canvas, xx0, yy0, xx0+w, yy0+h,
|
|
fill="", outline=color, width=4,
|
|
group=self.group)
|
|
drag.shadows.append(r)
|
|
# mylen = MfxCanvasLine(self.canvas, xx0, yy0, xx0+w, yy0+h,
|
|
# fill=color, width=4)
|
|
# drag.shadows.append(mylen)
|
|
# mylen = MfxCanvasLine(self.canvas, xx0, yy0+h, xx0+w, yy0,
|
|
# fill=color, width=4)
|
|
# drag.shadows.append(mylen)
|
|
return
|
|
#
|
|
shade = Image.new('RGBA', (w, h))
|
|
for c in cards:
|
|
x, y = self.getPositionFor(c)
|
|
x, y = x-xx0, y-yy0
|
|
im = c._active_image._pil_image
|
|
shade.paste(im, (x, y), im)
|
|
#
|
|
shade = markImage(shade)
|
|
tkshade = ImageTk.PhotoImage(shade)
|
|
im = MfxCanvasImage(self.canvas, xx0, yy0,
|
|
image=tkshade, anchor=ANCHOR_NW,
|
|
group=self.group)
|
|
drag.shadows.append(im)
|
|
|
|
def _stopDrag(self):
|
|
drag = self.game.drag
|
|
after_cancel(drag.timer)
|
|
drag.timer = None
|
|
self._deleteShade()
|
|
drag.canshade_stacks = []
|
|
drag.noshade_stacks = []
|
|
for s in drag.shadows:
|
|
s.delete()
|
|
drag.shadows = []
|
|
drag.stack = None
|
|
drag.cards = []
|
|
|
|
# finish a drag operation
|
|
def finishDrag(self, event=None):
|
|
if self.game.app.opt.dragcursor:
|
|
self.canvas.config(cursor='')
|
|
drag = self.game.drag.copy()
|
|
if self.game.app.opt.mouse_type == 'point-n-click':
|
|
drag.stack._stopDrag()
|
|
else:
|
|
self._stopDrag()
|
|
if drag.cards:
|
|
if self.game.app.opt.mouse_type == 'point-n-click':
|
|
self.releaseHandler(event, drag)
|
|
else:
|
|
assert drag.stack is self
|
|
self.releaseHandler(event, drag)
|
|
|
|
# cancel a drag operation
|
|
def cancelDrag(self, event=None):
|
|
if self.game.app.opt.dragcursor:
|
|
self.canvas.config(cursor='')
|
|
drag = self.game.drag.copy()
|
|
if self.game.app.opt.mouse_type == 'point-n-click':
|
|
drag.stack._stopDrag()
|
|
else:
|
|
self._stopDrag()
|
|
if drag.cards:
|
|
assert drag.stack is self
|
|
self.moveCardsBackHandler(event, drag)
|
|
|
|
def getHelp(self):
|
|
return str(self) # debug
|
|
|
|
def getBaseCard(self):
|
|
return ''
|
|
|
|
def _getBaseCard(self, rank=None):
|
|
# FIXME: no-french games
|
|
if self.cap.max_accept == 0:
|
|
return ''
|
|
if rank is None:
|
|
br = self.cap.base_rank
|
|
else:
|
|
br = rank
|
|
s = _('Base card - %s.')
|
|
if br == NO_RANK:
|
|
s = _('Empty row cannot be filled.')
|
|
elif br == -1:
|
|
s = s % _('any card')
|
|
elif br == 10:
|
|
s = s % _('Jack')
|
|
elif br == 11:
|
|
s = s % _('Queen')
|
|
elif br == 12:
|
|
s = s % _('King')
|
|
elif br == 0:
|
|
s = s % _('Ace')
|
|
else:
|
|
s = s % str(br+1)
|
|
return s
|
|
|
|
def getNumCards(self):
|
|
from pysollib.mygettext import ungettext
|
|
n = len(self.cards)
|
|
if n == 0:
|
|
return _('No cards')
|
|
else:
|
|
return ungettext('%d card', '%d cards', n) % n
|
|
|
|
|
|
# ************************************************************************
|
|
# * Abstract interface that supports a concept of dealing.
|
|
# ************************************************************************
|
|
|
|
class DealRow_StackMethods:
|
|
# Deal a card to each of the RowStacks. Return number of cards dealt.
|
|
def dealRow(self, rows=None, flip=1, reverse=0, frames=-1, sound=False):
|
|
if rows is None:
|
|
rows = self.game.s.rows
|
|
if sound and frames and self.game.app.opt.animations:
|
|
self.game.startDealSample()
|
|
n = self.dealToStacks(rows, flip, reverse, frames)
|
|
if sound:
|
|
self.game.stopSamples()
|
|
return n
|
|
|
|
# Same, but no error if not enough cards are available.
|
|
def dealRowAvail(self, rows=None, flip=1,
|
|
reverse=0, frames=-1, sound=False):
|
|
if rows is None:
|
|
rows = self.game.s.rows
|
|
if sound and frames and self.game.app.opt.animations:
|
|
self.game.startDealSample()
|
|
if len(self.cards) < len(rows):
|
|
rows = rows[:len(self.cards)]
|
|
n = self.dealToStacks(rows, flip, reverse, frames)
|
|
if sound:
|
|
self.game.stopSamples()
|
|
return n
|
|
|
|
def dealToStacks(self, stacks, flip=1, reverse=0, frames=-1):
|
|
if not self.cards or not stacks:
|
|
return 0
|
|
assert len(self.cards) >= len(stacks)
|
|
old_state = self.game.enterState(self.game.S_DEAL)
|
|
if reverse:
|
|
stacks = list(stacks)
|
|
stacks.reverse()
|
|
for r in stacks:
|
|
assert not self.getCard().face_up
|
|
assert r is not self
|
|
if flip:
|
|
self.game.flipMove(self)
|
|
self.game.moveMove(1, self, r, frames=frames)
|
|
if frames > 0:
|
|
self.game.top.update_idletasks()
|
|
self.game.top.busyUpdate()
|
|
self.game.leaveState(old_state)
|
|
if TOOLKIT == 'kivy':
|
|
self.game.top.waitAnimation()
|
|
return len(stacks)
|
|
|
|
# all Aces go to the Foundations
|
|
def dealToStacksOrFoundations(self, stacks, flip=1,
|
|
reverse=0, frames=-1, rank=-1):
|
|
if rank < 0:
|
|
rank = self.game.s.foundations[0].cap.base_rank
|
|
if not self.cards or not stacks:
|
|
return 0
|
|
old_state = self.game.enterState(self.game.S_DEAL)
|
|
if reverse:
|
|
stacks = list(stacks)
|
|
stacks.reverse()
|
|
n = 0
|
|
for r in stacks:
|
|
assert r is not self
|
|
while self.cards:
|
|
n += 1
|
|
if flip:
|
|
self.game.flipMove(self)
|
|
if flip and self.cards[-1].rank == rank:
|
|
for s in self.game.s.foundations:
|
|
assert s is not self
|
|
if s.acceptsCards(self, self.cards[-1:]):
|
|
self.game.moveMove(1, self, s, frames=frames)
|
|
break
|
|
else:
|
|
self.game.moveMove(1, self, r, frames=frames)
|
|
break
|
|
self.game.leaveState(old_state)
|
|
if TOOLKIT == 'kivy':
|
|
self.game.top.waitAnimation()
|
|
return n
|
|
|
|
|
|
class DealBaseCard_StackMethods:
|
|
def dealSingleBaseCard(self, frames=-1, update_saveinfo=1):
|
|
c = self.cards[-1]
|
|
self.dealBaseCards(ncards=1, frames=frames, update_saveinfo=0)
|
|
for s in self.game.s.foundations:
|
|
s.cap.base_rank = c.rank
|
|
if update_saveinfo:
|
|
cap = Struct(base_rank=c.rank)
|
|
self.game.saveinfo.stack_caps.append((s.id, cap))
|
|
return c
|
|
|
|
def dealBaseCards(self, ncards=1, frames=-1, update_saveinfo=1):
|
|
assert self.game.moves.state == self.game.S_INIT
|
|
assert not self.base_cards
|
|
while ncards > 0:
|
|
assert self.cards
|
|
c = self.cards[-1]
|
|
for s in self.game.s.foundations:
|
|
if (not s.cards and
|
|
(s.cap.base_suit < 0 or s.cap.base_suit == c.suit)):
|
|
break
|
|
else:
|
|
assert 0
|
|
s = None
|
|
s.cap.base_rank = c.rank
|
|
if update_saveinfo:
|
|
cap = Struct(base_rank=c.rank)
|
|
self.game.saveinfo.stack_caps.append((s.id, cap))
|
|
if not c.face_up:
|
|
self.game.flipMove(self)
|
|
self.game.moveMove(1, self, s, frames=frames)
|
|
ncards -= 1
|
|
|
|
|
|
class RedealCards_StackMethods:
|
|
|
|
def _redeal(self, rows=None, reverse=False, frames=0):
|
|
# move all cards to the Talon
|
|
num_cards = 0
|
|
assert len(self.cards) == 0
|
|
if rows is None:
|
|
rows = self.game.s.rows
|
|
rows = list(rows)
|
|
if reverse:
|
|
rows.reverse()
|
|
for r in rows:
|
|
for i in range(len(r.cards)):
|
|
num_cards += 1
|
|
self.game.moveMove(1, r, self, frames=frames, shadow=0)
|
|
if self.cards[-1].face_up:
|
|
self.game.flipMove(self)
|
|
assert len(self.cards) == num_cards
|
|
return num_cards
|
|
|
|
def redealCards(self, rows=None, sound=False,
|
|
shuffle=False, reverse=False, frames=0):
|
|
if sound and self.game.app.opt.animations:
|
|
self.game.startDealSample()
|
|
num_cards = self._redeal(rows=rows, reverse=reverse, frames=frames)
|
|
if num_cards == 0: # game already finished
|
|
return 0
|
|
if shuffle:
|
|
# shuffle
|
|
self.game.shuffleStackMove(self)
|
|
# redeal
|
|
self.game.nextRoundMove(self)
|
|
self.game.redealCards()
|
|
if sound:
|
|
self.game.stopSamples()
|
|
return num_cards
|
|
|
|
|
|
# ************************************************************************
|
|
# * The Talon is a stack with support for dealing.
|
|
# ************************************************************************
|
|
|
|
class TalonStack(Stack,
|
|
DealRow_StackMethods,
|
|
DealBaseCard_StackMethods,
|
|
):
|
|
def __init__(self, x, y, game, max_rounds=1, num_deal=1, **cap):
|
|
Stack.__init__(self, x, y, game, cap=cap)
|
|
self.max_rounds = max_rounds
|
|
self.num_deal = num_deal
|
|
self.init_redeal = Struct(
|
|
top_bottom=None,
|
|
img_coord=None,
|
|
txt_coord=None,
|
|
)
|
|
self.resetGame()
|
|
|
|
def resetGame(self):
|
|
Stack.resetGame(self)
|
|
self.round = 1
|
|
self.base_cards = [] # for DealBaseCard_StackMethods
|
|
|
|
def assertStack(self):
|
|
Stack.assertStack(self)
|
|
n = self.game.gameinfo.redeals
|
|
if n < 0:
|
|
assert self.max_rounds == n
|
|
else:
|
|
assert self.max_rounds == n + 1
|
|
|
|
# Control of dealing is transferred to the game which usually
|
|
# transfers it back to the Talon - see dealCards() below.
|
|
def clickHandler(self, event):
|
|
return self.game.dealCards(sound=True)
|
|
|
|
def rightclickHandler(self, event):
|
|
return self.clickHandler(event)
|
|
|
|
# Usually called by Game.canDealCards()
|
|
def canDealCards(self):
|
|
return len(self.cards) > 0
|
|
|
|
# Actual dealing, usually called by Game.dealCards().
|
|
# Either deal all cards in Game.startGame(), or subclass responsibility.
|
|
def dealCards(self, sound=False):
|
|
pass
|
|
|
|
# remove all cards from all stacks
|
|
def removeAllCards(self):
|
|
for stack in self.game.allstacks:
|
|
while stack.cards:
|
|
stack.removeCard(update=0)
|
|
# stack.removeCard(unhide=0, update=0)
|
|
for stack in self.game.allstacks:
|
|
stack.updateText()
|
|
|
|
def updateRedealImage(self):
|
|
deal = self.canDealCards() != 0
|
|
if self.images.redeal is not None:
|
|
img = (self.getRedealImages())[deal]
|
|
if img is not None and img is not self.images.redeal_img:
|
|
self.images.redeal.config(image=img)
|
|
self.images.redeal_img = img
|
|
|
|
def updateText(self, update_rounds=1, update_redeal=1):
|
|
# assertView(self)
|
|
Stack.updateText(self)
|
|
if update_rounds and self.game.preview <= 1:
|
|
if self.texts.rounds is not None:
|
|
t = _("Round %d") % self.round
|
|
self.texts.rounds.config(text=t)
|
|
if update_redeal:
|
|
deal = self.canDealCards() != 0
|
|
if self.images.redeal is not None:
|
|
img = (self.getRedealImages())[deal]
|
|
if img is not None and img is not self.images.redeal_img:
|
|
self.images.redeal.config(image=img)
|
|
self.images.redeal_img = img
|
|
t = ("", _("Redeal"))[deal]
|
|
else:
|
|
t = (_("Stop"), _("Redeal"))[deal]
|
|
if self.texts.redeal is not None and self.game.preview <= 1:
|
|
if t != self.texts.redeal_str:
|
|
self.texts.redeal.config(text=t)
|
|
self.texts.redeal_str = t
|
|
|
|
def _addRedealImage(self):
|
|
# add or remove the redeal image/text
|
|
|
|
if not self.is_visible or self.images.bottom is None:
|
|
return
|
|
if self.game.preview > 1:
|
|
return
|
|
images = self.game.app.images
|
|
cw, ch = images.getSize()
|
|
cx, cy = self.init_redeal.img_coord
|
|
ca = 'center'
|
|
tx, ty = self.init_redeal.txt_coord
|
|
|
|
if self.images.redeal:
|
|
self.canvas.delete(self.images.redeal)
|
|
self.images.redeal = None
|
|
self.images.redeal_img = None
|
|
if self.texts.redeal:
|
|
self.canvas.delete(self.texts.redeal)
|
|
self.texts.redeal = None
|
|
self.texts.redeal_str = ''
|
|
self.top_bottom = self.init_redeal.top_bottom
|
|
|
|
if cw >= 60 and ch >= 60:
|
|
# add a redeal image above the bottom image
|
|
img = (self.getRedealImages())[self.max_rounds != 1]
|
|
if img is not None:
|
|
self.images.redeal_img = img
|
|
if TOOLKIT == 'tk':
|
|
self.images.redeal = MfxCanvasImage(self.canvas,
|
|
cx, cy, image=img,
|
|
anchor="center",
|
|
group=self.group)
|
|
self.images.redeal.tkraise(self.top_bottom)
|
|
elif TOOLKIT == 'kivy':
|
|
self.images.redeal = MfxCanvasImage(self.canvas,
|
|
cx, cy, image=img,
|
|
anchor="center",
|
|
group=self.group,
|
|
hint="redeal_image")
|
|
self.images.redeal.tkraise(self.top_bottom)
|
|
elif TOOLKIT == 'gtk':
|
|
# FIXME
|
|
pass
|
|
self.top_bottom = self.images.redeal
|
|
if ch >= 90:
|
|
cy, ca = ty, "s"
|
|
else:
|
|
ca = None
|
|
font = self.game.app.getFont("canvas_default")
|
|
text_width = get_text_width(_('Redeal'), font=font,
|
|
root=self.canvas)
|
|
if cw >= text_width+4 and ca:
|
|
# add a redeal text below the bottom image
|
|
if self.max_rounds != 1:
|
|
# FIXME: sometimes canvas do not show the text
|
|
# print 'add txt', cx, cy
|
|
self.texts.redeal_str = ""
|
|
images = self.game.app.images
|
|
self.texts.redeal = MfxCanvasText(self.canvas, cx, cy,
|
|
anchor=ca, font=font,
|
|
group=self.group)
|
|
if TOOLKIT == 'tk':
|
|
self.texts.redeal.tkraise(self.top_bottom)
|
|
elif TOOLKIT == 'kivy':
|
|
self.texts.redeal.tkraise(self.top_bottom)
|
|
elif TOOLKIT == 'gtk':
|
|
# FIXME
|
|
pass
|
|
self.top_bottom = self.texts.redeal
|
|
|
|
def prepareView(self):
|
|
Stack.prepareView(self)
|
|
if 0:
|
|
if not self.is_visible or self.images.bottom is None:
|
|
return
|
|
if self.images.redeal is not None or self.texts.redeal is not None:
|
|
return
|
|
if self.game.preview > 1:
|
|
return
|
|
images = self.game.app.images
|
|
self.init_redeal.top_bottom = self.top_bottom
|
|
cx, cy = self.x + images.CARDW//2, self.y + images.CARDH//2
|
|
ty = self.y + images.CARDH - 4
|
|
self.init_redeal.img_coord = cx, cy
|
|
self.init_redeal.txt_coord = cx, ty
|
|
|
|
# At least display a redealImage at start, if USE_PIL is not set.
|
|
if USE_PIL is False:
|
|
self._addRedealImage()
|
|
|
|
getBottomImage = Stack._getTalonBottomImage
|
|
|
|
def getRedealImages(self):
|
|
# returns a tuple of two PhotoImages
|
|
return self.game.app.gimages.redeal
|
|
|
|
def getHelp(self):
|
|
from pysollib.mygettext import ungettext
|
|
if self.max_rounds == -2:
|
|
nredeals = _('Variable redeals.')
|
|
elif self.max_rounds == -1:
|
|
nredeals = _('Unlimited redeals.')
|
|
else:
|
|
n = self.max_rounds-1
|
|
nredeals = ungettext('%d redeal', '%d redeals', n) % n
|
|
# round = _('Round #%d.') % self.round
|
|
return _('Talon.')+' '+nredeals # +' '+round
|
|
|
|
# def getBaseCard(self):
|
|
# return self._getBaseCard()
|
|
|
|
def resize(self, xf, yf, widthpad=0, heightpad=0):
|
|
self._addRedealImage()
|
|
Stack.resize(self, xf, yf, widthpad=widthpad, heightpad=heightpad)
|
|
|
|
|
|
# Use for games that do not allow manual dealing from the talon.
|
|
class AutoDealTalonStack(TalonStack):
|
|
def canDealCards(self):
|
|
return False
|
|
|
|
|
|
# A single click deals one card to each of the RowStacks.
|
|
class DealRowTalonStack(TalonStack):
|
|
def dealCards(self, sound=False):
|
|
return self.dealRowAvail(sound=sound)
|
|
|
|
|
|
# A single click deals one card to each of the RowStacks.
|
|
class DealFirstRowTalonStack(TalonStack):
|
|
def dealCards(self, sound=False):
|
|
return self.dealRowAvail(sound=sound, rows=(self.game.s.rows[0],))
|
|
|
|
|
|
# For games where the Talon is only used for the initial dealing.
|
|
class InitialDealTalonStack(TalonStack):
|
|
# no bindings
|
|
def initBindings(self):
|
|
pass
|
|
# no bottom
|
|
getBottomImage = Stack._getNoneBottomImage
|
|
|
|
|
|
class RedealTalonStack(TalonStack, RedealCards_StackMethods):
|
|
def canDealCards(self):
|
|
if self.round == self.max_rounds:
|
|
return False
|
|
return not self.game.isGameWon()
|
|
|
|
def dealCards(self, sound=False):
|
|
RedealCards_StackMethods.redealCards(self, sound=sound)
|
|
|
|
|
|
class DealRowRedealTalonStack(TalonStack, RedealCards_StackMethods):
|
|
|
|
def canDealCards(self, rows=None):
|
|
if rows is None:
|
|
rows = self.game.s.rows
|
|
r_cards = sum([len(r.cards) for r in rows])
|
|
if self.cards:
|
|
return True
|
|
elif r_cards and self.round != self.max_rounds:
|
|
return True
|
|
return False
|
|
|
|
def dealCards(self, sound=False, rows=None, shuffle=False):
|
|
num_cards = 0
|
|
if rows is None:
|
|
rows = self.game.s.rows
|
|
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=rows, frames=4)
|
|
if shuffle:
|
|
# shuffle
|
|
self.game.shuffleStackMove(self)
|
|
self.game.nextRoundMove(self)
|
|
num_cards += self.dealRowAvail(rows=rows, sound=False)
|
|
if sound:
|
|
self.game.stopSamples()
|
|
return num_cards
|
|
|
|
def shuffleAndDealCards(self, sound=False, rows=None):
|
|
DealRowRedealTalonStack.dealCards(self, sound=sound,
|
|
rows=rows, shuffle=True)
|
|
|
|
|
|
class DealFirstRowRedealTalonStack(DealRowRedealTalonStack):
|
|
|
|
def canDealCards(self, rows=None):
|
|
return DealRowRedealTalonStack.canDealCards(
|
|
self, rows=self.game.s.reserves)
|
|
|
|
def dealCards(self, sound=False, rows=None):
|
|
return DealRowRedealTalonStack.dealCards(
|
|
self, sound=sound, rows=(self.game.s.rows[0],))
|
|
|
|
|
|
class DealReserveRedealTalonStack(DealRowRedealTalonStack):
|
|
|
|
def canDealCards(self, rows=None):
|
|
return DealRowRedealTalonStack.canDealCards(
|
|
self, rows=self.game.s.reserves)
|
|
|
|
def dealCards(self, sound=False, rows=None):
|
|
return DealRowRedealTalonStack.dealCards(
|
|
self, sound=sound, rows=self.game.s.reserves)
|
|
|
|
|
|
# Spider Talons
|
|
class SpiderTalonStack(DealRowRedealTalonStack):
|
|
def canDealCards(self):
|
|
if not DealRowRedealTalonStack.canDealCards(self):
|
|
return False
|
|
# no row may be empty
|
|
for r in self.game.s.rows:
|
|
if not r.cards:
|
|
return False
|
|
return True
|
|
|
|
|
|
class GroundsForADivorceTalonStack(DealRowRedealTalonStack):
|
|
# A single click deals a new cards to each non-empty row.
|
|
def dealCards(self, sound=True):
|
|
if self.cards:
|
|
rows = [r for r in self.game.s.rows if r.cards]
|
|
# if not rows:
|
|
# # deal one card to first row if all rows are empty
|
|
# rows = self.game.s.rows[:1]
|
|
return DealRowRedealTalonStack.dealRowAvail(self, rows=rows,
|
|
sound=sound)
|
|
return 0
|
|
|
|
|
|
# ************************************************************************
|
|
# * An OpenStack is a stack where cards can be placed and dragged
|
|
# * (i.e. FoundationStack, RowStack, ReserveStack, ...)
|
|
# *
|
|
# * Note that it defaults to max_move=1 and max_accept=0.
|
|
# ************************************************************************
|
|
|
|
class OpenStack(Stack):
|
|
def __init__(self, x, y, game, **cap):
|
|
kwdefault(cap, max_move=1, max_accept=0, max_cards=999999)
|
|
Stack.__init__(self, x, y, game, cap=cap)
|
|
|
|
#
|
|
# Capabilities {model}
|
|
#
|
|
|
|
def acceptsCards(self, from_stack, cards):
|
|
# default for OpenStack: we cannot accept
|
|
# cards (max_accept defaults to 0)
|
|
return self.basicAcceptsCards(from_stack, cards)
|
|
|
|
def canMoveCards(self, cards):
|
|
# import pdb
|
|
# pdb.set_trace()
|
|
# print('OpenStack.canMoveCards()', cards)
|
|
# default for OpenStack: we can move the top card
|
|
# (max_move defaults to 1)
|
|
return self.basicCanMoveCards(cards)
|
|
|
|
def canFlipCard(self):
|
|
# default for OpenStack: we can flip the top card
|
|
if self.basicIsBlocked() or not self.cards:
|
|
return False
|
|
return not self.cards[-1].face_up
|
|
|
|
def canDropCards(self, stacks):
|
|
if self.basicIsBlocked() or not self.cards:
|
|
return (None, 0)
|
|
cards = self.cards[-1:]
|
|
if self.canMoveCards(cards):
|
|
for s in stacks:
|
|
if s is not self and s.acceptsCards(self, cards):
|
|
return (s, 1)
|
|
return (None, 0)
|
|
|
|
#
|
|
# Mouse handlers {controller}
|
|
#
|
|
|
|
def clickHandler(self, event):
|
|
flipstacks, dropstacks, quickstacks = self.game.getAutoStacks(event)
|
|
if self in flipstacks and self.canFlipCard():
|
|
self.playFlipMove(animation=True)
|
|
# return -1 # continue this event (start a drag)
|
|
return 1 # break
|
|
return 0
|
|
|
|
def rightclickHandler(self, event):
|
|
if self.doubleclickHandler(event):
|
|
return 1
|
|
if self.game.app.opt.quickplay:
|
|
flipstacks, dropstacks, quickstacks = \
|
|
self.game.getAutoStacks(event)
|
|
if self in quickstacks:
|
|
n = self.quickPlayHandler(event)
|
|
self.game.stats.quickplay_moves += n
|
|
return n
|
|
return 0
|
|
|
|
def doubleclickHandler(self, event):
|
|
# flip or drop a card
|
|
flipstacks, dropstacks, quickstacks = self.game.getAutoStacks(event)
|
|
if self in flipstacks and self.canFlipCard():
|
|
self.playFlipMove(animation=True)
|
|
return -1 # continue this event (start a drag)
|
|
if self in dropstacks:
|
|
to_stack, ncards = self.canDropCards(self.game.s.foundations)
|
|
if to_stack:
|
|
self.game.playSample("autodrop", priority=30)
|
|
self.playMoveMove(ncards, to_stack, sound=False)
|
|
return 1
|
|
return 0
|
|
|
|
def controlclickHandler(self, event):
|
|
# highlight matching cards
|
|
if self.game.app.opt.highlight_cards:
|
|
return self.highlightMatchingCards(event)
|
|
return 0
|
|
|
|
def dragMove(self, drag, stack, sound=True):
|
|
if self.game.app.opt.mouse_type == 'point-n-click':
|
|
self.playMoveMove(len(drag.cards), stack, sound=sound)
|
|
else:
|
|
# self.playMoveMove(len(drag.cards), stack, frames=0, sound=sound)
|
|
self.playMoveMove(len(drag.cards), stack, frames=-2, sound=sound)
|
|
|
|
def releaseHandler(self, event, drag, sound=True):
|
|
cards = drag.cards
|
|
# check if we moved the card by at least 10 pixels
|
|
if event is not None:
|
|
dx, dy = event.x - drag.start_x, event.y - drag.start_y
|
|
if abs(dx) < 10 and abs(dy) < 10:
|
|
# move cards back to their origin stack
|
|
Stack.releaseHandler(self, event, drag, sound=sound)
|
|
return
|
|
# print dx, dy
|
|
# get destination stack
|
|
if self.game.app.opt.mouse_type == 'point-n-click':
|
|
from_stack = drag.stack
|
|
to_stack = self
|
|
else:
|
|
from_stack = self
|
|
to_stack = self.game.getClosestStack(cards[0], self)
|
|
# move cards
|
|
if (not to_stack or from_stack is to_stack or
|
|
not to_stack.acceptsCards(from_stack, cards)):
|
|
# move cards back to their origin stack
|
|
Stack.releaseHandler(self, event, drag, sound=sound)
|
|
else:
|
|
# this code actually moves the cards to the new stack
|
|
# self.playMoveMove(len(cards), stack, frames=0, sound=sound)
|
|
from_stack.dragMove(drag, to_stack, sound=sound)
|
|
|
|
def quickPlayHandler(self, event, from_stacks=None, to_stacks=None):
|
|
# from_stacks and to_stacks are meant for possible
|
|
# use in a subclasses
|
|
if from_stacks is None:
|
|
from_stacks = self.game.sg.dropstacks
|
|
if to_stacks is None:
|
|
# to_stacks = self.game.s.rows + self.game.s.reserves
|
|
# to_stacks = self.game.sg.dropstacks
|
|
to_stacks = self.game.s.foundations + self.game.sg.dropstacks
|
|
# from pprint import pprint; pprint(to_stacks)
|
|
moves = []
|
|
#
|
|
if not self.cards:
|
|
for s in from_stacks:
|
|
if s is not self and s.cards:
|
|
pile = s.getPile()
|
|
if pile and self.acceptsCards(s, pile):
|
|
score = self.game.getQuickPlayScore(len(pile), s, self)
|
|
moves.append((score, -len(moves), len(pile), s, self))
|
|
else:
|
|
pile1, pile2 = None, self.getPile()
|
|
if pile2:
|
|
i = self._findCard(event)
|
|
if i >= 0:
|
|
pile = self.cards[i:]
|
|
if len(pile) != len(pile2) and self.canMoveCards(pile):
|
|
pile1 = pile
|
|
for pile in (pile1, pile2):
|
|
if not pile:
|
|
continue
|
|
for s in to_stacks:
|
|
if s is not self and s.acceptsCards(self, pile):
|
|
score = self.game.getQuickPlayScore(len(pile), self, s)
|
|
moves.append((score, -len(moves), len(pile), self, s))
|
|
#
|
|
if moves:
|
|
moves.sort()
|
|
# from pprint import pprint; pprint(moves)
|
|
score, len_moves, ncards, from_stack, to_stack = moves[-1]
|
|
if score >= 0:
|
|
# self.game.playSample("startdrag")
|
|
from_stack.playMoveMove(ncards, to_stack)
|
|
return 1
|
|
return 0
|
|
|
|
def getHelp(self):
|
|
if self.cap.max_accept == 0:
|
|
return _('Reserve. No building.')
|
|
return 'Reserve.'
|
|
|
|
|
|
# ************************************************************************
|
|
# * Foundations stacks
|
|
# ************************************************************************
|
|
|
|
class AbstractFoundationStack(OpenStack):
|
|
def __init__(self, x, y, game, suit, **cap):
|
|
kwdefault(cap, suit=suit, base_suit=suit, base_rank=ACE,
|
|
dir=1, max_accept=1, max_cards=13)
|
|
OpenStack.__init__(self, x, y, game, **cap)
|
|
|
|
def canDropCards(self, stacks):
|
|
return (None, 0)
|
|
|
|
def clickHandler(self, event):
|
|
return 0
|
|
|
|
def rightclickHandler(self, event):
|
|
# return 0
|
|
if self.game.app.opt.quickplay:
|
|
n = self.quickPlayHandler(event)
|
|
self.game.stats.quickplay_moves += n
|
|
return n
|
|
return 0
|
|
|
|
def quickPlayHandler(self, event):
|
|
# return 0
|
|
from_stacks = self.game.sg.dropstacks + self.game.s.foundations
|
|
# to_stacks = self.game.sg.dropstacks
|
|
to_stacks = from_stacks
|
|
return OpenStack.quickPlayHandler(self, event, from_stacks, to_stacks)
|
|
|
|
getBottomImage = Stack._getSuitBottomImage
|
|
|
|
def getBaseCard(self):
|
|
return self._getBaseCard()
|
|
|
|
def closeStack(self):
|
|
if len(self.cards) == self.cap.max_cards:
|
|
self.is_filled = True
|
|
self._shadeStack()
|
|
|
|
def getHelp(self):
|
|
return _('Foundation.')
|
|
|
|
def varyAcceptsCards(self, from_stack, cards):
|
|
# if base rank of foundations is vary
|
|
subclass = self.__class__ # derived class (SS_FoundationStack, etc)
|
|
assert subclass is not AbstractFoundationStack
|
|
if self.cards:
|
|
return subclass.acceptsCards(self, from_stack, cards)
|
|
if not subclass.acceptsCards(self, from_stack, cards):
|
|
return False
|
|
# this stack don't have cards: check base rank of other stacks
|
|
for s in self.game.s.foundations:
|
|
if s.cards:
|
|
base_card = s.cards[0]
|
|
return base_card.rank == cards[0].rank
|
|
return True # all foundations is empty
|
|
|
|
def varyGetBaseCard(self):
|
|
rank = None
|
|
for s in self.game.s.foundations:
|
|
if s.cards:
|
|
rank = s.cards[0].rank
|
|
return self._getBaseCard(rank=rank)
|
|
|
|
|
|
# A SameSuit_FoundationStack is the typical Foundation stack.
|
|
# It builds up in rank and suit.
|
|
class SS_FoundationStack(AbstractFoundationStack):
|
|
def acceptsCards(self, from_stack, cards):
|
|
if not AbstractFoundationStack.acceptsCards(self, from_stack, cards):
|
|
return False
|
|
if self.cards:
|
|
# check the rank
|
|
if ((self.cards[-1].rank + self.cap.dir) % self.cap.mod !=
|
|
cards[0].rank):
|
|
return False
|
|
return True
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Foundation. Build up by suit.')
|
|
elif self.cap.dir < 0:
|
|
return _('Foundation. Build down by suit.')
|
|
else:
|
|
return _('Foundation. Build by same rank.')
|
|
|
|
|
|
# A Rank_FoundationStack builds up in rank and ignores color and suit.
|
|
class RK_FoundationStack(SS_FoundationStack):
|
|
def __init__(self, x, y, game, suit=ANY_SUIT, **cap):
|
|
SS_FoundationStack.__init__(self, x, y, game, ANY_SUIT, **cap)
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Foundation. Build up regardless of suit.')
|
|
elif self.cap.dir < 0:
|
|
return _('Foundation. Build down regardless of suit.')
|
|
else:
|
|
return _('Foundation. Build by same rank.')
|
|
|
|
|
|
# A AlternateColor_FoundationStack builds up in rank and alternate color.
|
|
# It is used in only a few games.
|
|
class AC_FoundationStack(SS_FoundationStack):
|
|
def __init__(self, x, y, game, suit, **cap):
|
|
kwdefault(cap, base_suit=suit)
|
|
SS_FoundationStack.__init__(self, x, y, game, ANY_SUIT, **cap)
|
|
|
|
def acceptsCards(self, from_stack, cards):
|
|
if not SS_FoundationStack.acceptsCards(self, from_stack, cards):
|
|
return False
|
|
if self.cards:
|
|
# check the color
|
|
if cards[0].color == self.cards[-1].color:
|
|
return False
|
|
return True
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Foundation. Build up by alternate color.')
|
|
elif self.cap.dir < 0:
|
|
return _('Foundation. Build down by alternate color.')
|
|
else:
|
|
return _('Foundation. Build by same rank.')
|
|
|
|
|
|
# A SameColor_FoundationStack builds up in rank and alternate color.
|
|
# It is used in only a few games.
|
|
class SC_FoundationStack(SS_FoundationStack):
|
|
def __init__(self, x, y, game, suit, **cap):
|
|
kwdefault(cap, base_suit=suit)
|
|
SS_FoundationStack.__init__(self, x, y, game, ANY_SUIT, **cap)
|
|
|
|
def acceptsCards(self, from_stack, cards):
|
|
if not SS_FoundationStack.acceptsCards(self, from_stack, cards):
|
|
return False
|
|
if self.cards:
|
|
# check the color
|
|
if cards[0].color != self.cards[-1].color:
|
|
return False
|
|
return True
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Foundation. Build up by color.')
|
|
elif self.cap.dir < 0:
|
|
return _('Foundation. Build down by color.')
|
|
else:
|
|
return _('Foundation. Build by same rank.')
|
|
|
|
|
|
# A ButOwn_FoundationStack builds up in rank and any suit but the same.
|
|
# It is used in only a few games.
|
|
class BO_FoundationStack(SS_FoundationStack):
|
|
def __init__(self, x, y, game, suit, **cap):
|
|
kwdefault(cap, base_suit=suit)
|
|
SS_FoundationStack.__init__(self, x, y, game, ANY_SUIT, **cap)
|
|
|
|
def acceptsCards(self, from_stack, cards):
|
|
if not SS_FoundationStack.acceptsCards(self, from_stack, cards):
|
|
return False
|
|
if self.cards:
|
|
# check the suit
|
|
if cards[0].suit == self.cards[-1].suit:
|
|
return False
|
|
return True
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Foundation. Build up in any suit but the same.')
|
|
elif self.cap.dir < 0:
|
|
return _('Foundation. Build down in any suit but the same.')
|
|
else:
|
|
return _('Foundation. Build by same rank.')
|
|
|
|
|
|
# Spider-type foundations
|
|
class Spider_SS_Foundation(AbstractFoundationStack):
|
|
def __init__(self, x, y, game, suit=ANY_SUIT, **cap):
|
|
kwdefault(cap, dir=-1, base_rank=KING,
|
|
min_accept=13, max_accept=13, 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
|
|
# now check the cards
|
|
return isSameSuitSequence(cards, self.cap.mod, self.cap.dir)
|
|
|
|
|
|
class Spider_AC_Foundation(Spider_SS_Foundation):
|
|
def acceptsCards(self, from_stack, cards):
|
|
if not AbstractFoundationStack.acceptsCards(self, from_stack, cards):
|
|
return False
|
|
# now check the cards
|
|
return isAlternateColorSequence(cards, self.cap.mod, self.cap.dir)
|
|
|
|
|
|
class Spider_SC_Foundation(Spider_SS_Foundation):
|
|
def acceptsCards(self, from_stack, cards):
|
|
if not AbstractFoundationStack.acceptsCards(self, from_stack, cards):
|
|
return False
|
|
# now check the cards
|
|
return isSameColorSequence(cards, self.cap.mod, self.cap.dir)
|
|
|
|
|
|
class Spider_RK_Foundation(Spider_SS_Foundation):
|
|
def acceptsCards(self, from_stack, cards):
|
|
if not AbstractFoundationStack.acceptsCards(self, from_stack, cards):
|
|
return False
|
|
# now check the cards
|
|
return isRankSequence(cards, self.cap.mod, self.cap.dir)
|
|
|
|
|
|
class Spider_BO_Foundation(Spider_SS_Foundation):
|
|
def acceptsCards(self, from_stack, cards):
|
|
if not AbstractFoundationStack.acceptsCards(self, from_stack, cards):
|
|
return False
|
|
# now check the cards
|
|
return isAnySuitButOwnSequence(cards, self.cap.mod, self.cap.dir)
|
|
|
|
# ************************************************************************
|
|
# * Abstract classes for row stacks.
|
|
# ************************************************************************
|
|
|
|
|
|
# Abstract class.
|
|
class SequenceStack_StackMethods:
|
|
def _isSequence(self, cards):
|
|
# Are the cards in a basic sequence for our stack ?
|
|
raise SubclassResponsibility
|
|
|
|
def _isAcceptableSequence(self, cards):
|
|
return self._isSequence(cards)
|
|
|
|
def _isMoveableSequence(self, cards):
|
|
# import pdb; pdb.set_trace()
|
|
return self._isSequence(cards)
|
|
|
|
def acceptsCards(self, from_stack, cards):
|
|
if not self.basicAcceptsCards(from_stack, cards):
|
|
return False
|
|
# cards must be an acceptable sequence
|
|
if not self._isAcceptableSequence(cards):
|
|
return False
|
|
# [topcard + cards] must be an acceptable sequence
|
|
if (self.cards and not
|
|
self._isAcceptableSequence([self.cards[-1]] + cards)):
|
|
return False
|
|
return True
|
|
|
|
def canMoveCards(self, cards):
|
|
return self.basicCanMoveCards(cards) and \
|
|
self._isMoveableSequence(cards)
|
|
|
|
|
|
# Abstract class.
|
|
class BasicRowStack(OpenStack):
|
|
def __init__(self, x, y, game, **cap):
|
|
kwdefault(cap, dir=-1, base_rank=ANY_RANK)
|
|
OpenStack.__init__(self, x, y, game, **cap)
|
|
self.CARD_YOFFSET = game.app.images.CARD_YOFFSET
|
|
|
|
def getHelp(self):
|
|
if self.cap.max_accept == 0:
|
|
return _('Tableau. No building.')
|
|
return ''
|
|
|
|
# def getBaseCard(self):
|
|
# return self._getBaseCard()
|
|
|
|
def spiderCanDropCards(self, stacks):
|
|
# print('spiderCanDropCards()', stacks)
|
|
# drop whole sequence
|
|
if len(self.cards) < 13:
|
|
return (None, 0)
|
|
cards = self.cards[-13:]
|
|
for s in stacks:
|
|
if s is not self and s.acceptsCards(self, cards):
|
|
return (s, 13)
|
|
return (None, 0)
|
|
|
|
def getReserveBottomImage(self):
|
|
return self.game.app.images.getReserveBottom()
|
|
|
|
|
|
# Abstract class.
|
|
class SequenceRowStack(SequenceStack_StackMethods, BasicRowStack):
|
|
# canMoveCards = OpenStack.canMoveCards
|
|
|
|
def __init__(self, x, y, game, **cap):
|
|
kwdefault(cap, max_move=999999, max_accept=999999)
|
|
BasicRowStack.__init__(self, x, y, game, **cap)
|
|
|
|
def getBaseCard(self):
|
|
return self._getBaseCard()
|
|
|
|
|
|
# ************************************************************************
|
|
# * Row stacks (the main playing stacks on the Tableau).
|
|
# ************************************************************************
|
|
|
|
#
|
|
# Implementation of common row stacks follows here.
|
|
#
|
|
|
|
# An AlternateColor_RowStack builds down by rank and alternate color.
|
|
# e.g. Klondike
|
|
class AC_RowStack(SequenceRowStack):
|
|
def _isSequence(self, cards):
|
|
return isAlternateColorSequence(cards, self.cap.mod, self.cap.dir)
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Tableau. Build up by alternate color.')
|
|
elif self.cap.dir < 0:
|
|
return _('Tableau. Build down by alternate color.')
|
|
else:
|
|
return _('Tableau. Build by same rank.')
|
|
|
|
|
|
# A SameColor_RowStack builds down by rank and same color.
|
|
# e.g. Klondike
|
|
class SC_RowStack(SequenceRowStack):
|
|
def _isSequence(self, cards):
|
|
return isSameColorSequence(cards, self.cap.mod, self.cap.dir)
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Tableau. Build up by color.')
|
|
elif self.cap.dir < 0:
|
|
return _('Tableau. Build down by color.')
|
|
else:
|
|
return _('Tableau. Build by same rank.')
|
|
|
|
|
|
# A SameSuit_RowStack builds down by rank and suit.
|
|
class SS_RowStack(SequenceRowStack):
|
|
def _isSequence(self, cards):
|
|
return isSameSuitSequence(cards, self.cap.mod, self.cap.dir)
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Tableau. Build up by suit.')
|
|
elif self.cap.dir < 0:
|
|
return _('Tableau. Build down by suit.')
|
|
else:
|
|
return _('Tableau. Build by same rank.')
|
|
|
|
|
|
# A Rank_RowStack builds down by rank ignoring suit.
|
|
class RK_RowStack(SequenceRowStack):
|
|
def _isSequence(self, cards):
|
|
return isRankSequence(cards, self.cap.mod, self.cap.dir)
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Tableau. Build up regardless of suit.')
|
|
elif self.cap.dir < 0:
|
|
return _('Tableau. Build down regardless of suit.')
|
|
else:
|
|
return _('Tableau. Build by same rank.')
|
|
|
|
|
|
# ButOwn_RowStack
|
|
class BO_RowStack(SequenceRowStack):
|
|
def _isSequence(self, cards):
|
|
return isAnySuitButOwnSequence(cards, self.cap.mod, self.cap.dir)
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Tableau. Build up in any suit but the same.')
|
|
elif self.cap.dir < 0:
|
|
return _('Tableau. Build down in any suit but the same.')
|
|
else:
|
|
return _('Tableau. Build by same rank.')
|
|
|
|
|
|
# A Freecell_AlternateColor_RowStack
|
|
class FreeCell_AC_RowStack(AC_RowStack):
|
|
def canMoveCards(self, cards):
|
|
max_move = getNumberOfFreeStacks(self.game.s.reserves) + 1
|
|
return len(cards) <= max_move and AC_RowStack.canMoveCards(self, cards)
|
|
|
|
|
|
# A Freecell_SameSuit_RowStack (i.e. Baker's Game)
|
|
class FreeCell_SS_RowStack(SS_RowStack):
|
|
def canMoveCards(self, cards):
|
|
max_move = getNumberOfFreeStacks(self.game.s.reserves) + 1
|
|
return len(cards) <= max_move and SS_RowStack.canMoveCards(self, cards)
|
|
|
|
|
|
# A Freecell_Rank_RowStack
|
|
class FreeCell_RK_RowStack(RK_RowStack):
|
|
def canMoveCards(self, cards):
|
|
max_move = getNumberOfFreeStacks(self.game.s.reserves) + 1
|
|
return len(cards) <= max_move and RK_RowStack.canMoveCards(self, cards)
|
|
|
|
|
|
# A Spider_AlternateColor_RowStack builds down by rank and alternate color,
|
|
# but accepts sequences that match by rank only.
|
|
class Spider_AC_RowStack(AC_RowStack):
|
|
def _isAcceptableSequence(self, cards):
|
|
return isRankSequence(cards, self.cap.mod, self.cap.dir)
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Tableau. Build up regardless of suit. '
|
|
'Sequences of cards in alternate color '
|
|
'can be moved as a unit.')
|
|
elif self.cap.dir < 0:
|
|
return _('Tableau. Build down regardless of suit. '
|
|
'Sequences of cards in alternate color can be moved '
|
|
'as a unit.')
|
|
else:
|
|
return _('Tableau. Build by same rank.')
|
|
|
|
|
|
# A Spider_SameSuit_RowStack builds down by rank and suit,
|
|
# but accepts sequences that match by rank only.
|
|
class Spider_SS_RowStack(SS_RowStack):
|
|
def _isAcceptableSequence(self, cards):
|
|
return isRankSequence(cards, self.cap.mod, self.cap.dir)
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Tableau. Build up regardless of suit. '
|
|
'Sequences of cards in the same suit can be moved '
|
|
'as a unit.')
|
|
elif self.cap.dir < 0:
|
|
return _('Tableau. Build down regardless of suit. '
|
|
'Sequences of cards in the same suit can be moved '
|
|
'as a unit.')
|
|
else:
|
|
return _('Tableau. Build by same rank.')
|
|
|
|
|
|
# A Spider_SameColor_RowStack builds down by rank and color,
|
|
# but accepts sequences that match by rank only.
|
|
class Spider_SC_RowStack(SC_RowStack):
|
|
def _isAcceptableSequence(self, cards):
|
|
return isRankSequence(cards, self.cap.mod, self.cap.dir)
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Tableau. Build up regardless of suit. '
|
|
'Sequences of cards in the same color can be moved '
|
|
'as a unit.')
|
|
elif self.cap.dir < 0:
|
|
return _('Tableau. Build down regardless of suit. '
|
|
'Sequences of cards in the same color can be moved '
|
|
'as a unit.')
|
|
else:
|
|
return _('Tableau. Build by same rank.')
|
|
|
|
|
|
# A Spider_ButOwn_RowStack builds down by rank and any suit but own,
|
|
# but accepts sequences that match by rank only.
|
|
class Spider_BO_RowStack(BO_RowStack):
|
|
def _isAcceptableSequence(self, cards):
|
|
return isRankSequence(cards, self.cap.mod, self.cap.dir)
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Tableau. Build up regardless of suit. '
|
|
'Sequences of cards in any suit but the same '
|
|
'can be moved as a unit.')
|
|
elif self.cap.dir < 0:
|
|
return _('Tableau. Build down regardless of suit. '
|
|
'Sequences of cards in any suit but the same '
|
|
'can be moved as a unit.')
|
|
else:
|
|
return _('Tableau. Build by same rank.')
|
|
|
|
|
|
# A Yukon_AlternateColor_RowStack builds down by rank and alternate color,
|
|
# but can move any face-up cards regardless of sequence.
|
|
class Yukon_AC_RowStack(BasicRowStack):
|
|
def __init__(self, x, y, game, **cap):
|
|
kwdefault(cap, max_move=999999, max_accept=999999)
|
|
BasicRowStack.__init__(self, x, y, game, **cap)
|
|
|
|
def _isYukonSequence(self, c1, c2):
|
|
# print('Yukon_AC_RowStack._isYukonSequence()', c1, c2)
|
|
return ((c1.rank + self.cap.dir) % self.cap.mod == c2.rank and
|
|
c1.color != c2.color)
|
|
|
|
def acceptsCards(self, from_stack, cards):
|
|
# print('Yukon_AC_RowStack.acceptsCards()', from_stack, cards)
|
|
if not self.basicAcceptsCards(from_stack, cards):
|
|
return False
|
|
# [topcard + card[0]] must be acceptable
|
|
if self.cards and not self._isYukonSequence(self.cards[-1], cards[0]):
|
|
return False
|
|
return True
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Tableau. Build up by alternate color, '
|
|
'can move any face-up cards regardless of sequence.')
|
|
elif self.cap.dir < 0:
|
|
return _('Tableau. Build down by alternate color, '
|
|
'can move any face-up cards regardless of sequence.')
|
|
else:
|
|
return _('Tableau. Build by same rank, can move '
|
|
'any face-up cards regardless of sequence.')
|
|
|
|
def getBaseCard(self):
|
|
return self._getBaseCard()
|
|
|
|
|
|
# A Yukon_SameSuit_RowStack builds down by rank and suit,
|
|
# but can move any face-up cards regardless of sequence.
|
|
class Yukon_SS_RowStack(Yukon_AC_RowStack):
|
|
def _isYukonSequence(self, c1, c2):
|
|
return ((c1.rank + self.cap.dir) % self.cap.mod == c2.rank and
|
|
c1.suit == c2.suit)
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Tableau. Build up by suit, can move any face-up cards '
|
|
'regardless of sequence.')
|
|
elif self.cap.dir < 0:
|
|
return _('Tableau. Build down by suit, can move any '
|
|
'face-up cards regardless of sequence.')
|
|
else:
|
|
return _('Tableau. Build by same rank, can move any '
|
|
'face-up cards regardless of sequence.')
|
|
|
|
|
|
# A Yukon_SameColor_RowStack builds down by rank and color,
|
|
# but can move any face-up cards regardless of sequence.
|
|
class Yukon_SC_RowStack(Yukon_AC_RowStack):
|
|
def _isYukonSequence(self, c1, c2):
|
|
return ((c1.rank + self.cap.dir) % self.cap.mod == c2.rank and
|
|
c1.color == c2.color)
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Tableau. Build up by color, can move any face-up cards '
|
|
'regardless of sequence.')
|
|
elif self.cap.dir < 0:
|
|
return _('Tableau. Build down by color, can move any '
|
|
'face-up cards regardless of sequence.')
|
|
else:
|
|
return _('Tableau. Build by same rank, can move any '
|
|
'face-up cards regardless of sequence.')
|
|
|
|
|
|
# A Yukon_Rank_RowStack builds down by rank
|
|
# but can move any face-up cards regardless of sequence.
|
|
class Yukon_RK_RowStack(Yukon_AC_RowStack):
|
|
def _isYukonSequence(self, c1, c2):
|
|
return (c1.rank + self.cap.dir) % self.cap.mod == c2.rank
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Tableau. Build up regardless of suit, '
|
|
'can move any face-up cards regardless of sequence.')
|
|
elif self.cap.dir < 0:
|
|
return _('Tableau. Build up regardless of suit, can move any '
|
|
'face-up cards regardless of sequence.')
|
|
else:
|
|
return _('Tableau. Build by same rank, can move any '
|
|
'face-up cards regardless of sequence.')
|
|
|
|
|
|
# A Yukon_ButOwn_RowStack builds down by rank and suit,
|
|
# but can move any face-up cards regardless of sequence.
|
|
class Yukon_BO_RowStack(Yukon_AC_RowStack):
|
|
def _isYukonSequence(self, c1, c2):
|
|
return ((c1.rank + self.cap.dir) % self.cap.mod == c2.rank and
|
|
c1.suit != c2.suit)
|
|
|
|
def getHelp(self):
|
|
if self.cap.dir > 0:
|
|
return _('Tableau. Build up by any suit but the same, '
|
|
'can move any face-up cards regardless of sequence.')
|
|
elif self.cap.dir < 0:
|
|
return _('Tableau. Build down by any suit but the same, '
|
|
'can move any face-up cards regardless of sequence.')
|
|
else:
|
|
return _('Tableau. Build by same rank, can move any '
|
|
'face-up cards regardless of sequence.')
|
|
|
|
#
|
|
# King-versions of some of the above stacks: they accepts only Kings or
|
|
# sequences starting with a King as base_rank cards (i.e. when empty).
|
|
#
|
|
|
|
|
|
class KingAC_RowStack(AC_RowStack):
|
|
def __init__(self, x, y, game, **cap):
|
|
kwdefault(cap, base_rank=KING)
|
|
AC_RowStack.__init__(self, x, y, game, **cap)
|
|
|
|
|
|
class KingSS_RowStack(SS_RowStack):
|
|
def __init__(self, x, y, game, **cap):
|
|
kwdefault(cap, base_rank=KING)
|
|
SS_RowStack.__init__(self, x, y, game, **cap)
|
|
|
|
|
|
class KingRK_RowStack(RK_RowStack):
|
|
def __init__(self, x, y, game, **cap):
|
|
kwdefault(cap, base_rank=KING)
|
|
RK_RowStack.__init__(self, x, y, game, **cap)
|
|
|
|
|
|
# up or down by color
|
|
class UD_SC_RowStack(SequenceRowStack):
|
|
def __init__(self, x, y, game, **cap):
|
|
kwdefault(cap, max_move=1, max_accept=1)
|
|
SequenceRowStack.__init__(self, x, y, game, **cap)
|
|
|
|
def _isSequence(self, cards):
|
|
return (isSameColorSequence(cards, self.cap.mod, 1) or
|
|
isSameColorSequence(cards, self.cap.mod, -1))
|
|
|
|
def getHelp(self):
|
|
return _('Tableau. Build up or down by color.')
|
|
|
|
|
|
# up or down by alternate color
|
|
class UD_AC_RowStack(SequenceRowStack):
|
|
def __init__(self, x, y, game, **cap):
|
|
kwdefault(cap, max_move=1, max_accept=1)
|
|
SequenceRowStack.__init__(self, x, y, game, **cap)
|
|
|
|
def _isSequence(self, cards):
|
|
return (isAlternateColorSequence(cards, self.cap.mod, 1) or
|
|
isAlternateColorSequence(cards, self.cap.mod, -1))
|
|
|
|
def getHelp(self):
|
|
return _('Tableau. Build up or down by alternate color.')
|
|
|
|
|
|
# up or down by suit
|
|
class UD_SS_RowStack(SequenceRowStack):
|
|
def __init__(self, x, y, game, **cap):
|
|
kwdefault(cap, max_move=1, max_accept=1)
|
|
SequenceRowStack.__init__(self, x, y, game, **cap)
|
|
|
|
def _isSequence(self, cards):
|
|
return (isSameSuitSequence(cards, self.cap.mod, 1) or
|
|
isSameSuitSequence(cards, self.cap.mod, -1))
|
|
|
|
def getHelp(self):
|
|
return _('Tableau. Build up or down by suit.')
|
|
|
|
|
|
# up or down by rank ignoring suit
|
|
class UD_RK_RowStack(SequenceRowStack):
|
|
def __init__(self, x, y, game, **cap):
|
|
kwdefault(cap, max_move=1, max_accept=1)
|
|
SequenceRowStack.__init__(self, x, y, game, **cap)
|
|
|
|
def _isSequence(self, cards):
|
|
return (isRankSequence(cards, self.cap.mod, 1) or
|
|
isRankSequence(cards, self.cap.mod, -1))
|
|
|
|
def getHelp(self):
|
|
return _('Tableau. Build up or down regardless of suit.')
|
|
|
|
|
|
# up or down by rank ignoring suit
|
|
class UD_BO_RowStack(SequenceRowStack):
|
|
def __init__(self, x, y, game, **cap):
|
|
kwdefault(cap, max_move=1, max_accept=1)
|
|
SequenceRowStack.__init__(self, x, y, game, **cap)
|
|
|
|
def _isSequence(self, cards):
|
|
return (isAnySuitButOwnSequence(cards, self.cap.mod, 1) or
|
|
isAnySuitButOwnSequence(cards, self.cap.mod, -1))
|
|
|
|
def getHelp(self):
|
|
return _('Tableau. Build up or down in any suit but the same.')
|
|
|
|
|
|
# To simplify playing we also consider the number of free rows.
|
|
# See also the "SuperMove" section in the FreeCell FAQ.
|
|
class SuperMoveStack_StackMethods:
|
|
def _getMaxMove(self, to_stack_ncards):
|
|
max_move = getNumberOfFreeStacks(self.game.s.reserves) + 1
|
|
if self.cap.base_rank != ANY_RANK:
|
|
return max_move
|
|
n = getNumberOfFreeStacks(self.game.s.rows)
|
|
if to_stack_ncards == 0:
|
|
n -= 1
|
|
return max_move << max(n, 0)
|
|
|
|
def _getNumSSSeq(self, cards):
|
|
# num of same-suit sequences (for SuperMoveSpider_RowStack)
|
|
if not cards:
|
|
return 0
|
|
n = 1
|
|
suit = cards[-1].suit
|
|
for c in cards[-2::-1]:
|
|
if c.suit != suit:
|
|
suit = c.suit
|
|
n += 1
|
|
return n
|
|
|
|
|
|
class SuperMoveSS_RowStack(SuperMoveStack_StackMethods, SS_RowStack):
|
|
def canMoveCards(self, cards):
|
|
if not SS_RowStack.canMoveCards(self, cards):
|
|
return False
|
|
return len(cards) <= self._getMaxMove(1)
|
|
|
|
def acceptsCards(self, from_stack, cards):
|
|
if not SS_RowStack.acceptsCards(self, from_stack, cards):
|
|
return False
|
|
return len(cards) <= self._getMaxMove(len(self.cards))
|
|
|
|
|
|
class SuperMoveAC_RowStack(SuperMoveStack_StackMethods, AC_RowStack):
|
|
def canMoveCards(self, cards):
|
|
if not AC_RowStack.canMoveCards(self, cards):
|
|
return False
|
|
return len(cards) <= self._getMaxMove(1)
|
|
|
|
def acceptsCards(self, from_stack, cards):
|
|
if not AC_RowStack.acceptsCards(self, from_stack, cards):
|
|
return False
|
|
return len(cards) <= self._getMaxMove(len(self.cards))
|
|
|
|
|
|
class SuperMoveRK_RowStack(SuperMoveStack_StackMethods, RK_RowStack):
|
|
def canMoveCards(self, cards):
|
|
if not RK_RowStack.canMoveCards(self, cards):
|
|
return False
|
|
return len(cards) <= self._getMaxMove(1)
|
|
|
|
def acceptsCards(self, from_stack, cards):
|
|
if not RK_RowStack.acceptsCards(self, from_stack, cards):
|
|
return False
|
|
return len(cards) <= self._getMaxMove(len(self.cards))
|
|
|
|
|
|
class SuperMoveSC_RowStack(SuperMoveStack_StackMethods, SC_RowStack):
|
|
def canMoveCards(self, cards):
|
|
if not SC_RowStack.canMoveCards(self, cards):
|
|
return False
|
|
return len(cards) <= self._getMaxMove(1)
|
|
|
|
def acceptsCards(self, from_stack, cards):
|
|
if not SC_RowStack.acceptsCards(self, from_stack, cards):
|
|
return False
|
|
return len(cards) <= self._getMaxMove(len(self.cards))
|
|
|
|
|
|
class SuperMoveBO_RowStack(SuperMoveStack_StackMethods, BO_RowStack):
|
|
def canMoveCards(self, cards):
|
|
if not BO_RowStack.canMoveCards(self, cards):
|
|
return False
|
|
return len(cards) <= self._getMaxMove(1)
|
|
|
|
def acceptsCards(self, from_stack, cards):
|
|
if not BO_RowStack.acceptsCards(self, from_stack, cards):
|
|
return False
|
|
return len(cards) <= self._getMaxMove(len(self.cards))
|
|
|
|
|
|
# ************************************************************************
|
|
# * WasteStack (a helper stack for the Talon, e.g. in Klondike)
|
|
# ************************************************************************
|
|
|
|
class WasteStack(OpenStack):
|
|
def getHelp(self):
|
|
return _('Waste.')
|
|
|
|
|
|
class WasteTalonStack(TalonStack):
|
|
# A single click moves the top cards to the game's waste and
|
|
# moves it face up; if we're out of cards, it moves the waste
|
|
# back to the talon and increases the number of rounds (redeals).
|
|
def __init__(self, x, y, game, max_rounds, num_deal=1, waste=None, **cap):
|
|
TalonStack.__init__(self, x, y, game, max_rounds, num_deal, **cap)
|
|
self.waste = waste
|
|
|
|
def prepareStack(self):
|
|
TalonStack.prepareStack(self)
|
|
if self.waste is None:
|
|
self.waste = self.game.s.waste
|
|
|
|
def canDealCards(self):
|
|
waste = self.waste
|
|
if self.cards:
|
|
num_cards = min(len(self.cards), self.num_deal)
|
|
return len(waste.cards) + num_cards <= waste.cap.max_cards
|
|
elif waste.cards and self.round != self.max_rounds:
|
|
return True
|
|
return False
|
|
|
|
def dealCards(self, sound=False, shuffle=False):
|
|
old_state = self.game.enterState(self.game.S_DEAL)
|
|
num_cards = 0
|
|
waste = self.waste
|
|
if self.cards:
|
|
if sound and not self.game.demo:
|
|
self.game.playSample("dealwaste")
|
|
num_cards = min(len(self.cards), self.num_deal)
|
|
assert len(waste.cards) + num_cards <= waste.cap.max_cards
|
|
for i in range(num_cards):
|
|
if not self.cards[-1].face_up:
|
|
if 1:
|
|
self.game.flipAndMoveMove(self, waste)
|
|
else:
|
|
self.game.flipMove(self)
|
|
self.game.moveMove(1, self, waste, frames=4, shadow=0)
|
|
else:
|
|
self.game.moveMove(1, self, waste, frames=4, shadow=0)
|
|
self.fillStack()
|
|
if TOOLKIT == 'kivy':
|
|
self.game.top.waitAnimation()
|
|
elif waste.cards and self.round != self.max_rounds:
|
|
if sound:
|
|
self.game.playSample("turnwaste", priority=20)
|
|
num_cards = len(waste.cards)
|
|
self.game.turnStackMove(waste, self)
|
|
if shuffle:
|
|
# shuffle
|
|
self.game.shuffleStackMove(self)
|
|
self.game.nextRoundMove(self)
|
|
self.game.leaveState(old_state)
|
|
return num_cards
|
|
|
|
def shuffleAndDealCards(self, sound=False):
|
|
WasteTalonStack.dealCards(self, sound=sound, shuffle=True)
|
|
|
|
|
|
class FaceUpWasteTalonStack(WasteTalonStack):
|
|
def canFlipCard(self):
|
|
return len(self.cards) > 0 and not self.cards[-1].face_up
|
|
|
|
def fillStack(self):
|
|
if self.canFlipCard():
|
|
self.game.singleFlipMove(self)
|
|
self.game.fillStack(self)
|
|
|
|
def dealCards(self, sound=False):
|
|
retval = WasteTalonStack.dealCards(self, sound=sound)
|
|
if self.canFlipCard():
|
|
self.flipMove()
|
|
return retval
|
|
|
|
|
|
class OpenTalonStack(TalonStack, OpenStack):
|
|
canMoveCards = OpenStack.canMoveCards
|
|
canDropCards = OpenStack.canDropCards
|
|
releaseHandler = OpenStack.releaseHandler
|
|
|
|
def __init__(self, x, y, game, **cap):
|
|
kwdefault(cap, max_move=1)
|
|
TalonStack.__init__(self, x, y, game, **cap)
|
|
|
|
def canDealCards(self):
|
|
return False
|
|
|
|
def canFlipCard(self):
|
|
return len(self.cards) > 0 and not self.cards[-1].face_up
|
|
|
|
def fillStack(self):
|
|
if self.canFlipCard():
|
|
self.game.singleFlipMove(self)
|
|
self.game.fillStack(self)
|
|
|
|
def clickHandler(self, event):
|
|
if self.canDealCards():
|
|
return TalonStack.clickHandler(self, event)
|
|
else:
|
|
return OpenStack.clickHandler(self, event)
|
|
|
|
|
|
# ************************************************************************
|
|
# * ReserveStack (free cell)
|
|
# ************************************************************************
|
|
|
|
class ReserveStack(OpenStack):
|
|
def __init__(self, x, y, game, **cap):
|
|
kwdefault(cap, max_accept=1, max_cards=1)
|
|
OpenStack.__init__(self, x, y, game, **cap)
|
|
|
|
getBottomImage = Stack._getReserveBottomImage
|
|
|
|
def getHelp(self):
|
|
if self.cap.max_accept == 0:
|
|
return _('Reserve. No building.')
|
|
return _('Free cell.')
|
|
|
|
|
|
# ************************************************************************
|
|
# * InvisibleStack (an internal off-screen stack to hold cards)
|
|
# ************************************************************************
|
|
|
|
class InvisibleStack(Stack):
|
|
def __init__(self, game, **cap):
|
|
x, y = game.getInvisibleCoords()
|
|
kwdefault(cap, max_move=0, max_accept=0)
|
|
Stack.__init__(self, x, y, game, cap=cap)
|
|
|
|
def assertStack(self):
|
|
Stack.assertStack(self)
|
|
assert not self.is_visible
|
|
|
|
# no bindings
|
|
def initBindings(self):
|
|
pass
|
|
|
|
# no bottom
|
|
getBottomImage = Stack._getNoneBottomImage
|
|
|
|
|
|
# ************************************************************************
|
|
# * ArbitraryStack (stack with arbitrary access)
|
|
# *
|
|
# * NB: don't support hint and demo for non-top cards
|
|
# * NB: this stack only for CARD_XOFFSET == 0
|
|
# ************************************************************************
|
|
|
|
class ArbitraryStack(OpenStack):
|
|
|
|
def __init__(self, x, y, game, **cap):
|
|
kwdefault(cap, max_accept=0)
|
|
OpenStack.__init__(self, x, y, game, **cap)
|
|
self.CARD_YOFFSET = game.app.images.CARD_YOFFSET
|
|
|
|
def canMoveCards(self, cards):
|
|
return True
|
|
|
|
def getDragCards(self, index):
|
|
return [self.cards[index]]
|
|
|
|
def startDrag(self, event, sound=True):
|
|
OpenStack.startDrag(self, event, sound=sound)
|
|
if self.game.app.opt.mouse_type == 'point-n-click':
|
|
self.cards[self.game.drag.index].tkraise()
|
|
self.game.drag.shadows[0].tkraise()
|
|
else:
|
|
for c in self.cards[self.game.drag.index+1:]:
|
|
c.moveBy(0, -self.CARD_YOFFSET[0])
|
|
|
|
def doubleclickHandler(self, event):
|
|
# flip or drop a card
|
|
flipstacks, dropstacks, quickstacks = self.game.getAutoStacks(event)
|
|
if self in flipstacks and self.canFlipCard():
|
|
self.playFlipMove(animation=True)
|
|
return -1 # continue this event (start a drag)
|
|
if self in dropstacks:
|
|
i = self._findCard(event)
|
|
if i < 0:
|
|
return 0
|
|
cards = [self.cards[i]]
|
|
for s in self.game.s.foundations:
|
|
if s is not self and s.acceptsCards(self, cards):
|
|
self.game.playSample("autodrop", priority=30)
|
|
self.playSingleCardMove(i, s, sound=False)
|
|
return 1
|
|
return 0
|
|
|
|
def moveCardsBackHandler(self, event, drag):
|
|
i = self.cards.index(drag.cards[0])
|
|
for card in self.cards[i:]:
|
|
self._position(card)
|
|
card.tkraise()
|
|
|
|
def singleCardMove(self, index, to_stack, frames=-1, shadow=-1):
|
|
self.game.singleCardMove(
|
|
self, to_stack, index, frames=frames, shadow=shadow)
|
|
self.fillStack()
|
|
|
|
def dragMove(self, drag, to_stack, sound=True):
|
|
self.playSingleCardMove(drag.index, to_stack, frames=0, sound=sound)
|
|
|
|
def playSingleCardMove(self, index, to_stack, frames=-1, shadow=-1,
|
|
sound=True):
|
|
if sound:
|
|
if to_stack in self.game.s.foundations:
|
|
self.game.playSample("drop", priority=30)
|
|
else:
|
|
self.game.playSample("move", priority=10)
|
|
self.singleCardMove(index, to_stack, frames=frames, shadow=shadow)
|
|
if not self.game.checkForWin():
|
|
# let the player put cards back from the foundations
|
|
if self not in self.game.s.foundations:
|
|
self.game.autoPlay()
|
|
self.game.finishMove()
|
|
|
|
def quickPlayHandler(self, event, from_stacks=None, to_stacks=None):
|
|
if to_stacks is None:
|
|
to_stacks = self.game.s.foundations + self.game.sg.dropstacks
|
|
if not self.cards:
|
|
return 0
|
|
#
|
|
moves = []
|
|
i = self._findCard(event)
|
|
if i < 0:
|
|
return 0
|
|
pile = [self.cards[i]]
|
|
for s in to_stacks:
|
|
if s is not self and s.acceptsCards(self, pile):
|
|
score = self.game.getQuickPlayScore(1, self, s)
|
|
moves.append((score, -len(moves), i, s))
|
|
#
|
|
if moves:
|
|
moves.sort()
|
|
# from pprint import pprint; pprint(moves)
|
|
score, len_moves, index, to_stack = moves[-1]
|
|
if score >= 0:
|
|
# self.game.playSample("startdrag")
|
|
self.playSingleCardMove(index, to_stack)
|
|
return 1
|
|
return 0
|
|
|
|
|
|
# ************************************************************************
|
|
# * A StackWrapper is a functor (function object) that creates a
|
|
# * new stack when called, i.e. it wraps the constructor.
|
|
# *
|
|
# * "cap" are the capabilites, see class Stack above.
|
|
# ************************************************************************
|
|
|
|
# self.cap override any call-time cap
|
|
class StackWrapper:
|
|
def __init__(self, stack_class, **cap):
|
|
assert issubclass(stack_class, Stack)
|
|
self.stack_class = stack_class
|
|
self.cap = cap
|
|
|
|
# return a new stack (an instance of the stack class)
|
|
def __call__(self, x, y, game, **cap):
|
|
# must preserve self.cap, so create a shallow copy
|
|
# import pdb
|
|
# pdb.set_trace()
|
|
c = self.cap.copy()
|
|
kwdefault(c, **cap)
|
|
return self.stack_class(x, y, game, **c)
|
|
|
|
|
|
# call-time cap override self.cap
|
|
class WeakStackWrapper(StackWrapper):
|
|
def __call__(self, x, y, game, **cap):
|
|
kwdefault(cap, **self.cap)
|
|
return self.stack_class(x, y, game, **cap)
|
|
|
|
|
|
# self.cap only, call-time cap is completely ignored
|
|
class FullStackWrapper(StackWrapper):
|
|
def __call__(self, x, y, game, **cap):
|
|
return self.stack_class(x, y, game, **self.cap)
|