Sokoban 
Author Message
 Sokoban

# Sokoban in python

import Tkinter

WALL   = 'black'
BOX    = 'blue'
EMPTY  = 'yellow'
FULL   = 'green'
PLAYER = 'red'

size = 20
margin = 5

def loadmap(mapdata):
    lines = filter(None, mapdata.split('\n'))
    xs, ys = len(lines[0]), len(lines)
    wall, depot, box, player = {}, {}, {}, {}
    mapstruct = wall, depot, box, player
    switch = {' ': (),
              '#': (wall,),
              '$': (box,),
              '.': (depot,),
              '*': (depot, box),

              '+': (depot, player)}
    def setter(dic):
        dic[x, y] = 1
    for y, line in zip(range(ys), lines):
        for x, char in zip(range(xs), line):
            map(setter, switch[char])
    return xs, ys, mapstruct

def makecanvas(parent, xs, ys):
    width, height = xs*size+margin*2, ys*size+margin*2
    canvas = Tkinter.Canvas(
        parent, width=width, height=height)
    return canvas

def drawmap(canvas, mapstruct):
    def square(x, y, fill):
        return canvas.create_rectangle(
            (x+0)*size+margin, (y+0)*size+margin,
            (x+1)*size+margin, (y+1)*size+margin,
            width=0, fill=fill)
    def circle(x, y, fill):
        return 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)
    switch = {'s': square,
              'c': circle}
    def setter(dic, kind, fill):
        def set(pos):
            x, y = pos
            dic[x, y] = switch[kind](x, y, fill)
        return set
    idstruct = {}, {}, {}, {}
    kinds = 'sscc'
    colors = WALL, EMPTY, BOX, PLAYER
    for dic, kind, fill, data in zip(
        idstruct, kinds, colors, mapstruct):
        map(setter(dic, kind, fill), data)
    return idstruct

def filldepot(canvas, mapstruct, idstruct):
    depot = mapstruct[1]
    depotid = idstruct[1]
    box = mapstruct[2]
    for pos in depot:
        if pos in box:
            canvas.itemconfigure(depotid[pos], fill=FULL)

def makemover(canvas, mapstruct, idstruct):
    def delta(event):
        key = event.keysym
        switch = {'Up': (0, -1),
                  'Left': (-1, 0),
                  'Down': (0, 1),
                  'Right': (1, 0)}
        return switch[key]
    def move(dic, idic, pos, dd):
        x, y, dx, dy = pos + dd
        canvas.move(idic[x, y], dx*size, dy*size)
        idic[x+dx, y+dy] = idic[x, y]
        del idic[x, y]
        dic[x+dx, y+dy] = dic[x, y]
        del dic[x, y]
    def fill(idic, pos, fill):
        canvas.itemconfigure(idic[pos], fill=fill)
    def mover(event):
        wall, depot, box, player = mapstruct
        wallid, depotid, boxid, playerid = idstruct
        pos = player.keys()[0]
        dd = delta(event)
        x, y, dx, dy = pos + dd
        dpos, ddpos = (x+dx, y+dy), (x+dx+dx, y+dy+dy)
        if dpos not in wall:
            if dpos not in box:
                move(player, playerid, pos, dd)
            elif ddpos not in wall and ddpos not in box:
                move(player, playerid, pos, dd)
                move(box, boxid, dpos, dd)
                if dpos in depot:
                    fill(depotid, dpos, EMPTY)
                if ddpos in depot:
                    fill(depotid, ddpos, FULL)
    return mover

def makegame(parent, mapdata):
    xs, ys, mapstruct = loadmap(mapdata)
    canvas = makecanvas(parent, xs, ys)
    idstruct = drawmap(canvas, mapstruct)
    filldepot(canvas, mapstruct, idstruct)
    mover = makemover(canvas, mapstruct, idstruct)
    parent.bind('<Key>', mover)
    canvas.pack(padx=margin, pady=margin)

def main():
    filename = raw_input('> ')
    mapdata = file(filename).read()
    root = Tkinter.Tk()
    makegame(root, mapdata)
    root.mainloop()

if __name__ == '__main__':
    main()



Fri, 28 Jan 2005 21:16:03 GMT  
 Sokoban


Quote:
> # Sokoban in Python

Without a sample map or any knowledge of how to construct one, I could
not get very far 8-(.


Fri, 28 Jan 2005 22:56:04 GMT  
 Sokoban

Quote:



> > # Sokoban in Python

> Without a sample map or any knowledge of how to construct one, I could
> not get very far 8-(.

From reading the code, it looks like this should work to get you started
(untested as I don't have access to a Tkinter-enabled Python here at work):

---Start sample map---

#################
---End sample map---

--

When we attempt the impossible, we can experience true growth.



Fri, 28 Jan 2005 23:14:50 GMT  
 Sokoban



Quote:
> From reading the code, it looks like this should work to get you
started
> (untested as I don't have access to a Tkinter-enabled Python here at
work):

> ---Start sample map---
> #################

> #################
> ---End sample map---

Thanks.  Worked great!  (Was pleasantly surprised to discover that
PythonWin runs Tkinter programs just fine!)  I gather point is to use
'player' to push balls into boxes (or boxes into depots).  Design
problem to to design nontrivial maps with nontrivial solution.
Vaguely like some levels of Chip's Challenge (Win95 freebee).

With two players, it randomly selects one to move with each keystroke.
That could make for interesting challenges.

Terry



Sat, 29 Jan 2005 02:49:05 GMT  
 Sokoban
What could I do to avoid these errors (There appear about 20 of them)?:

D:/Program Files/Python21/sokoban:73: SyntaxWarning: local name 'delta' in
'makemover' shadows use of 'delta' as global in nested scope 'mover'
  def makemover(canvas, mapstruct, idstruct):

Sometimes I get to think I'll never have the luck to just have some code
from somewhere running without problems ;)

TIA,

Mik


Quote:
> # Sokoban in Python

[All The Code]


Sat, 29 Jan 2005 04:19:44 GMT  
 Sokoban



Quote:
> What could I do to avoid these errors (There appear about 20 of
them)?:

> D:/Program Files/Python21/sokoban:73: SyntaxWarning: local name
'delta' in
> 'makemover' shadows use of 'delta' as global in nested scope 'mover'
>   def makemover(canvas, mapstruct, idstruct):

> Sometimes I get to think I'll never have the luck to just have some
code
> from somewhere running without problems ;)

Ran fine for me with 2.2.  Suspect you need to add
from __future__ import nested_scopes

TJR



Sat, 29 Jan 2005 05:53:41 GMT  
 Sokoban

Quote:
> # Sokoban in Python

Very nice, but...

No Undo!?!

AAAAGH!



Sat, 29 Jan 2005 07:15:22 GMT  
 Sokoban


Quote:



>> # Sokoban in Python

> Without a sample map or any knowledge of how to construct one, I could
> not get very far 8-(.

Try http://www.ne.jp/asahi/ai/yoshio/sokoban/main.htm for a lot of
different maps, although be warned that if you haven't played sokoban
before you might be better just to search google for an existing
implementation and get the introductory levels from it.

--

int month(char *p){return(124864/((p[0]+p[1]-p[2]&0x1f)+1)%12)["\5\x8\3"
"\6\7\xb\1\x9\xa\2\0\4"];} // Who said my code was obscure?



Sat, 29 Jan 2005 17:10:13 GMT  
 Sokoban


Quote:
>> # Sokoban in Python

> Very nice, but...

> No Undo!?!

> AAAAGH!

Have you ever played Nethack? The more recent versions include some Sokoban
levels: no undo, monsters that attack you (or worse lurk behind boulders to
stop you pushing them), spare boulders (which can actually make it harder)
not to mention the mimics that look like boulders but aren't. Some of the
usual nethack things are allowed (creating/destroying boulders,
jumping/levitating over the pits, or squeezing round a boulder), but the
game penalises you if you do any of them. Diagonal movement is allowed, but
only where it doesn't affect the gameplay.

--

int month(char *p){return(124864/((p[0]+p[1]-p[2]&0x1f)+1)%12)["\5\x8\3"
"\6\7\xb\1\x9\xa\2\0\4"];} // Who said my code was obscure?



Sat, 29 Jan 2005 17:20:21 GMT  
 Sokoban
Thanks for feedbacks.

Quote:

> 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.

Quote:

> 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.

Quote:

> What could I do to avoid these errors?
> [snip nested scope errors]

fron __future__ import nested_scope, or see below.

Quote:

> 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):

    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),

              '+': (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 ...

read more »



Sat, 29 Jan 2005 19:53:41 GMT  
 Sokoban

Quote:
> def rewinder(event, canvas, level, idtable):
>     ...
>     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

This is wrong. It should be this way:
Quote:
> def rewinder(event, canvas, level, idtable):
>     ...
>     if lastmove == '+':
>         pidlimit = len(pidtable.keys())
>         pos = curpos(level)
>         fill(canvas, level, idtable, 'player', pos, INACTIVE)
>         if pid == 0:
>             pid = pidlimit
>         pid = pid - 1
>         level.save({'pid': pid})
>         pos = curpos(level)
>         fill(canvas, level, idtable, 'player', pos, ACTIVE)
>         return



Sun, 30 Jan 2005 15:13:18 GMT  
 
 [ 12 post ] 

 Relevant Pages 

1. delimiters (was: SOKOBAN.F Bug)

2. SOKOBAN.F Bug

3. FINALLY, The Sokoban Contest Winner

4. Sokoban Contest

5. My 1995 Sokoban Contest Entry

6. Programmable Robotic Sokoban with Elica

7. Programmable Robotic Sokoban with Elica

8. Sokoban in Oberon for Linux

9. Sokoban

10. Array out of bounds in Sokoban solver and hash of arrays

 

 
Powered by phpBB® Forum Software