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
Alexandre Detiste 148f189a74
trim usage of six (#382)
This is artisanal manual craftwork :-)

     def mDone(self, button):
         if button == 0:        # "OK" or double click
-            if isinstance(self.tree.selection_key, six.string_types):
-                self.key = str(self.tree.selection_key)
-            else:
-                self.key = self.tree.selection_key
+            self.key = self.tree.selection_key
2024-09-18 20:33:10 -04:00

1056 lines
36 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 re
import time
from pysollib.game import Game
from pysollib.gamedb import GI, GameInfo, registerGame
from pysollib.hint import AbstractHint
from pysollib.layout import Layout
from pysollib.mfxutil import Image, Struct, kwdefault
from pysollib.mygettext import _
from pysollib.mygettext import ungettext
from pysollib.pysoltk import ANCHOR_NW, EVENT_HANDLED, bind
from pysollib.pysoltk import MfxCanvasImage, MfxCanvasText
from pysollib.pysoltk import MfxMessageDialog
from pysollib.settings import DEBUG, TOOLKIT
from pysollib.stack import \
InitialDealTalonStack, \
OpenStack
from pysollib.util import ANY_SUIT, NO_RANK
def factorial(x):
if x <= 1:
return 1
a = 1
for i in range(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 == 'kivy':
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()
def _calcMouseBind(self, binding_format):
return self.game.app.opt.calcCustomMouseButtonsBinding(binding_format)
# 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,
self._calcMouseBind("<{mouse_button1}>"),
self.__clickEventHandler
)
bind(
group,
self._calcMouseBind("<{mouse_button3}>"),
self.__controlclickEventHandler
)
bind(
group,
self._calcMouseBind("<Control-{mouse_button1}>"),
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)
if TOOLKIT == 'kivy':
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
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.getHighlightedCard(
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.images.cs
if cs.version == 6 or cs.mahjongg3d:
dx = l.XOFFSET
dy = -l.YOFFSET
d_x = cs.SHADOW_XOFFSET
d_y = cs.SHADOW_YOFFSET
if self.preview:
size_cap, r = 100, 2
if l.CW // r > size_cap or l.CH // r > size_cap:
r = max(l.CW, l.CH) // size_cap
# Fixme
dx, dy, d_x, d_y = dx // r, dy // r, d_x // r, d_y // r
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(key=lambda x: (x[0], x[2]-x[1]))
# 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 = {}, {}, {}, {}
# 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([_f for _f in above.keys() if _f])
below = tuple([_f for _f in below.keys() if _f])
left = tuple([_f for _f in left.keys() if _f])
right = tuple([_f for _f in right.keys() if _f])
# 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 == 'kivy':
x = -1000
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 range(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 range(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 range(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 range(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:
if TOOLKIT != 'kivy':
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(ii.cards) for ii 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
x, y = s.x, s.y
img = self.app.images.getHighlightedCard(
c1.deck, c1.suit, c1.rank, 'black')
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
# ************************************************************************
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 = type(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=list(range(3)), ranks=list(range(ranks)),
trumps=list(range(trumps)),
si={"decks": decks, "ncards": ncards})
gi.ncards = ncards
gi.rules_filename = "mahjongg.html"
registerGame(gi)
return gi