1
0
Fork 0
mirror of https://github.com/shlomif/PySolFC.git synced 2025-04-05 00:02:29 -04:00
PySolFC/pysollib/games/mahjongg/mahjongg.py
skomoroh dfe790a101 * changed GPL version to 3
* added config-file option sound_sample_buffer_size
* bugfixes


git-svn-id: file:///home/shlomif/Backup/svn-dumps/PySolFC/svnsync-repos/pysolfc/PySolFC/trunk@234 efabe8c0-fbe8-4139-b769-b5e6d273206e
2009-06-12 09:32:43 +00:00

1024 lines
34 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/>.
##
##---------------------------------------------------------------------------##
__all__ = []
# Imports
import sys, re
import time
#from tkFont import Font
from gettext import ungettext
# PySol imports
from pysollib.gamedb import registerGame, GameInfo, GI
from pysollib.util import *
from pysollib.mfxutil import kwdefault, Struct, Image
from pysollib.stack import *
from pysollib.game import Game
from pysollib.layout import Layout
from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint
from pysollib.settings import TOOLKIT, DEBUG
from pysollib.pysoltk import MfxCanvasText, MfxCanvasImage
from pysollib.pysoltk import bind, EVENT_HANDLED, ANCHOR_NW
from pysollib.pysoltk import MfxMessageDialog
def factorial(x):
if x <= 1:
return 1
a = 1
for i in xrange(x):
a *= (i+1)
return a
# /***********************************************************************
# //
# ************************************************************************/
class Mahjongg_Hint(AbstractHint):
# FIXME: no intelligence whatsoever is implemented here
def computeHints(self):
game = self.game
# get free stacks
stacks = []
for r in game.s.rows:
if r.cards and not r.basicIsBlocked():
stacks.append(r)
# find matching tiles
i = 0
for r in stacks:
for t in stacks[i+1:]:
if game.cardsMatch(r.cards[0], t.cards[0]):
# simple scoring...
##score = 10000 + r.id + t.id
rb = r.blockmap
tb = t.blockmap
score = \
10000 + \
1000 * (len(rb.below) + len(tb.below)) + \
len(rb.all_left) + len(rb.all_right) + \
len(tb.all_left) + len(tb.all_right)
self.addHint(score, 1, r, t)
i += 1
# /***********************************************************************
# //
# ************************************************************************/
#class Mahjongg_Foundation(AbstractFoundationStack):
class Mahjongg_Foundation(OpenStack):
def __init__(self, x, y, game, suit=ANY_SUIT, **cap):
kwdefault(cap, max_move=0, max_accept=0, max_cards=game.NCARDS)
OpenStack.__init__(self, x, y, game, **cap)
def acceptsCards(self, from_stack, cards):
# We do not accept any cards - pairs will get
# delivered by _dropPairMove() below.
return 0
def basicIsBlocked(self):
return 1
#def initBindings(self):
# pass
def _position(self, card):
#AbstractFoundationStack._position(self, card)
OpenStack._position(self, card)
fnds = self.game.s.foundations
cols = (3, 2, 1, 0)
for i in cols:
for j in range(9):
n = i*9+j
if fnds[n].cards:
fnds[n].group.tkraise()
return
def getHelp(self):
return ''
# /***********************************************************************
# //
# ************************************************************************/
class Mahjongg_RowStack(OpenStack):
def __init__(self, x, y, game, **cap):
kwdefault(cap, max_move=1, max_accept=1, max_cards=2,
base_rank=NO_RANK)
OpenStack.__init__(self, x, y, game, **cap)
def basicIsBlocked(self):
# any of above blocks
for stack in self.blockmap.above:
if stack.cards:
return 1
# any of left blocks - but we can try right as well
for stack in self.blockmap.left:
if stack.cards:
break
else:
return 0
# any of right blocks
for stack in self.blockmap.right:
if stack.cards:
return 1
return 0
def acceptsCards(self, from_stack, cards):
if not OpenStack.acceptsCards(self, from_stack, cards):
return 0
return self.game.cardsMatch(self.cards[0], cards[-1])
def canFlipCard(self):
return 0
def canDropCards(self, stacks):
return (None, 0)
def moveMove(self, ncards, to_stack, frames=-1, shadow=-1):
self._dropPairMove(ncards, to_stack, frames=-1, shadow=shadow)
def _dropPairMove(self, n, other_stack, frames=-1, shadow=-1):
##print 'drop:', self.id, other_stack.id
assert n == 1 and self.acceptsCards(other_stack, [other_stack.cards[-1]])
if not self.game.demo:
self.game.playSample("droppair", priority=200)
old_state = self.game.enterState(self.game.S_FILL)
c = self.cards[0]
if c.suit == 3:
if c.rank >= 8:
i = 35
elif c.rank >= 4:
i = 34
else:
i = 30+c.rank
elif c.rank == 9:
i = 27+c.suit
else:
i = c.suit*9+c.rank
f = self.game.s.foundations[i]
self.game.moveMove(n, self, f, frames=frames, shadow=shadow)
self.game.moveMove(n, other_stack, f, frames=frames, shadow=shadow)
self.game.leaveState(old_state)
self.fillStack()
other_stack.fillStack()
#
# Mahjongg special overrides
#
# Mahjongg special: we must preserve the relative stacking order
# to keep our pseudo 3D look.
def _position(self, card):
OpenStack._position(self, card)
#
if TOOLKIT == 'tk':
rows = [s for s in self.game.s.rows[:self.id] if s.cards]
if rows:
self.group.tkraise(rows[-1].group)
return
rows = [s for s in self.game.s.rows[self.id+1:] if s.cards]
if rows:
self.group.lower(rows[0].group)
return
elif TOOLKIT == 'gtk':
# FIXME (this is very slow)
for s in self.game.s.rows[self.id+1:]:
s.group.tkraise()
# In Mahjongg games type there are a lot of stacks, so we optimize
# and don't create bindings that are not used anyway.
def initBindings(self):
group = self.group
# FIXME: dirty hack to access the Stack's private methods
#bind(group, "<1>", self._Stack__clickEventHandler)
#bind(group, "<3>", self._Stack__controlclickEventHandler)
#bind(group, "<Control-1>", self._Stack__controlclickEventHandler)
#
bind(group, "<1>", self.__clickEventHandler)
bind(group, "<3>", self.__controlclickEventHandler)
bind(group, "<Control-1>", self.__controlclickEventHandler)
##bind(group, "<Enter>", self._Stack__enterEventHandler)
##bind(group, "<Leave>", self._Stack__leaveEventHandler)
def __defaultClickEventHandler(self, event, handler):
self.game.event_handled = True # for Game.undoHandler
if self.game.demo:
self.game.stopDemo(event)
if self.game.busy:
return EVENT_HANDLED
handler(event)
return EVENT_HANDLED
def __clickEventHandler(self, event):
##print 'click:', self.id
return self.__defaultClickEventHandler(event, self.clickHandler)
def __controlclickEventHandler(self, event):
return self.__defaultClickEventHandler(event, self.controlclickHandler)
def clickHandler(self, event):
game = self.game
drag = game.drag
# checks
if not self.cards:
return 1
card = self.cards[-1]
from_stack = drag.stack
if from_stack is self:
# remove selection
self.game.playSample("nomove")
self._stopDrag()
return 1
if self.basicIsBlocked():
### remove selection
##self.game.playSample("nomove")
return 1
# possible move
if from_stack:
if self.acceptsCards(from_stack, from_stack.cards):
self._stopDrag()
# this code actually moves the tiles
from_stack.playMoveMove(1, self, frames=0, sound=True)
return 1
drag.stack = self
self.game.playSample("startdrag")
# create the shade image (see stack.py, _updateShade)
if drag.shade_img:
#drag.shade_img.dtag(drag.shade_stack.group)
drag.shade_img.delete()
#game.canvas.delete(drag.shade_img)
drag.shade_img = None
img = game.app.images.getShadowCard(card.deck, card.suit, card.rank)
if img is None:
return 1
img = MfxCanvasImage(game.canvas, self.x, self.y, image=img,
anchor=ANCHOR_NW, group=self.group)
drag.shade_img = img
# raise/lower the shade image to the correct stacking order
img.tkraise(card.item)
drag.shade_stack = self
return 1
def cancelDrag(self, event=None):
if event is None:
self._stopDrag()
def _findCard(self, event):
# we need to override this because the shade may be hiding
# the tile (from Tk's stacking view)
return len(self.cards) - 1
def getBottomImage(self):
return None
# /***********************************************************************
# //
# ************************************************************************/
class AbstractMahjonggGame(Game):
Hint_Class = Mahjongg_Hint
RowStack_Class = Mahjongg_RowStack
GAME_VERSION = 3
NCARDS = 144
def getTiles(self):
# decode tile positions
L = self.L
assert L[0] == "0"
assert (len(L) - 1) % 3 == 0
tiles = []
max_tl, max_tx, max_ty = -1, -1, -1
t = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
for i in range(1, len(L), 3):
n = t.find(L[i])
level, height = n / 7, n % 7 + 1
tx = t.find(L[i+1])
ty = t.find(L[i+2])
assert n >= 0 and tx >= 0 and ty >= 0
max_tl = max(level + height - 1, max_tl)
max_tx = max(tx, max_tx)
max_ty = max(ty, max_ty)
for tl in range(level, level + height):
tiles.append((tl, tx, ty))
assert len(tiles) == self.NCARDS
#tiles.sort()
#tiles = tuple(tiles)
return tiles, max_tl, max_tx, max_ty
#
# game layout
#
def createGame(self):
tiles, max_tl, max_tx, max_ty = self.getTiles()
# start layout
l, s = Layout(self), self.s
show_removed = self.app.opt.mahjongg_show_removed
##dx, dy = 2, -2
##dx, dy = 3, -3
cs = self.app.cardset
if cs.version >= 6:
dx = l.XOFFSET
dy = -l.YOFFSET
d_x = cs.SHADOW_XOFFSET
d_y = cs.SHADOW_YOFFSET
if self.preview:
# Fixme
dx, dy, d_x, d_y = dx/2, dy/2, d_x/2, d_y/2
self._delta_x, self._delta_y = dx, -dy
else:
dx = 3
dy = -3
d_x = 0
d_y = 0
self._delta_x, self._delta_y = 0, 0
#print dx, dy, d_x, d_y, cs.version
font = self.app.getFont("canvas_default")
# width of self.texts.info
#ti_width = Font(self.canvas, font).measure(_('Remaining'))
ti_width = 80
# set window size
dxx, dyy = abs(dx) * (max_tl+1), abs(dy) * (max_tl+1)
# foundations dxx dyy
if self.NCARDS > 144:
fdxx = abs(dx)*8
fdyy = abs(dy)*8
else:
fdxx = abs(dx)*4
fdyy = abs(dy)*4
cardw, cardh = l.CW - d_x, l.CH - d_y
if show_removed:
left_margin = l.XM + 4*cardw+fdxx+d_x + l.XM
else:
left_margin = l.XM
tableau_width = (max_tx+2)*cardw/2+dxx+d_x
right_margin = l.XM+ti_width+l.XM
w = left_margin + tableau_width + right_margin
h = l.YM + dyy + (max_ty + 2) * cardh / 2 + d_y + l.YM
if show_removed:
h = max(h, l.YM+fdyy+cardh*9+d_y+l.YM)
self.setSize(w, h)
# set game extras
self.check_dist = l.CW*l.CW + l.CH*l.CH # see _getClosestStack()
# sort tiles (for 3D)
tiles.sort(lambda a, b:
cmp(a[0], b[0]) or
cmp(-a[1]+a[2], -b[1]+b[2])
)
# create a row stack for each tile and compute the tilemap
tilemap = {}
x0 = left_margin
y0 = l.YM + dyy
for level, tx, ty in tiles:
#print level, tx, ty
x = x0 + (tx * cardw) / 2 + level * dx
y = y0 + (ty * cardh) / 2 + level * dy
stack = self.RowStack_Class(x, y, self)
##stack.G = (level, tx, ty)
stack.CARD_XOFFSET = dx
stack.CARD_YOFFSET = dy
s.rows.append(stack)
# tilemap - each tile covers 4 positions
tilemap[(level, tx, ty)] = stack
tilemap[(level, tx+1, ty)] = stack
tilemap[(level, tx, ty+1)] = stack
tilemap[(level, tx+1, ty+1)] = stack
# compute blockmap
for stack in s.rows:
level, tx, ty = tiles[stack.id]
above, below, left, right, up, bottom = {}, {}, {}, {}, {}, {}
# above blockers
for tl in range(level+1, level+2):
above[tilemap.get((tl, tx, ty))] = 1
above[tilemap.get((tl, tx+1, ty))] = 1
above[tilemap.get((tl, tx, ty+1))] = 1
above[tilemap.get((tl, tx+1, ty+1))] = 1
#
for tl in range(level):
below[tilemap.get((tl, tx, ty))] = 1
below[tilemap.get((tl, tx+1, ty))] = 1
below[tilemap.get((tl, tx, ty+1))] = 1
below[tilemap.get((tl, tx+1, ty+1))] = 1
# left blockers
left[tilemap.get((level, tx-1, ty))] = 1
left[tilemap.get((level, tx-1, ty+1))] = 1
# right blockers
right[tilemap.get((level, tx+2, ty))] = 1
right[tilemap.get((level, tx+2, ty+1))] = 1
# up blockers
##up[tilemap.get((level, tx, ty-1))] = 1
##up[tilemap.get((level, tx+1, ty-1))] = 1
# bottom blockers
##bottom[tilemap.get((level, tx, ty+2))] = 1
##bottom[tilemap.get((level, tx+1, ty+2))] = 1
# sanity check - assert that there are no overlapping tiles
assert tilemap.get((level, tx, ty)) is stack
assert tilemap.get((level, tx+1, ty)) is stack
assert tilemap.get((level, tx, ty+1)) is stack
assert tilemap.get((level, tx+1, ty+1)) is stack
#
above = tuple(filter(None, above.keys()))
below = tuple(filter(None, below.keys()))
left = tuple(filter(None, left.keys()))
right = tuple(filter(None, right.keys()))
##up = tuple(filter(None, up.keys()))
##bottom = tuple(filter(None, bottom.keys()))
# assemble
stack.blockmap = Struct(
above = above,
below = below,
left = left,
right = right,
##up = up,
##bottom = bottom,
all_left = None,
all_right = None,
)
def get_all_left(s):
if s.blockmap.all_left is None:
s.blockmap.all_left = {}
for t in s.blockmap.left:
if t.blockmap.all_left is None:
get_all_left(t)
s.blockmap.all_left.update(t.blockmap.all_left)
s.blockmap.all_left[t] = 1
def get_all_right(s):
if s.blockmap.all_right is None:
s.blockmap.all_right = {}
for t in s.blockmap.right:
if t.blockmap.all_right is None:
get_all_right(t)
s.blockmap.all_right.update(t.blockmap.all_right)
s.blockmap.all_right[t] = 1
for r in s.rows:
get_all_left(r)
get_all_right(r)
for r in s.rows:
r.blockmap.all_left = tuple(r.blockmap.all_left.keys())
r.blockmap.all_right = tuple(r.blockmap.all_right.keys())
# create other stacks
for i in range(4):
for j in range(9):
if show_removed:
x = l.XM+i*cardw
y = l.YM+fdyy+j*cardh
else:
if TOOLKIT == 'tk':
x = -l.XS-self.canvas.xmargin
y = l.YM+dyy
elif TOOLKIT == 'gtk':
# FIXME
x = self.width -l.XS
y = self.height - l.YS
stack = Mahjongg_Foundation(x, y, self)
if show_removed:
stack.CARD_XOFFSET = dx
stack.CARD_YOFFSET = dy
s.foundations.append(stack)
self.texts.info = MfxCanvasText(self.canvas,
self.width - l.XM - ti_width,
l.YM + dyy,
anchor="nw", font=font)
# the Talon is invisble
s.talon = InitialDealTalonStack(-l.XS-self.canvas.xmargin,
self.height-dyy, self)
# Define stack groups
l.defaultStackGroups()
#
# game overrides
#
def _shuffleHook(self, cards):
if self.app.opt.mahjongg_create_solvable == 0:
return cards
# try to create a solvable game
if self.app.opt.mahjongg_create_solvable == 1:
# easy
return self._shuffleHook1(cards[:])
# hard
new_cards = self._shuffleHook2(self.s.rows, cards)
if new_cards is None:
return cards
return new_cards
def _shuffleHook1(self, cards):
# old version; it generate a very easy layouts
old_cards = cards[:]
rows = self.s.rows
def is_blocked(s, new_cards):
# any of above blocks
for stack in s.blockmap.above:
if new_cards[stack.id] is None:
return True
# any of left blocks - but we can try right as well
for stack in s.blockmap.left:
if new_cards[stack.id] is None:
break
else:
return False
# any of right blocks
for stack in s.blockmap.right:
if new_cards[stack.id] is None:
return True
return False
def create_solvable(cards, new_cards):
if not cards:
return new_cards
# select two matching cards
c1 = cards[0]
del cards[0]
c2 = None
for i in xrange(len(cards)):
if self.cardsMatch(c1, cards[i]):
c2 = cards[i]
del cards[i]
break
#
free_stacks = [] # none-blocked stacks
for r in rows:
if new_cards[r.id] is None and not is_blocked(r, new_cards):
free_stacks.append(r)
if len(free_stacks) < 2:
return None # try another way
#
i = factorial(len(free_stacks))/2/factorial(len(free_stacks)-2)
old_pairs = []
for j in xrange(i):
nc = new_cards[:]
while True:
# create uniq pair
r1 = self.random.randrange(0, len(free_stacks))
r2 = self.random.randrange(0, len(free_stacks)-1)
if r2 >= r1:
r2 += 1
if (r1, r2) not in old_pairs and (r2, r1) not in old_pairs:
old_pairs.append((r1, r2))
break
# add two selected cards to new_cards
s1 = free_stacks[r1]
s2 = free_stacks[r2]
nc[s1.id] = c1
nc[s2.id] = c2
# check if this layout is solvable (backtracking)
nc = create_solvable(cards[:], nc)
if nc:
return nc
return None # try another way
new_cards = create_solvable(cards, [None]*len(cards))
if new_cards:
new_cards.reverse()
return new_cards
print 'oops! can\'t create a solvable game'
return old_cards
def _shuffleHook2(self, rows, cards):
start_time = time.time()
iters = [0]
# limitations
max_time = 5.0 # seconds
max_iters = 2*len(cards)
def is_suitable(stack, cards):
for s in stack.blockmap.below:
if cards[s.id] == 1:
continue
# check if below stacks are non-empty
if cards[s.id] is None:
return False
for s in stack.blockmap.left:
if cards[s.id] == 1:
continue
if cards[s.id] is None:
for t in s.blockmap.all_left:
if cards[t.id] == 1:
continue
if cards[t.id] is not None:
# we have empty stack between two non-empty
return False
for s in stack.blockmap.right:
if cards[s.id] == 1:
continue
if cards[s.id] is None:
for t in s.blockmap.all_right:
if cards[t.id] == 1:
continue
if cards[t.id] is not None:
# we have empty stack between two non-empty
return False
return True
def create_solvable(cards, new_cards):
iters[0] += 1
if iters[0] > max_iters:
return None
if time.time() - start_time > max_time:
return None
if not cards:
return new_cards
nc = new_cards[:]
# select two matching cards
c1 = cards[0]
del cards[0]
c2 = None
for i in xrange(len(cards)):
if self.cardsMatch(c1, cards[i]):
c2 = cards[i]
del cards[i]
break
# find suitable stacks
## suitable_stacks = []
## for r in rows:
## if nc[r.id] is None and is_suitable(r, nc):
## suitable_stacks.append(r)
suitable_stacks = [r for r in rows
if nc[r.id] is None and is_suitable(r, nc)]
old_pairs = []
i = factorial(len(suitable_stacks))/2/factorial(len(suitable_stacks)-2)
for j in xrange(i):
if iters[0] > max_iters:
return None
if time.time() - start_time > max_time:
return None
# select two suitable stacks
while True:
# create a uniq pair
r1 = self.random.randrange(0, len(suitable_stacks))
r2 = self.random.randrange(0, len(suitable_stacks))
if r1 == r2:
continue
if (r1, r2) not in old_pairs and (r2, r1) not in old_pairs:
old_pairs.append((r1, r2))
break
s1 = suitable_stacks[r1]
s2 = suitable_stacks[r2]
# check if s1 don't block s2
nc[s1.id] = c1
if not is_suitable(s2, nc):
nc[s1.id] = None
continue
nc[s2.id] = c2
# check if this layout is solvable (backtracking)
ret = create_solvable(cards[:], nc)
if ret:
ret = [x for x in ret if x != 1]
return ret
nc[s1.id] = nc[s2.id] = None # try another way
return None
new_cards = [None]*len(self.s.rows) # None - empty stack, 1 - non-used
drows = dict.fromkeys(rows) # optimization
for r in self.s.rows:
if r not in drows:
new_cards[r.id] = 1
del drows
while True:
ret = create_solvable(cards[:], new_cards)
if DEBUG:
print 'create_solvable time:', time.time() - start_time
if ret:
ret.reverse()
return ret
if time.time() - start_time > max_time or \
iters[0] <= max_iters:
print 'oops! can\'t create a solvable game'
return None
iters = [0]
print 'oops! can\'t create a solvable game'
return None
def _mahjonggShuffle(self):
talon = self.s.talon
rows = []
cards = []
for r in self.s.rows:
if r.cards:
rows.append(r)
cards.append(r.cards[0])
if not rows:
return
if self.app.opt.mahjongg_create_solvable == 0:
self.playSample('turnwaste')
old_state = self.enterState(self.S_FILL)
self.saveSeedMove()
for r in rows:
self.moveMove(1, r, talon, frames=0)
self.shuffleStackMove(talon)
for r in rows:
self.moveMove(1, talon, r, frames=0)
self.leaveState(old_state)
self.finishMove()
return
self.playSample('turnwaste')
old_state = self.enterState(self.S_FILL)
self.saveSeedMove()
new_cards = self._shuffleHook2(rows, cards)
if new_cards is None:
d = MfxMessageDialog(self.top, title=_('Warning'),
text=_('''\
Sorry, I can\'t find
a solvable configuration.'''),
bitmap='warning')
self.leaveState(old_state)
##self.finishMove()
# hack
am = self.moves.current[0]
am.undo(self) # restore random
self.moves.current = []
return
self.stats.shuffle_moves += 1
# move new_cards to talon
for c in new_cards:
for r in rows:
if r.cards and r.cards[0] is c:
self.moveMove(1, r, talon, frames=0)
break
# deal
for r in rows:
self.moveMove(1, talon, r, frames=0)
self.leaveState(old_state)
self.finishMove()
def canShuffle(self):
return True
def startGame(self):
assert len(self.s.talon.cards) == self.NCARDS
#self.s.talon.dealRow(rows = self.s.rows, frames = 0)
n = 12
self.s.talon.dealRow(rows=self.s.rows[:self.NCARDS-n], frames=0)
self.startDealSample()
self.s.talon.dealRow(rows=self.s.rows[self.NCARDS-n:])
assert len(self.s.talon.cards) == 0
def isGameWon(self):
return sum([len(f.cards) for f in self.s.foundations]) == self.NCARDS
def shallHighlightMatch(self, stack1, card1, stack2, card2):
if stack1.basicIsBlocked() or stack2.basicIsBlocked():
return 0
return self.cardsMatch(card1, card2)
def getAutoStacks(self, event=None):
return ((), (), ())
def updateText(self):
if self.preview > 1 or self.texts.info is None:
return
# find matching tiles
stacks = []
for r in self.s.rows:
if r.cards and not r.basicIsBlocked():
stacks.append(r)
f, i = 0, 0
for r in stacks:
n = 0
for t in stacks[i+1:]:
if self.cardsMatch(r.cards[0], t.cards[0]):
n += 1
#if n == 3: n = 1
#elif n == 2: n = 0
n = n % 2
f += n
i += 1
if f == 0:
f = _('No Free\nMatching\nPairs')
else:
f = ungettext('%d Free\nMatching\nPair',
'%d Free\nMatching\nPairs',
f) % f
t = sum([len(i.cards) for i in self.s.foundations])
r1 = ungettext('%d\nTile\nRemoved\n\n',
'%d\nTiles\nRemoved\n\n',
t) % t
r2 = ungettext('%d\nTile\nRemaining\n\n',
'%d\nTiles\nRemaining\n\n',
self.NCARDS - t) % (self.NCARDS - t)
t = r1 + r2 + f
self.texts.info.config(text=t)
#
# Mahjongg special overrides
#
def getHighlightPilesStacks(self):
# Mahjongg special: highlight all moveable tiles
return ((self.s.rows, 1),)
def _highlightCards(self, info, sleep=1.5, delta=(1,1,1,1)):
if not Image:
delta = (-self._delta_x, 0, 0, -self._delta_y)
return Game._highlightCards(self, info, sleep=sleep, delta=delta)
if not info:
return 0
if self.pause:
return 0
self.stopWinAnimation()
items = []
for s, c1, c2, color in info:
assert c1 is c2
assert c1 in s.cards
tkraise = False
x, y = s.x, s.y
img = self.app.images.getHighlightCard(c1.deck, c1.suit, c1.rank)
if img is None:
continue
img = MfxCanvasImage(self.canvas, x, y, image=img,
anchor=ANCHOR_NW, group=s.group)
if self.drag.stack and s is self.drag.stack:
img.tkraise(self.drag.shade_img)
else:
img.tkraise(c1.item)
items.append(img)
if not items:
return 0
self.canvas.update_idletasks()
if sleep:
self.sleep(sleep)
items.reverse()
for r in items:
r.delete()
self.canvas.update_idletasks()
return EVENT_HANDLED
else:
# remove items later (find_card_dialog)
return items
def getCardFaceImage(self, deck, suit, rank):
if suit == 3:
cs = self.app.cardset
if len(cs.ranks) >= 12 and len(cs.suits) >= 4:
# make Mahjongg type games playable with other cardsets
if rank >= 8: # flower
suit = 1
rank = len(cs.ranks) - 2
elif rank >= 4: # season
rank = max(10, len(cs.ranks) - 3)
else: # wind
suit = rank
rank = len(cs.ranks) - 1
return self.app.images.getFace(deck, suit, rank)
def getCardBackImage(self, deck, suit, rank):
# We avoid screen updates caused by flipping cards - all
# cards are face up anyway. The Talon should be invisible
# or else the top tile of the Talon will be visible during
# game start.
return self.getCardFaceImage(deck, suit, rank)
def _createCard(self, id, deck, suit, rank, x, y):
##if deck >= 1 and suit == 3 and rank >= 4:
if deck%4 and suit == 3 and rank >= 4:
return None
return Game._createCard(self, id, deck, suit, rank, x, y)
def _getClosestStack(self, cx, cy, stacks, dragstack):
closest, cdist = None, 999999999
# Since we only compare distances,
# we don't bother to take the square root.
for stack in stacks:
dist = (stack.x - cx)**2 + (stack.y - cy)**2
if dist < cdist:
# Mahjongg special: if the stack is very close, do
# not consider blocked stacks
if dist > self.check_dist or not stack.basicIsBlocked():
closest, cdist = stack, dist
return closest
#
# Mahjongg extras
#
def cardsMatch(self, card1, card2):
if card1.suit != card2.suit:
return 0
if card1.suit == 3:
if card1.rank >= 8:
return card2.rank >= 8
if card1.rank >= 4:
return 7 >= card2.rank >= 4
return card1.rank == card2.rank
## mahjongg util
def comp_cardset(ncards):
# calc decks, ranks & trumps
assert ncards % 4 == 0
assert 0 < ncards <= 288 # ???
decks = 1
cards = ncards/4
if ncards > 144:
assert ncards % 8 == 0
decks = 2
cards = cards/2
ranks, trumps = divmod(cards, 3)
if ranks > 10:
trumps += (ranks-10)*3
ranks = 10
if trumps > 4:
trumps = 4+(trumps-4)*4
assert 0 <= ranks <= 10 and 0 <= trumps <= 12
return decks, ranks, trumps
# /***********************************************************************
# // register a Mahjongg type game
# ************************************************************************/
from new import classobj
def r(id, short_name, name=None, ncards=144, layout=None):
assert layout
if not name:
name = "Mahjongg " + short_name
classname = re.sub('\W', '', name)
# create class
gameclass = classobj(classname, (AbstractMahjonggGame,), {})
gameclass.L = layout
gameclass.NCARDS = ncards
decks, ranks, trumps = comp_cardset(ncards)
gi = GameInfo(id, gameclass, name,
GI.GT_MAHJONGG, 4*decks, 0, ##GI.SL_MOSTLY_SKILL,
category=GI.GC_MAHJONGG, short_name=short_name,
suits=range(3), ranks=range(ranks), trumps=range(trumps),
si={"decks": decks, "ncards": ncards})
gi.ncards = ncards
gi.rules_filename = "mahjongg.html"
registerGame(gi)
return gi