mirror of
https://github.com/shlomif/PySolFC.git
synced 2025-04-05 00:02:29 -04:00
* improved mahjongg games solubility
git-svn-id: file:///home/shlomif/Backup/svn-dumps/PySolFC/svnsync-repos/pysolfc/PySolFC/trunk@165 efabe8c0-fbe8-4139-b769-b5e6d273206e
This commit is contained in:
parent
f704ddd02b
commit
933dc7b4cc
4 changed files with 171 additions and 6 deletions
2
pysol.py
2
pysol.py
|
@ -27,5 +27,7 @@ init()
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from pysollib.main import main
|
from pysollib.main import main
|
||||||
|
#import profile
|
||||||
|
#profile.run("main(sys.argv)")
|
||||||
sys.exit(main(sys.argv))
|
sys.exit(main(sys.argv))
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,7 @@ class Options:
|
||||||
self.highlight_samerank = True
|
self.highlight_samerank = True
|
||||||
self.highlight_not_matching = True
|
self.highlight_not_matching = True
|
||||||
self.mahjongg_show_removed = False
|
self.mahjongg_show_removed = False
|
||||||
self.mahjongg_create_solvable = True
|
self.mahjongg_create_solvable = 2 # 0 - none, 1 - easy, 2 - hard
|
||||||
self.shisen_show_hint = True
|
self.shisen_show_hint = True
|
||||||
self.animations = 2 # default to Fast
|
self.animations = 2 # default to Fast
|
||||||
self.redeal_animation = True
|
self.redeal_animation = True
|
||||||
|
@ -254,6 +254,7 @@ class Options:
|
||||||
def setConstants(self):
|
def setConstants(self):
|
||||||
self.dragcursor = True
|
self.dragcursor = True
|
||||||
self.randomize_place = False
|
self.randomize_place = False
|
||||||
|
self.mahjongg_create_solvable = 2
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
opt = Options()
|
opt = Options()
|
||||||
|
|
|
@ -27,7 +27,8 @@
|
||||||
__all__ = []
|
__all__ = []
|
||||||
|
|
||||||
# Imports
|
# Imports
|
||||||
import sys, string, re
|
import sys, re
|
||||||
|
import time
|
||||||
#from tkFont import Font
|
#from tkFont import Font
|
||||||
|
|
||||||
# PySol imports
|
# PySol imports
|
||||||
|
@ -44,7 +45,7 @@ from pysollib.settings import TOOLKIT
|
||||||
|
|
||||||
|
|
||||||
def factorial(x):
|
def factorial(x):
|
||||||
if x < 1:
|
if x <= 1:
|
||||||
return 1
|
return 1
|
||||||
a = 1
|
a = 1
|
||||||
for i in xrange(x):
|
for i in xrange(x):
|
||||||
|
@ -218,6 +219,8 @@ class Mahjongg_RowStack(OpenStack):
|
||||||
bind(group, "<1>", self.__clickEventHandler)
|
bind(group, "<1>", self.__clickEventHandler)
|
||||||
bind(group, "<3>", self.__controlclickEventHandler)
|
bind(group, "<3>", self.__controlclickEventHandler)
|
||||||
bind(group, "<Control-1>", 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):
|
def __defaultClickEventHandler(self, event, handler):
|
||||||
self.game.event_handled = True # for Game.undoHandler
|
self.game.event_handled = True # for Game.undoHandler
|
||||||
|
@ -427,13 +430,19 @@ class AbstractMahjonggGame(Game):
|
||||||
# compute blockmap
|
# compute blockmap
|
||||||
for stack in s.rows:
|
for stack in s.rows:
|
||||||
level, tx, ty = tiles[stack.id]
|
level, tx, ty = tiles[stack.id]
|
||||||
above, left, right, up, bottom = {}, {}, {}, {}, {}
|
above, below, left, right, up, bottom = {}, {}, {}, {}, {}, {}
|
||||||
# above blockers
|
# above blockers
|
||||||
for tl in range(level+1, level+2):
|
for tl in range(level+1, level+2):
|
||||||
above[tilemap.get((tl, tx, ty))] = 1
|
above[tilemap.get((tl, tx, ty))] = 1
|
||||||
above[tilemap.get((tl, tx+1, ty))] = 1
|
above[tilemap.get((tl, tx+1, ty))] = 1
|
||||||
above[tilemap.get((tl, tx, ty+1))] = 1
|
above[tilemap.get((tl, tx, ty+1))] = 1
|
||||||
above[tilemap.get((tl, tx+1, 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 blockers
|
||||||
left[tilemap.get((level, tx-1, ty))] = 1
|
left[tilemap.get((level, tx-1, ty))] = 1
|
||||||
left[tilemap.get((level, tx-1, ty+1))] = 1
|
left[tilemap.get((level, tx-1, ty+1))] = 1
|
||||||
|
@ -453,19 +462,49 @@ class AbstractMahjonggGame(Game):
|
||||||
assert tilemap.get((level, tx+1, ty+1)) is stack
|
assert tilemap.get((level, tx+1, ty+1)) is stack
|
||||||
#
|
#
|
||||||
above = tuple(filter(None, above.keys()))
|
above = tuple(filter(None, above.keys()))
|
||||||
|
below = tuple(filter(None, below.keys()))
|
||||||
left = tuple(filter(None, left.keys()))
|
left = tuple(filter(None, left.keys()))
|
||||||
right = tuple(filter(None, right.keys()))
|
right = tuple(filter(None, right.keys()))
|
||||||
##up = tuple(filter(None, up.keys()))
|
##up = tuple(filter(None, up.keys()))
|
||||||
##bottom = tuple(filter(None, bottom.keys()))
|
##bottom = tuple(filter(None, bottom.keys()))
|
||||||
|
|
||||||
# assemble
|
# assemble
|
||||||
stack.blockmap = Struct(
|
stack.blockmap = Struct(
|
||||||
above = above,
|
above = above,
|
||||||
|
below = below,
|
||||||
left = left,
|
left = left,
|
||||||
right = right,
|
right = right,
|
||||||
##up = up,
|
##up = up,
|
||||||
##bottom = bottom,
|
##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
|
# create other stacks
|
||||||
for i in range(4):
|
for i in range(4):
|
||||||
for j in range(9):
|
for j in range(9):
|
||||||
|
@ -503,9 +542,17 @@ class AbstractMahjonggGame(Game):
|
||||||
#
|
#
|
||||||
|
|
||||||
def _shuffleHook(self, cards):
|
def _shuffleHook(self, cards):
|
||||||
if not self.app.opt.mahjongg_create_solvable:
|
if self.app.opt.mahjongg_create_solvable == 0:
|
||||||
return cards
|
return cards
|
||||||
# try to create a solvable game
|
# try to create a solvable game
|
||||||
|
if self.app.opt.mahjongg_create_solvable == 1:
|
||||||
|
# easy
|
||||||
|
return self._shuffleHook1(cards[:])
|
||||||
|
# hard
|
||||||
|
return self._shuffleHook2(cards)
|
||||||
|
|
||||||
|
|
||||||
|
def _shuffleHook1(self, cards):
|
||||||
old_cards = cards[:]
|
old_cards = cards[:]
|
||||||
rows = self.s.rows
|
rows = self.s.rows
|
||||||
|
|
||||||
|
@ -578,6 +625,121 @@ class AbstractMahjonggGame(Game):
|
||||||
return old_cards
|
return old_cards
|
||||||
|
|
||||||
|
|
||||||
|
def _shuffleHook2(self, cards):
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
iters = [0]
|
||||||
|
# limitations
|
||||||
|
max_time = 2.0 # seconds
|
||||||
|
max_iters = len(cards)
|
||||||
|
|
||||||
|
rows = self.s.rows
|
||||||
|
|
||||||
|
def is_suitable(stack, cards):
|
||||||
|
for s in stack.blockmap.below:
|
||||||
|
# check if below stacks are non-empty
|
||||||
|
if cards[s.id] is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
nleft = 0
|
||||||
|
for s in stack.blockmap.left:
|
||||||
|
if cards[s.id] is None:
|
||||||
|
for t in s.blockmap.all_left:
|
||||||
|
if cards[t.id] is not None:
|
||||||
|
# we have empty stack between two non-empty
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
nleft += 1
|
||||||
|
|
||||||
|
nright = 0
|
||||||
|
for s in stack.blockmap.right:
|
||||||
|
if cards[s.id] is None:
|
||||||
|
for t in s.blockmap.all_right:
|
||||||
|
if cards[t.id] is not None:
|
||||||
|
# we have empty stack between two non-empty
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
nright += 1
|
||||||
|
|
||||||
|
if nleft > 0 and nright > 0:
|
||||||
|
# we have left and right non-empty stacks
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# create uniq pair
|
||||||
|
r1 = self.random.randrange(0, len(suitable_stacks))
|
||||||
|
r2 = self.random.randrange(0, len(suitable_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
|
||||||
|
# select two suitable stacks
|
||||||
|
s1 = suitable_stacks[r1]
|
||||||
|
s2 = suitable_stacks[r2]
|
||||||
|
#
|
||||||
|
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:
|
||||||
|
return ret
|
||||||
|
nc[s1.id] = nc[s2.id] = None # try another way
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if time.time() - start_time > max_time:
|
||||||
|
print 'oops! can\'t create a solvable game'
|
||||||
|
return cards
|
||||||
|
ret = create_solvable(cards[:], [None]*len(cards))
|
||||||
|
if ret:
|
||||||
|
ret.reverse()
|
||||||
|
return ret
|
||||||
|
iters = [0]
|
||||||
|
print 'oops! can\'t create a solvable game'
|
||||||
|
return cards
|
||||||
|
|
||||||
|
|
||||||
def startGame(self):
|
def startGame(self):
|
||||||
assert len(self.s.talon.cards) == self.NCARDS
|
assert len(self.s.talon.cards) == self.NCARDS
|
||||||
#self.s.talon.dealRow(rows = self.s.rows, frames = 0)
|
#self.s.talon.dealRow(rows = self.s.rows, frames = 0)
|
||||||
|
|
|
@ -1133,7 +1133,7 @@ class Stack:
|
||||||
self.cursor_changed = True
|
self.cursor_changed = True
|
||||||
else:
|
else:
|
||||||
help = self.getHelp() ##+' '+self.getBaseCard(),
|
help = self.getHelp() ##+' '+self.getBaseCard(),
|
||||||
if DEBUG >= 5:
|
if DEBUG:
|
||||||
help = repr(self)
|
help = repr(self)
|
||||||
after_idle(self.canvas, self.game.showHelp,
|
after_idle(self.canvas, self.game.showHelp,
|
||||||
'help', help,
|
'help', help,
|
||||||
|
|
Loading…
Add table
Reference in a new issue