1
0
Fork 0
mirror of https://github.com/shlomif/PySolFC.git synced 2025-04-05 00:02:29 -04:00

+ 3 new games

+ bind undo/redo to mouse click on background
+ `find card' dialog (unfinished)


git-svn-id: https://pysolfc.svn.sourceforge.net/svnroot/pysolfc/PySolFC/trunk@32 39dd0a4e-7c14-0410-91b3-c4f2d318f732
This commit is contained in:
skomoroh 2006-07-30 21:14:39 +00:00
parent 7ab84b4c32
commit 3c45181990
12 changed files with 390 additions and 33 deletions

View file

@ -45,6 +45,7 @@ from pysolrandom import constructRandom
from version import VERSION
from settings import PACKAGE, PACKAGE_URL
from settings import TOP_TITLE
from gamedb import GI
# stats imports
from stats import PysolStatsFormatter
@ -66,6 +67,7 @@ from pysoltk import ColorsDialog
from pysoltk import FontsDialog
from pysoltk import EditTextDialog
from pysoltk import TOOLBAR_BUTTONS
from pysoltk import create_find_card_dialog, connect_game_find_card_dialog, destroy_find_card_dialog
from help import helpAbout, helpHTML
gettext = _
@ -95,6 +97,7 @@ class PysolMenubarActions:
quickplay = 0,
demo = 0,
highlight_piles = 0,
find_card = 0,
rules = 0,
pause = 0,
)
@ -188,9 +191,12 @@ class PysolMenubarActions:
tkopt.splashscreen.set(opt.splashscreen)
tkopt.sticky_mouse.set(opt.sticky_mouse)
tkopt.negative_bottom.set(opt.negative_bottom)
for w in TOOLBAR_BUTTONS:
tkopt.toolbar_vars[w].set(opt.toolbar_vars[w])
if game.gameinfo.category == GI.GC_FRENCH:
connect_game_find_card_dialog(game)
else:
destroy_find_card_dialog()
# will get called after connectGame()
def updateRecentGamesMenu(self, gameids):
@ -274,6 +280,8 @@ class PysolMenubarActions:
ms.quickplay = 1
if opt.highlight_piles and game.getHighlightPilesStacks():
ms.highlight_piles = 1
if game.gameinfo.category == GI.GC_FRENCH:
ms.find_card = 1
if game.app.getGameRulesFilename(game.id): # note: this may return ""
ms.rules = 1
if not game.finished:
@ -301,6 +309,7 @@ class PysolMenubarActions:
# Assist menu
self.setMenuState(ms.hint, "assist.hint")
self.setMenuState(ms.highlight_piles, "assist.highlightpiles")
self.setMenuState(ms.find_card, "assist.findcard")
self.setMenuState(ms.demo, "assist.demo")
self.setMenuState(ms.demo, "assist.demoallgames")
# Options menu
@ -589,6 +598,10 @@ class PysolMenubarActions:
if self._cancelDrag(break_pause=False): return
self.mPlayerStats(mode=106)
def mFindCard(self, *args):
create_find_card_dialog(self.game.top, self.game,
self.app.getFindCardImagesDir())
def mEditGameComment(self, *args):
if self._cancelDrag(break_pause=False): return
game, gi = self.game, self.game.gameinfo

View file

@ -892,6 +892,9 @@ class Application:
return None
return d
def getFindCardImagesDir(self):
return self._getImagesDir('cards')
def getToolbarImagesDir(self):
if self.opt.toolbar_size:
size = 'large'

View file

@ -142,6 +142,7 @@ class Game:
#
data = [], # raw data
)
self.event_handled = False # if click event handled by Stack (???)
self.reset()
# main constructor
@ -214,9 +215,13 @@ class Game:
def initBindings(self):
# note: a Game is only allowed to bind self.canvas and not to self.top
bind(self.canvas, "<1>", self.clickHandler)
##bind(self.canvas, "<1>", self.clickHandler)
bind(self.canvas, "<2>", self.clickHandler)
bind(self.canvas, "<3>", self.clickHandler)
##bind(self.canvas, "<3>", self.clickHandler)
##bind(self.canvas, "<Double-1>", self.undoHandler)
##bind(self.canvas, "<Double-1>", self.undoHandler)
bind(self.canvas, "<1>", self.undoHandler)
bind(self.canvas, "<3>", self.redoHandler)
bind(self.top, '<Unmap>', self._unmapHandler)
def __createCommon(self, app):
@ -721,12 +726,29 @@ class Game:
#
# UI & graphics support
#
def clickHandler(self, *args):
def _defaultHandler(self):
self.interruptSleep()
self.deleteStackDesc()
if self.demo:
self.stopDemo()
def clickHandler(self, event):
self._defaultHandler()
self.event_handled = False
return EVENT_PROPAGATE
def undoHandler(self, event):
self._defaultHandler()
if not self.event_handled:
self.app.menubar.mUndo()
self.event_handled = False
return EVENT_PROPAGATE
def redoHandler(self, event):
self._defaultHandler()
if not self.event_handled:
self.app.menubar.mRedo()
self.event_handled = False
return EVENT_PROPAGATE
def updateStatus(self, **kw):
@ -1392,6 +1414,16 @@ for %d moves.
return self.dealCards(sound=sound)
return 0
## for find_card_dialog
def highlightCard(self, suit, rank):
col = self.app.opt.highlight_samerank_colors[3]
info = []
for s in self.allstacks:
for c in s.cards:
if c.suit == suit and c.rank == rank:
if s.basicShallHighlightSameRank(c):
info.append((s, c, c, col))
return self._highlightCards(info, 0)
### highlight all moveable piles
def getHighlightPilesStacks(self):
@ -1429,12 +1461,15 @@ for %d moves.
if not items:
return 0
self.canvas.update_idletasks()
self.sleep(sleep)
items.reverse()
for r in items:
r.delete()
self.canvas.update_idletasks()
return EVENT_HANDLED
if sleep:
self.sleep(sleep)
items.reverse()
for r in items:
r.delete()
self.canvas.update_idletasks()
return EVENT_HANDLED
else:
return items
def highlightNotMatching(self):
if self.demo:

View file

@ -108,12 +108,17 @@ class DoubleKlondikeByThrees(DoubleKlondike):
# /***********************************************************************
# // Gargantua (Double Klondike with one redeal)
# // Pantagruel
# ************************************************************************/
class Gargantua(DoubleKlondike):
def createGame(self):
DoubleKlondike.createGame(self, max_rounds=2)
class Pantagruel(DoubleKlondike):
RowStack_Class = AC_RowStack
def createGame(self):
DoubleKlondike.createGame(self, max_rounds=1)
# /***********************************************************************
# // Harp (Double Klondike with 10 non-king rows and no redeal)
@ -311,5 +316,7 @@ registerGame(GameInfo(562, Delivery, "Delivery",
registerGame(GameInfo(590, ChineseKlondike, "Chinese Klondike",
GI.GT_KLONDIKE, 3, -1, GI.SL_BALANCED,
suits=(0, 1, 2) ))
registerGame(GameInfo(591, Pantagruel, "Pantagruel",
GI.GT_KLONDIKE, 2, 0, GI.SL_BALANCED))

View file

@ -112,6 +112,9 @@ class Mahjongg_Foundation(OpenStack):
fnds[n].group.tkraise()
return
def getHelp(self):
return ''
# /***********************************************************************
# //
@ -211,6 +214,7 @@ class Mahjongg_RowStack(OpenStack):
bind(group, "<Control-1>", self.__controlclickEventHandler)
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:
@ -527,7 +531,7 @@ class AbstractMahjonggGame(Game):
#
i = factorial(len(free_stacks))/2/factorial(len(free_stacks)-2)
old_pairs = []
for _ in xrange(i):
for j in xrange(i):
nc = new_cards[:]
while True:
# create uniq pair

View file

@ -394,6 +394,7 @@ class MountOlympus_RowStack(SS_RowStack):
class MountOlympus(Game):
RowStack_Class = MountOlympus_RowStack
def createGame(self):
# create layout
@ -415,7 +416,7 @@ class MountOlympus(Game):
x += l.XS
x, y = l.XM, l.YM+2*l.YS
for i in range(9):
s.rows.append(MountOlympus_RowStack(x, y, self, dir=-2))
s.rows.append(self.RowStack_Class(x, y, self, dir=-2))
x += l.XS
s.talon=DealRowTalonStack(l.XM, l.YM, self)
l.createText(s.talon, 's')
@ -446,7 +447,16 @@ class MountOlympus(Game):
(card1.rank + 2 == card2.rank or card2.rank + 2 == card1.rank))
class Zeus_RowStack(MountOlympus_RowStack):
def acceptsCards(self, from_stack, cards):
if not MountOlympus_RowStack.acceptsCards(self, from_stack, cards):
return False
if not self.cards:
return cards[0].rank in (QUEEN, KING)
return True
class Zeus(MountOlympus):
RowStack_Class = Zeus_RowStack
def startGame(self):
self.s.talon.dealRow(rows=self.s.foundations, frames=0)
self.startDealSample()

View file

@ -183,7 +183,7 @@ class Pyramid(Game):
w = l.XM + max_rows*l.XS
h = l.YM + 4*l.YS
if reserves:
h += l.YS+4*l.YOFFSET
h += l.YS+2*l.YOFFSET
self.setSize(w, h)
# create stacks
@ -331,6 +331,53 @@ class Thirteen(Pyramid):
self.s.talon.dealCards() # deal first card to WasteStack
# /***********************************************************************
# // Thirteens
# ************************************************************************/
class Thirteens(Pyramid):
def createGame(self):
# create layout
l, s = Layout(self), self.s
# set window
self.setSize(l.XM+5*l.XS, l.YM+4*l.YS)
# create stacks
x, y = l.XM, l.YM
for i in range(2):
x = l.XM
for j in range(5):
s.rows.append(Giza_Reserve(x, y, self, max_accept=1))
x += l.XS
y += l.YS
x, y = l.XM, self.height-l.YS
s.talon = TalonStack(x, y, self)
l.createText(s.talon, 'n')
x, y = self.width-l.XS, self.height-l.YS
s.foundations.append(Pyramid_Foundation(x, y, self,
suit=ANY_SUIT, dir=0, base_rank=ANY_RANK,
max_move=0, max_cards=52))
# define stack-groups
l.defaultStackGroups()
def startGame(self):
self.startDealSample()
self.s.talon.dealRow()
def fillStack(self, stack):
if stack in self.s.rows:
if not stack.cards and self.s.talon.cards:
old_state = self.enterState(self.S_FILL)
self.s.talon.flipMove()
self.s.talon.moveMove(1, stack)
self.leaveState(old_state)
# register the game
registerGame(GameInfo(38, Pyramid, "Pyramid",
GI.GT_PAIRING_TYPE, 1, 2, GI.SL_MOSTLY_LUCK))
@ -338,8 +385,10 @@ registerGame(GameInfo(193, RelaxedPyramid, "Relaxed Pyramid",
GI.GT_PAIRING_TYPE | GI.GT_RELAXED, 1, 2, GI.SL_MOSTLY_LUCK))
##registerGame(GameInfo(44, Thirteen, "Thirteen",
## GI.GT_PAIRING_TYPE, 1, 0))
registerGame(GameInfo(591, Giza, "Giza",
registerGame(GameInfo(592, Giza, "Giza",
GI.GT_PAIRING_TYPE | GI.GT_OPEN, 1, 0, GI.SL_BALANCED))
registerGame(GameInfo(593, Thirteens, "Thirteens",
GI.GT_PAIRING_TYPE, 1, 0, GI.SL_LUCK))

View file

@ -334,6 +334,7 @@ class IdleAces(Game):
# /***********************************************************************
# // Lady of the Manor
# // Archway
# ************************************************************************/
class LadyOfTheManor_RowStack(BasicRowStack):

View file

@ -38,9 +38,12 @@
import sys, os, re, time, types
import random
##----------------------------------------------------------------------
class PysolRandom(random.Random):
# /***********************************************************************
# // system based random (need python >= 2.3)
# ************************************************************************/
class SysRandom(random.Random):
#MAX_SEED = 0L
#MAX_SEED = 0xffffffffffffffffL # 64 bits
MAX_SEED = 100000000000000000000L # 20 digits
@ -91,7 +94,7 @@ class PysolRandom(random.Random):
# // We use a seed of type long in the range [0, MAX_SEED].
# ************************************************************************/
class PysolRandomMFX:
class MFXRandom:
MAX_SEED = 0L
ORIGIN_UNKNOWN = 0
@ -188,12 +191,36 @@ class PysolRandomMFX:
seq[n], seq[j] = seq[j], seq[n]
n = n - 1
# /***********************************************************************
# // Linear Congruential random generator
# //
# // Knuth, Donald.E., "The Art of Computer Programming,", Vol 2,
# // Seminumerical Algorithms, Third Edition, Addison-Wesley, 1998,
# // p. 106 (line 26) & p. 108
# ************************************************************************/
class LCRandom64(MFXRandom):
MAX_SEED = 0xffffffffffffffffL # 64 bits
def str(self, seed):
s = repr(long(seed))
if s[-1:] == "L":
s = s[:-1]
s = "0"*(20-len(s)) + s
return s
def random(self):
self.seed = (self.seed*6364136223846793005L + 1L) & self.MAX_SEED
return ((self.seed >> 21) & 0x7fffffffL) / 2147483648.0
# /***********************************************************************
# // Linear Congruential random generator
# // In PySol this is only used for 0 <= seed <= 32000.
# ************************************************************************/
class LCRandom31(PysolRandomMFX):
class LCRandom31(MFXRandom):
MAX_SEED = 0x7fffffffL # 31 bits
def str(self, seed):
@ -207,6 +234,9 @@ class LCRandom31(PysolRandomMFX):
self.seed = (self.seed*214013L + 2531011L) & self.MAX_SEED
return a + (int(self.seed >> 16) % (b+1-a))
# select
##PysolRandom = LCRandom64
PysolRandom = SysRandom
# /***********************************************************************
# // PySol support code

View file

@ -895,6 +895,7 @@ class Stack:
#
def __defaultClickEventHandler(self, event, handler, start_drag=0, cancel_drag=1):
self.game.event_handled = True # for Game.clickHandler
if self.game.demo:
self.game.stopDemo(event)
self.game.interruptSleep()

View file

@ -315,6 +315,7 @@ class PysolMenubar(PysolMenubarActions):
menu = MfxMenu(self.__menubar, label=n_("&Assist"))
menu.add_command(label=n_("&Hint"), command=self.mHint, accelerator="H")
menu.add_command(label=n_("Highlight p&iles"), command=self.mHighlightPiles, accelerator="I")
menu.add_command(label=n_("Find card"), command=self.mFindCard, accelerator="F")
menu.add_separator()
menu.add_command(label=n_("&Demo"), command=self.mDemo, accelerator=m+"D")
menu.add_command(label=n_("Demo (&all games)"), command=self.mMixedDemo)
@ -417,6 +418,7 @@ class PysolMenubar(PysolMenubarActions):
##self._bindKey("", "Shift_L", self.mHighlightPiles)
##self._bindKey("", "Shift_R", self.mHighlightPiles)
self._bindKey("", "i", self.mHighlightPiles)
self._bindKey("", "f", self.mFindCard)
self._bindKey(ctrl, "d", self.mDemo)
self._bindKey(ctrl, "e", self.mSelectCardsetDialog)
self._bindKey(ctrl, "b", self.mOptChangeCardback) # undocumented

View file

@ -39,6 +39,9 @@ __all__ = ['MfxMessageDialog',
'MfxTooltip',
'MfxScrolledCanvas',
'StackDesc',
'create_find_card_dialog',
'connect_game_find_card_dialog',
'destroy_find_card_dialog',
]
# imports
@ -55,7 +58,7 @@ from tkconst import EVENT_HANDLED, EVENT_PROPAGATE
from tkutil import after, after_idle, after_cancel
from tkutil import bind, unbind_destroy, makeImage
from tkutil import makeToplevel, setTransient
from tkcanvas import MfxCanvas
from tkcanvas import MfxCanvas, MfxCanvasGroup, MfxCanvasImage, MfxCanvasRectangle
# /***********************************************************************
@ -78,23 +81,24 @@ class MfxDialog: # ex. _ToplevelDialog
bind(self.top, "WM_DELETE_WINDOW", self.wmDeleteWindow)
#
def mainloop(self, focus=None, timeout=0):
def mainloop(self, focus=None, timeout=0, transient=True):
bind(self.top, "<Escape>", self.mCancel)
bind(self.top, '<Alt-Key>', self.altKeyEvent) # for accelerators
if focus is not None:
focus.focus()
setTransient(self.top, self.parent)
try:
self.top.grab_set()
except Tkinter.TclError:
if traceback: traceback.print_exc()
pass
if timeout > 0:
self.timer = after(self.top, timeout, self.mTimeout)
try: self.top.mainloop()
except SystemExit:
pass
self.destroy()
if transient:
setTransient(self.top, self.parent)
try:
self.top.grab_set()
except Tkinter.TclError:
if traceback: traceback.print_exc()
pass
if timeout > 0:
self.timer = after(self.top, timeout, self.mTimeout)
try: self.top.mainloop()
except SystemExit:
pass
self.destroy()
def destroy(self):
after_cancel(self.timer)
@ -716,5 +720,203 @@ class StackDesc:
self.label.unbind('<ButtonPress>', b)
# /***********************************************************************
# //
# ************************************************************************/
class FindCardDialog(MfxDialog):
SUIT_IMAGES = {} # key: (suit, color)
RANK_IMAGES = {} # key: (rank, color)
def __init__(self, parent, game, dir, title='Find card', **kw):
kw = self.initKw(kw)
MfxDialog.__init__(self, parent, title, kw.resizable, kw.default)
top_frame, bottom_frame = self.createFrames(kw)
self.createBitmaps(top_frame, kw)
self.images_dir = dir
#self.default_color = top_frame['bg']
#self.bg_colors = ('#f9e3d2', '#c9e3d2')
#self.highlight_color = 'white'
self.label_width, self.label_height = 36, 30
self.top_frame = top_frame
self.canvas = MfxCanvas(top_frame, bg='white')
self.canvas.pack(expand=True, fill='both')
#
self.button = kw.default
self.labels = []
self.tk_images = []
self.highlight_items = None
self.last_card = None
self.connectGame(game)
#
focus = self.createButtons(bottom_frame, kw)
##self.mainloop(focus, kw.timeout, transient=False)
def initKw(self, kw):
kw = KwStruct(kw,
strings=(_("&Close"),), default=0,
resizable=0,
padx=4, pady=4,
separatorwidth=0,
)
return MfxDialog.initKw(self, kw)
def createCardLabel(self, suit, rank, x0, y0):
dx, dy = self.label_width, self.label_height
dir = self.images_dir
images = self.tk_images
canvas = self.canvas
group = MfxCanvasGroup(canvas)
s = 'cshd'[suit]
if suit >= 2: c = 'red'
else: c = 'black'
x1, y1 = x0+dx-2, y0+dy-2
rect = MfxCanvasRectangle(self.canvas, x0, y0, x1, y1,
width=2, fill='white', outline='white')
rect.addtag(group)
#
fn = os.path.join(dir, c+'-'+str(rank)+'.gif')
rim = FindCardDialog.RANK_IMAGES.get((rank, c))
if not rim:
rim = Tkinter.PhotoImage(file=fn)
FindCardDialog.RANK_IMAGES[(rank, c)] = rim
fn = os.path.join(dir, 'large-'+s+'.gif')
sim = FindCardDialog.SUIT_IMAGES.get((suit, c))
if not sim:
sim = Tkinter.PhotoImage(file=fn)
FindCardDialog.SUIT_IMAGES[(suit, c)] = sim
#
x0 = x0+(dx-rim.width()-sim.width())/2
x, y = x0, y0+(dy-rim.height())/2
im = MfxCanvasImage(canvas, x, y, image=rim, anchor='nw')
im.addtag(group)
x, y = x0+rim.width(), y0+(dy-sim.height())/2
im = MfxCanvasImage(canvas, x, y, image=sim, anchor='nw')
im.addtag(group)
bind(group, '<Enter>',
lambda e, suit=suit, rank=rank, rect=rect:
self.enterEvent(suit, rank, rect))
bind(group, '<Leave>',
lambda e, suit=suit, rank=rank, rect=rect:
self.leaveEvent(suit, rank, rect))
self.labels.append(group)
def connectGame(self, game):
self.game = game
suits = game.gameinfo.suits
ranks = game.gameinfo.ranks
dx, dy = self.label_width, self.label_height
i = 0
for suit in suits:
j = 0
for rank in ranks:
x, y = dx*j+2, dy*i+2
self.createCardLabel(suit=suit, rank=rank, x0=x, y0=y)
j += 1
i += 1
w, h = dx*j, dy*i
self.canvas.config(width=w, height=h)
## if self.labels:
## for l in self.labels:
## unbind_destroy(l)
## l.grid_forget()
## frame = self.top_frame
## from pysollib.util import SUITS, RANKS
## suits = game.gameinfo.suits
## ranks = game.gameinfo.ranks
## ns = 0
## for i in suits:
## color = self.bg_colors[ns%2]
## suit = SUITS[i]
## suit_label = Tkinter.Label(frame, text=suit, bg=color)
## suit_label.grid(row=i, column=0, sticky='ew')
## self.labels.append(suit_label)
## ns += 1
## nk = 0
## for j in ranks:
## color = self.bg_colors[(ns+nk)%2]
## rank = RANKS[j]
## rank_label = Tkinter.Label(frame, text=rank, bg=color)
## bind(rank_label, '<Enter>', lambda e, label=rank_label, suit=i, rank=j: self.enterEvent(label, suit, rank))
## bind(rank_label, '<Leave>', lambda e, label=rank_label, suit=i, rank=j: self.leaveEvent(label, suit, rank))
## self.labels.append(rank_label)
## #rank_label.config(highlightthickness=1,
## # highlightbackground='black')
## rank_label.grid(row=i, column=j+1, sticky='ew')
## nk += 1
## def showCard(self, suit, rank, rect):
## print suit, rank
def enterEvent(self, suit, rank, rect):
#print 'enterEvent', suit, rank
#if (suit, rank) == self.last_card: return
self.last_card = (suit, rank)
self.highlight_items = self.game.highlightCard(suit, rank)
if not self.highlight_items:
self.highlight_items = []
## dx, dy = self.label_width, self.label_height
## x0, y0 = dx*rank, dy*suit
## x1, y1 = dx*(rank+1), dy*(suit+1)
rect.config(outline='red')
#item = MfxCanvasRectangle(self.canvas, x0, y0, x1, y1,
# width=2, fill=None, outline='red')
#self.highlight_items.append(item)
def leaveEvent(self, suit, rank, rect):
#print 'leaveEvent', suit, rank
#if (suit, rank) == self.last_card: return
for i in self.highlight_items:
i.delete()
#self.game.canvas.update_idletasks()
#self.canvas.update_idletasks()
rect.config(outline='white')
self.last_card = None
def destroy(self):
for l in self.labels:
unbind_destroy(l)
unbind_destroy(self.top)
self.top.wm_withdraw()
self.top.destroy()
def tkraise(self):
self.top.tkraise()
def mDone(self, button):
self.destroy()
pass
def wmDeleteWindow(self, *event):
self.destroy()
pass
find_card_dialog = None
def create_find_card_dialog(parent, game, dir):
global find_card_dialog
try:
find_card_dialog.tkraise()
except:
##traceback.print_exc()
find_card_dialog = FindCardDialog(parent, game, dir)
def connect_game_find_card_dialog(game):
try:
find_card_dialog.connectGame(game)
except:
pass
def destroy_find_card_dialog():
global find_card_dialog
try:
find_card_dialog.destroy()
except:
##traceback.print_exc()
pass
find_card_dialog = None