Multiban (was Re: Sokoban)
Seo Sanghyeon
unendliche at hanmail.net
Tue Aug 13 07:53:41 EDT 2002
Thanks for feedbacks.
Terry Reedy (tjreedy at udel.edu) wrote:
> Without a sample map or any knowledge of how to construct one,
> I could not get very far 8-(.
It uses XSokoban text format, de facto standard for Sokoban levels.
http://www.clickfest88.freeserve.co.uk/xsbformat.html
2315 levels in XSokoban text format.
http://mujweb.cz/Zabava/sokoban/downloada.htm
But it fails if it encounter comments. For solution, see below.
Terry Reedy (tjreedy at udel.edu) wrote:
> With two players, it randomly selects one to move with each keystroke.
> That could make for interesting challenges.
Oh, it was not my intention, but see below.
Michael B?ker (aida.kensuke at gmx.no-spam-accepted.net) wrote:
> What could I do to avoid these errors?
> [snip nested scope errors]
fron __future__ import nested_scope, or see below.
Lee Harr (missive at frontiernet.net) wrote:
> Very nice, but...
> No Undo?!
See below...
----
So, here is Multiban,
Sokoban with multiple players,
which read XSokoban format with comments,
which runs on Python 1.5.2
which has undo,
which prints your solution,
etc.
== MANUAL ==
Up, Left, Down, Right -> Move a man
Tab -> Select a man
BackSpace -> Undo
Enjoy!
----
# Sokoban in Python
# Tested on Python 2.2.1 AND Python 1.5.2
# XSokoban format for levels
# Lurd format for bookmarks
import Tkinter # to do graphics
import re # to parse xsb format
import string
upper = {'u': 'U', 'l': 'L', 'd': 'D', 'r': 'R'}
lower = {'U': 'u', 'L': 'l', 'D': 'd', 'R': 'r'}
# == start configuration =====
WALL = 'black'
BOX = 'blue'
EMPTY = 'yellow'
FULL = 'green'
INACTIVE = 'brown'
ACTIVE = 'red'
size = 20
margin = 5
maxline = 60
# == end configuration =======
class Struct:
def load(self, keys):
if type(keys) == type(()):
res = []
for key in keys:
res.append(self.__dict__[key])
return res
else:
res = self.__dict__[keys]
return res
def save(self, dic):
for key in dic.keys():
self.__dict__[key] = dic[key]
# ============================
def loadlevel(filename):
xsbline = re.compile('[ #$.*@+]+$')
lines = open(filename).readlines()
lines = map(lambda x: x[:-1], lines)
lines = filter(xsbline.match, lines)
xs = max(map(len, lines))
ys = len(lines)
wall, depot, box, player = {}, {}, {}, {}
switch = {' ': (),
'#': (wall,),
'$': (box,),
'.': (depot,),
'*': (depot, box),
'@': (player,),
'+': (depot, player)}
for y in range(len(lines)):
for x in range(len(lines[y])):
for dic in switch[lines[y][x]]:
dic[x, y] = 1
assert len(depot.keys()) == len(box.keys())
boxcnt = len(box.keys())
level = Struct()
level.save({'xs': xs,
'ys': ys,
'wall': wall,
'depot': depot,
'box': box,
'player': player,
'boxcnt': boxcnt})
return level
# ============================
def create_canvas(parent, level):
xs, ys = level.load(('xs', 'ys'))
width = xs*size+margin*2
height = ys*size+margin*2
canvas = Tkinter.Canvas(
parent, width=width, height=height)
return canvas
def create_square(canvas, pos, fill):
x, y = pos
id = canvas.create_rectangle(
(x+0)*size+margin, (y+0)*size+margin,
(x+1)*size+margin, (y+1)*size+margin,
width=0, fill=fill)
return id
def create_circle(canvas, pos, fill):
x, y = pos
id = canvas.create_oval(
(x+0.2)*size+margin, (y+0.2)*size+margin,
(x+0.8)*size+margin, (y+0.8)*size+margin,
width=0, fill=fill)
return id
# ============================
def drawlevel(canvas, level):
depotid, boxid, playerid = {}, {}, {}
xs, ys, wall, depot, box, player = level.load(
('xs', 'ys', 'wall', 'depot', 'box', 'player'))
for x in range(xs):
for y in range(ys):
pos = x, y
if pos in wall.keys():
create_square(canvas, pos, WALL)
if pos in depot.keys():
depotid[pos] = create_square(canvas, pos, EMPTY)
# circles should be drawn after squares are drawn
for x in range(xs):
for y in range(ys):
pos = x, y
if pos in box.keys():
boxid[pos] = create_circle(canvas, pos, BOX)
if pos in player.keys():
playerid[pos] = create_circle(canvas, pos, INACTIVE)
idtable = Struct()
idtable.save({'depot': depotid,
'box': boxid,
'player': playerid})
return idtable
# ============================
def initlevel(canvas, level, idtable):
depot, box = level.load(('depot', 'box'))
depotid = idtable.load('depot')
boxcnt = level.load('boxcnt')
for pos in depot.keys():
if pos in box.keys():
fill(canvas, level, idtable, 'depot', pos, FULL)
boxcnt = boxcnt - 1
level.save({'boxcnt': boxcnt})
players = level.load('player').keys()
pidtable = {}
newpid = 0
for pos in players:
pidtable[newpid] = pos
newpid = newpid + 1
level.save({'pidtable': pidtable,
'pid': 0})
pos = curpos(level)
fill(canvas, level, idtable, 'player', pos, ACTIVE)
level.save({'history': []})
# ============================
def move(canvas, level, idtable, code, pos, dd):
x, y = pos
dx, dy = dd
dpos = x+dx, y+dy
dic = level.load(code)
idic = idtable.load(code)
dic[dpos] = dic[pos]
idic[dpos] = idic[pos]
canvas.move(idic[pos], dx*size, dy*size)
del dic[pos]
del idic[pos]
def fill(canvas, level, idtable, code, pos, fill):
idic = idtable.load(code)
canvas.itemconfigure(idic[pos], fill=fill)
def curpos(level):
pidtable, pid = level.load(('pidtable', 'pid'))
pos = pidtable[pid]
return pos
def checkfin(level):
boxcnt = level.load('boxcnt')
fin = (boxcnt == 0)
return fin
def delta(event):
key = event.keysym
switch = {'Up': (0, -1),
'Left': (-1, 0),
'Down': (0, 1),
'Right': (1, 0)}
dd = switch[key]
return dd
def convert(dd):
switch_1 = {(0, -1): 'u',
(-1, 0): 'l',
(0, 1): 'd',
(1, 0): 'r'}
switch_2 = {'u': (0, -1),
'l': (-1, 0),
'd': (0, 1),
'r': (1, 0)}
if type(dd) == type(()):
cc = switch_1[dd]
else:
cc = switch_2[dd]
return cc
# ============================
def mover(event, canvas, level, idtable):
dd = delta(event)
dir = convert(dd)
pos = curpos(level)
x, y = pos
dx, dy = dd
dpos = x+dx, y+dy
ddpos = x+dx+dx, y+dy+dy
wall, box, depot = level.load(('wall', 'box', 'depot'))
boxcnt = level.load('boxcnt')
history = level.load('history')
pidtable, pid = level.load(('pidtable', 'pid'))
if dpos not in wall.keys():
if dpos not in box.keys():
move(canvas, level, idtable, 'player', pos, dd)
pidtable[pid] = dpos
history.append(dir)
elif (ddpos not in wall.keys() and
ddpos not in box.keys()):
move(canvas, level, idtable, 'player', pos, dd)
pidtable[pid] = dpos
move(canvas, level, idtable, 'box', dpos, dd)
if dpos in depot.keys():
fill(canvas, level, idtable, 'depot', dpos, EMPTY)
boxcnt = boxcnt + 1
if ddpos in depot.keys():
fill(canvas, level, idtable, 'depot', ddpos, FULL)
boxcnt = boxcnt - 1
level.save({'boxcnt': boxcnt})
history.append(upper[dir])
# ============================
def selector(event, canvas, level, idtable):
pidtable, pid = level.load(('pidtable', 'pid'))
pidlimit = len(pidtable.keys())
pos = curpos(level)
fill(canvas, level, idtable, 'player', pos, INACTIVE)
pid = pid + 1
if pid == pidlimit:
pid = 0
level.save({'pid': pid})
pos = curpos(level)
fill(canvas, level, idtable, 'player', pos, ACTIVE)
history = level.load('history')
history.append('+')
# ============================
def rewinder(event, canvas, level, idtable):
history = level.load('history')
if not history:
return
lastmove = history.pop()
pidtable, pid = level.load(('pidtable', 'pid'))
if lastmove == '+':
pos = curpos(level)
fill(canvas, level, idtable, 'player', pos, INACTIVE)
pid = pid - 1
if pid == -1:
pid = 0
level.save({'pid': pid})
pos = curpos(level)
fill(canvas, level, idtable, 'player', pos, ACTIVE)
return
dir = lastmove
push = 0
if dir in upper.values():
push = 1
dir = lower[dir]
dd = convert(dir)
pos = curpos(level)
x, y = pos
dx, dy = dd
idd = -dx, -dy
dpos = x+dx, y+dy
ipos = x-dx, y-dy
depot = level.load('depot')
boxcnt = level.load('boxcnt')
if not push:
move(canvas, level, idtable, 'player', pos, idd)
pidtable[pid] = ipos
else:
move(canvas, level, idtable, 'player', pos, idd)
pidtable[pid] = ipos
move(canvas, level, idtable, 'box', dpos, idd)
if dpos in depot.keys():
fill(canvas, level, idtable, 'depot', dpos, EMPTY)
boxcnt = boxcnt + 1
if pos in depot.keys():
fill(canvas, level, idtable, 'depot', pos, FULL)
boxcnt = boxcnt - 1
level.save({'boxcnt': boxcnt})
# ============================
def splitline(string, maxline):
res = ''
cnt = 0
while 1:
line = string[cnt*maxline:(cnt+1)*maxline]
if not line: break
res = res + line + '\n'
cnt = cnt + 1
return res
# ============================
def finish(parent, level):
solution = string.join(level.load('history'), '')
solution = splitline(solution, maxline)
print solution
button = Tkinter.Button(
parent, text='Close', command=parent.destroy)
button.pack()
# ============================
def makegame(parent, filename):
level = loadlevel(filename)
canvas = create_canvas(parent, level)
idtable = drawlevel(canvas, level)
initlevel(canvas, level, idtable)
def move_callback(event, parent=parent,
canvas=canvas, level=level, idtable=idtable):
mover(event, canvas, level, idtable)
if checkfin(level):
finish(parent, level)
def rewind_callback(event, parent=parent,
canvas=canvas, level=level, idtable=idtable):
rewinder(event, canvas, level, idtable)
if checkfin(level):
finish(parent, level)
def select_callback(event, parent=parent,
canvas=canvas, level=level, idtable=idtable):
selector(event, canvas, level, idtable)
if checkfin(level):
finish(parent, level)
parent.bind('<Up>', move_callback)
parent.bind('<Left>', move_callback)
parent.bind('<Down>', move_callback)
parent.bind('<Right>', move_callback)
parent.bind('<BackSpace>', rewind_callback)
parent.bind('<Tab>', select_callback)
canvas.pack(side=Tkinter.TOP,
padx=margin, pady=margin)
if checkfin(level):
finish(parent, level)
# ============================
def main():
filename = raw_input('> ')
root = Tkinter.Tk()
makegame(root, filename)
root.mainloop()
if __name__ == '__main__':
main()
More information about the Python-list
mailing list