#!/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 . # # --------------------------------------------------------------------------- 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 from six.moves import range 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, "", self._Stack__controlclickEventHandler) # bind( group, self._calcMouseBind("<{mouse_button1}>"), self.__clickEventHandler ) bind( group, self._calcMouseBind("<{mouse_button3}>"), self.__controlclickEventHandler ) bind( group, self._calcMouseBind(""), self.__controlclickEventHandler ) # bind(group, "", self._Stack__enterEventHandler) # bind(group, "", 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