mirror of
https://github.com/shlomif/PySolFC.git
synced 2025-04-05 00:02:29 -04:00
556 lines
19 KiB
Python
556 lines
19 KiB
Python
#!/usr/bin/env python
|
|
# -*- mode: python; coding: utf-8; -*-
|
|
# ---------------------------------------------------------------------------##
|
|
#
|
|
# Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer
|
|
# Copyright (C) 2003 Mt. Hood Playing Card Co.
|
|
# Copyright (C) 2005-2009 Skomoroh
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
# ---------------------------------------------------------------------------##
|
|
|
|
import sys
|
|
|
|
if sys.version_info > (3,):
|
|
def cmp(a, b):
|
|
return ((b > a)-(a > b))
|
|
|
|
# imports
|
|
|
|
|
|
# ************************************************************************
|
|
# * moves (undo / redo)
|
|
# ************************************************************************
|
|
|
|
# Currently we have the following atomic moves:
|
|
# - move the top cards from one stack on the top of another
|
|
# - flip the top card of a stack
|
|
# - turn a whole stack onto another stack
|
|
# - update the model or complete view a stack
|
|
# - increase the round (the number of redeals)
|
|
# - save the seed of game.random
|
|
# - shuffle a stack
|
|
|
|
class AtomicMove:
|
|
|
|
def do(self, game):
|
|
self.redo(game)
|
|
|
|
def __repr__(self):
|
|
return str(self.__dict__)
|
|
|
|
def __str__(self):
|
|
return str(self.__dict__)
|
|
|
|
# Custom comparison for detecting redo moves. See Game.finishMove().
|
|
def cmpForRedo(self, other):
|
|
return -1
|
|
|
|
# Custom comparison for detecting manual undoing.
|
|
# Override only for move types where this is possible.
|
|
def cmpForUndo(self, other):
|
|
return -1
|
|
|
|
|
|
# ************************************************************************
|
|
# * Move the top N cards from a stack to another stack.
|
|
# ************************************************************************
|
|
|
|
class AMoveMove(AtomicMove):
|
|
def __init__(self, ncards, from_stack, to_stack, frames, shadow=-1):
|
|
assert from_stack is not to_stack
|
|
self.ncards = ncards
|
|
self.from_stack_id = from_stack.id
|
|
self.to_stack_id = to_stack.id
|
|
self.frames = frames
|
|
self.shadow = shadow
|
|
|
|
# do the actual move
|
|
def _doMove(self, game, ncards, from_stack, to_stack):
|
|
if game.moves.state == game.S_PLAY:
|
|
assert to_stack.acceptsCards(
|
|
from_stack, from_stack.cards[-ncards:])
|
|
frames = self.frames
|
|
if frames == -2 and game.moves.state not in (game.S_UNDO, game.S_REDO):
|
|
# don't use animation for drag-move
|
|
frames = 0
|
|
cards = from_stack.cards[-ncards:]
|
|
if frames != 0:
|
|
from_stack.unshadeStack()
|
|
x, y = to_stack.getPositionForNextCard()
|
|
game.animatedMoveTo(from_stack, to_stack, cards, x, y,
|
|
frames=frames, shadow=self.shadow)
|
|
for i in range(ncards):
|
|
from_stack.removeCard()
|
|
for c in cards:
|
|
to_stack.addCard(c)
|
|
from_stack.updatePositions()
|
|
to_stack.updatePositions()
|
|
|
|
def redo(self, game):
|
|
self._doMove(game, self.ncards, game.allstacks[self.from_stack_id],
|
|
game.allstacks[self.to_stack_id])
|
|
|
|
def undo(self, game):
|
|
self._doMove(game, self.ncards, game.allstacks[self.to_stack_id],
|
|
game.allstacks[self.from_stack_id])
|
|
|
|
def cmpForRedo(self, other):
|
|
return (cmp(self.ncards, other.ncards) or
|
|
cmp(self.from_stack_id, other.from_stack_id) or
|
|
cmp(self.to_stack_id, other.to_stack_id))
|
|
|
|
def cmpForUndo(self, other):
|
|
return (cmp(self.ncards, other.ncards) or
|
|
cmp(self.from_stack_id, other.to_stack_id) or
|
|
cmp(self.to_stack_id, other.from_stack_id))
|
|
|
|
|
|
# ************************************************************************
|
|
# * Flip the top card of a stack.
|
|
# ************************************************************************
|
|
|
|
class AFlipMove(AtomicMove):
|
|
def __init__(self, stack):
|
|
self.stack_id = stack.id
|
|
|
|
# do the actual move
|
|
def _doMove(self, game, stack):
|
|
card = stack.cards[-1]
|
|
# game.animatedFlip(stack)
|
|
if card.face_up:
|
|
card.showBack()
|
|
else:
|
|
card.showFace()
|
|
|
|
def redo(self, game):
|
|
self._doMove(game, game.allstacks[self.stack_id])
|
|
|
|
def undo(self, game):
|
|
self._doMove(game, game.allstacks[self.stack_id])
|
|
|
|
def cmpForRedo(self, other):
|
|
return cmp(self.stack_id, other.stack_id)
|
|
|
|
|
|
# flip with animation
|
|
class ASingleFlipMove(AFlipMove):
|
|
def _doMove(self, game, stack):
|
|
card = stack.cards[-1]
|
|
game.animatedFlip(stack)
|
|
if card.face_up:
|
|
card.showBack()
|
|
else:
|
|
card.showFace()
|
|
|
|
|
|
# flip and move one card
|
|
class AFlipAndMoveMove(AtomicMove):
|
|
|
|
def __init__(self, from_stack, to_stack, frames):
|
|
assert from_stack is not to_stack
|
|
self.from_stack_id = from_stack.id
|
|
self.to_stack_id = to_stack.id
|
|
self.frames = frames
|
|
|
|
def _doMove(self, game, from_stack, to_stack):
|
|
if game.moves.state == game.S_PLAY:
|
|
assert to_stack.acceptsCards(from_stack, from_stack.cards[-1])
|
|
if self.frames == 0:
|
|
moved = True
|
|
else:
|
|
moved = game.animatedFlipAndMove(from_stack, to_stack, self.frames)
|
|
c = from_stack.cards[-1]
|
|
if c.face_up:
|
|
c.showBack()
|
|
else:
|
|
c.showFace()
|
|
if not moved:
|
|
cards = from_stack.cards[-1:]
|
|
x, y = to_stack.getPositionForNextCard()
|
|
game.animatedMoveTo(from_stack, to_stack, cards, x, y,
|
|
frames=self.frames, shadow=0)
|
|
c = from_stack.removeCard(update=False)
|
|
to_stack.addCard(c, update=False)
|
|
from_stack.updateText()
|
|
to_stack.updateText()
|
|
|
|
def redo(self, game):
|
|
self._doMove(game, game.allstacks[self.from_stack_id],
|
|
game.allstacks[self.to_stack_id])
|
|
|
|
def undo(self, game):
|
|
self._doMove(game, game.allstacks[self.to_stack_id],
|
|
game.allstacks[self.from_stack_id])
|
|
|
|
def cmpForRedo(self, other):
|
|
return (cmp(self.from_stack_id, other.from_stack_id) or
|
|
cmp(self.to_stack_id, other.to_stack_id))
|
|
|
|
|
|
# ************************************************************************
|
|
# * Flip all cards
|
|
# ************************************************************************
|
|
|
|
class AFlipAllMove(AtomicMove):
|
|
def __init__(self, stack):
|
|
self.stack_id = stack.id
|
|
|
|
def redo(self, game):
|
|
stack = game.allstacks[self.stack_id]
|
|
for card in stack.cards:
|
|
if card.face_up:
|
|
card.showBack()
|
|
else:
|
|
card.showFace()
|
|
stack.refreshView()
|
|
|
|
def undo(self, game):
|
|
stack = game.allstacks[self.stack_id]
|
|
for card in stack.cards:
|
|
if card.face_up:
|
|
card.showBack()
|
|
else:
|
|
card.showFace()
|
|
stack.refreshView()
|
|
|
|
def cmpForRedo(self, other):
|
|
return cmp(self.stack_id, other.stack_id)
|
|
|
|
|
|
# ************************************************************************
|
|
# * Turn the Waste stack onto the empty Talon.
|
|
# ************************************************************************
|
|
|
|
class ATurnStackMove(AtomicMove):
|
|
def __init__(self, from_stack, to_stack):
|
|
assert from_stack is not to_stack
|
|
self.from_stack_id = from_stack.id
|
|
self.to_stack_id = to_stack.id
|
|
|
|
def redo(self, game):
|
|
from_stack = game.allstacks[self.from_stack_id]
|
|
to_stack = game.allstacks[self.to_stack_id]
|
|
assert len(from_stack.cards) > 0
|
|
assert len(to_stack.cards) == 0
|
|
mylen = len(from_stack.cards)
|
|
for i in range(mylen):
|
|
# unhide = (i >= mylen - 2)
|
|
unhide = 1
|
|
# print 1, unhide, from_stack.getCard().__dict__
|
|
card = from_stack.removeCard(unhide=unhide, update=0)
|
|
# print 2, unhide, card.__dict__
|
|
assert card.face_up
|
|
to_stack.addCard(card, unhide=unhide, update=0)
|
|
card.showBack(unhide=unhide)
|
|
# print 3, unhide, to_stack.getCard().__dict__
|
|
from_stack.updateText()
|
|
to_stack.updateText()
|
|
|
|
def undo(self, game):
|
|
from_stack = game.allstacks[self.to_stack_id]
|
|
to_stack = game.allstacks[self.from_stack_id]
|
|
assert len(from_stack.cards) > 0
|
|
assert len(to_stack.cards) == 0
|
|
mylen = len(from_stack.cards)
|
|
for i in range(mylen):
|
|
# unhide = (i >= mylen - 2)
|
|
unhide = 1
|
|
card = from_stack.removeCard(unhide=unhide, update=0)
|
|
assert not card.face_up
|
|
card.showFace(unhide=unhide)
|
|
to_stack.addCard(card, unhide=unhide, update=0)
|
|
from_stack.updateText()
|
|
to_stack.updateText()
|
|
|
|
def cmpForRedo(self, other):
|
|
return (cmp(self.from_stack_id, other.from_stack_id) or
|
|
cmp(self.to_stack_id, other.to_stack_id))
|
|
|
|
|
|
# ************************************************************************
|
|
# * ATurnStackMove is somewhat optimized to avoid unnecessary
|
|
# * unhide and hide operations.
|
|
# * FIXME: doesn't work yet
|
|
# ************************************************************************
|
|
|
|
class NEW_ATurnStackMove(AtomicMove):
|
|
def __init__(self, from_stack, to_stack, update_flags=1):
|
|
assert from_stack is not to_stack
|
|
self.from_stack_id = from_stack.id
|
|
self.to_stack_id = to_stack.id
|
|
self.update_flags = update_flags
|
|
|
|
# do the actual turning move
|
|
def _doMove(self, from_stack, to_stack, show_face):
|
|
assert len(from_stack.cards) > 0
|
|
assert len(to_stack.cards) == 0
|
|
for card in from_stack.cards:
|
|
card.item.dtag(from_stack.group)
|
|
card.item.addtag(to_stack.group)
|
|
if show_face:
|
|
assert not card.face_up
|
|
card.showFace(unhide=0)
|
|
else:
|
|
assert card.face_up
|
|
card.showBack(unhide=0)
|
|
to_stack.cards = from_stack.cards
|
|
from_stack.cards = []
|
|
from_stack.refreshView()
|
|
from_stack.updateText()
|
|
to_stack.refreshView()
|
|
to_stack.updateText()
|
|
|
|
def redo(self, game):
|
|
from_stack = game.allstacks[self.from_stack_id]
|
|
to_stack = game.allstacks[self.to_stack_id]
|
|
if self.update_flags & 1:
|
|
assert to_stack is game.s.talon
|
|
assert to_stack.round < to_stack.max_rounds or \
|
|
to_stack.max_rounds < 0
|
|
to_stack.round = to_stack.round + 1
|
|
self._doMove(from_stack, to_stack, 0)
|
|
|
|
def undo(self, game):
|
|
from_stack = game.allstacks[self.from_stack_id]
|
|
to_stack = game.allstacks[self.to_stack_id]
|
|
if self.update_flags & 1:
|
|
assert to_stack is game.s.talon
|
|
assert to_stack.round > 1
|
|
to_stack.round = to_stack.round - 1
|
|
self._doMove(to_stack, from_stack, 1)
|
|
|
|
def cmpForRedo(self, other):
|
|
return (cmp(self.from_stack_id, other.from_stack_id) or
|
|
cmp(self.to_stack_id, other.to_stack_id) or
|
|
cmp(self.update_flags, other.update_flags))
|
|
|
|
|
|
# ************************************************************************
|
|
# * Update the view or model of a stack. Only needed for complex
|
|
# * games in combination with undo.
|
|
# ************************************************************************
|
|
|
|
class AUpdateStackMove(AtomicMove):
|
|
def __init__(self, stack, flags):
|
|
self.stack_id = stack.id
|
|
self.flags = flags
|
|
|
|
# do the actual move
|
|
def _doMove(self, game, stack, undo):
|
|
if self.flags & 64:
|
|
# model
|
|
stack.updateModel(undo, self.flags)
|
|
else:
|
|
# view
|
|
if self.flags & 16:
|
|
stack.updateText()
|
|
if self.flags & 32:
|
|
stack.refreshView()
|
|
|
|
def redo(self, game):
|
|
if (self.flags & 3) in (1, 3):
|
|
self._doMove(game, game.allstacks[self.stack_id], 0)
|
|
|
|
def undo(self, game):
|
|
if (self.flags & 3) in (2, 3):
|
|
self._doMove(game, game.allstacks[self.stack_id], 1)
|
|
|
|
def cmpForRedo(self, other):
|
|
return cmp(self.stack_id, other.stack_id) or \
|
|
cmp(self.flags, other.flags)
|
|
|
|
|
|
AUpdateStackModelMove = AUpdateStackMove
|
|
AUpdateStackViewMove = AUpdateStackMove
|
|
|
|
|
|
# ************************************************************************
|
|
# * Increase the `round' member variable of a Talon stack.
|
|
# ************************************************************************
|
|
|
|
class ANextRoundMove(AtomicMove):
|
|
def __init__(self, stack):
|
|
self.stack_id = stack.id
|
|
|
|
def redo(self, game):
|
|
stack = game.allstacks[self.stack_id]
|
|
assert stack is game.s.talon
|
|
assert stack.round < stack.max_rounds or stack.max_rounds < 0
|
|
stack.round = stack.round + 1
|
|
stack.updateText()
|
|
|
|
def undo(self, game):
|
|
stack = game.allstacks[self.stack_id]
|
|
assert stack is game.s.talon
|
|
assert stack.round > 1
|
|
stack.round = stack.round - 1
|
|
stack.updateText()
|
|
|
|
def cmpForRedo(self, other):
|
|
return cmp(self.stack_id, other.stack_id)
|
|
|
|
|
|
# ************************************************************************
|
|
# * Save the current state (needed for undo in some games).
|
|
# ************************************************************************
|
|
|
|
class ASaveSeedMove(AtomicMove):
|
|
def __init__(self, game):
|
|
self.state = game.random.getstate()
|
|
|
|
def redo(self, game):
|
|
game.random.setstate(self.state)
|
|
|
|
def undo(self, game):
|
|
game.random.setstate(self.state)
|
|
|
|
def cmpForRedo(self, other):
|
|
return cmp(self.state, other.state)
|
|
|
|
|
|
# ************************************************************************
|
|
# * Save game variables
|
|
# ************************************************************************
|
|
|
|
class ASaveStateMove(AtomicMove):
|
|
def __init__(self, game, flags):
|
|
self.state = game.getState()
|
|
self.flags = flags
|
|
|
|
def redo(self, game):
|
|
if (self.flags & 3) in (1, 3):
|
|
game.setState(self.state)
|
|
|
|
def undo(self, game):
|
|
if (self.flags & 3) in (2, 3):
|
|
game.setState(self.state)
|
|
|
|
def cmpForRedo(self, other):
|
|
return cmp(self.state, other.state)
|
|
|
|
|
|
# ************************************************************************
|
|
# * Shuffle all cards of a stack. Saves the seed. Does not flip any cards.
|
|
# ************************************************************************
|
|
|
|
class AShuffleStackMove(AtomicMove):
|
|
def __init__(self, stack, game):
|
|
self.stack_id = stack.id
|
|
# save cards and state
|
|
self.card_ids = tuple([c.id for c in stack.cards])
|
|
self.state = game.random.getstate()
|
|
|
|
def redo(self, game):
|
|
stack = game.allstacks[self.stack_id]
|
|
# paranoia
|
|
assert stack is game.s.talon or stack in game.s.internals
|
|
# shuffle (see random)
|
|
game.random.setstate(self.state)
|
|
seq = stack.cards
|
|
n = len(seq) - 1
|
|
while n > 0:
|
|
j = game.random.randint(0, n)
|
|
seq[n], seq[j] = seq[j], seq[n]
|
|
n = n - 1
|
|
stack.refreshView()
|
|
|
|
def undo(self, game):
|
|
stack = game.allstacks[self.stack_id]
|
|
# restore cards
|
|
cards = []
|
|
for id in self.card_ids:
|
|
c = game.cards[id]
|
|
assert c.id == id
|
|
cards.append(c)
|
|
stack.cards = cards
|
|
# restore the state
|
|
game.random.setstate(self.state)
|
|
stack.refreshView()
|
|
|
|
def cmpForRedo(self, other):
|
|
return (cmp(self.stack_id, other.stack_id) or
|
|
cmp(self.card_ids, other.card_ids) or
|
|
self.state != other.state)
|
|
|
|
|
|
# ************************************************************************
|
|
# * ASingleCardMove - move single card from *anyone* position
|
|
# * (for ArbitraryStack)
|
|
# ************************************************************************
|
|
|
|
class ASingleCardMove(AtomicMove):
|
|
|
|
def __init__(self, from_stack, to_stack, from_pos, frames, shadow=-1):
|
|
self.from_stack_id = from_stack.id
|
|
self.to_stack_id = to_stack.id
|
|
self.from_pos = from_pos
|
|
self.frames = frames
|
|
self.shadow = shadow
|
|
|
|
def redo(self, game):
|
|
from_stack = game.allstacks[self.from_stack_id]
|
|
to_stack = game.allstacks[self.to_stack_id]
|
|
from_pos = self.from_pos
|
|
if game.moves.state == game.S_PLAY:
|
|
assert to_stack.acceptsCards(
|
|
from_stack, [from_stack.cards[from_pos]])
|
|
card = from_stack.cards[from_pos]
|
|
card = from_stack.removeCard(card, update_positions=1)
|
|
if self.frames != 0:
|
|
x, y = to_stack.getPositionFor(card)
|
|
game.animatedMoveTo(from_stack, to_stack, [card], x, y,
|
|
frames=self.frames, shadow=self.shadow)
|
|
to_stack.addCard(card)
|
|
# to_stack.refreshView()
|
|
|
|
def undo(self, game):
|
|
from_stack = game.allstacks[self.from_stack_id]
|
|
to_stack = game.allstacks[self.to_stack_id]
|
|
from_pos = self.from_pos
|
|
card = to_stack.removeCard()
|
|
# if self.frames != 0:
|
|
# x, y = to_stack.getPositionFor(card)
|
|
# game.animatedMoveTo(from_stack, to_stack, [card], x, y,
|
|
# frames=self.frames, shadow=self.shadow)
|
|
from_stack.insertCard(card, from_pos)
|
|
# to_stack.refreshView()
|
|
|
|
def cmpForRedo(self, other):
|
|
return cmp((self.from_stack_id, self.to_stack_id, self.from_pos),
|
|
(other.from_stack_id, other.to_stack_id, other.from_pos))
|
|
|
|
|
|
# ************************************************************************
|
|
# * AInnerMove - change position of single card in stack (TODO)
|
|
# ************************************************************************
|
|
|
|
class AInnerMove(AtomicMove):
|
|
|
|
def __init__(self, stack, from_pos, to_pos):
|
|
self.stack_id = stack.id
|
|
self.from_pos, self.to_pos = from_pos, to_pos
|
|
|
|
def redo(self, game):
|
|
# stack = game.allstacks[self.stack_id]
|
|
pass
|
|
|
|
def undo(self, game):
|
|
# stack = game.allstacks[self.stack_id]
|
|
pass
|
|
|
|
def cmpForRedo(self, other):
|
|
return cmp((self.stack_id, self.from_pos, self.to_pos),
|
|
(other.stack_id, other.from_pos, other.to_pos))
|