sokoban.py

Created by arthurjacquin

Created on July 10, 2021

5.18 KB


# Sokoban - June 2020
# Arthur Jacquin (arthur@jacquin.xyz)
# https://github.com/arthur-jacquin/numworks-games

# Modules
from ion import keydown
from kandinsky import *
from time import sleep

# Levels
LEVELS = ('4-5#10-|4-#3-#10-|4-#$--#10-|--3#--$##9-|--#--$-$-#9-|3#-#-##-#3-6#|#3-#-##-5#--..#|#-$--$10-..#|5#-3#-#@##--..#|4-#5-9#|4-7#8-',
    '12#--|#..--#5-3#|#..--#-$--$--#|#..--#$4#--#|#..4-@-##--#|#..--#-#--$-##|6#-##$-$-#|--#-$--$-$-$-#|--#4-#5-#|--12#',
    '8-8#-|8-#5-@#-|8-#-$#$-##-|8-#-$--$#--|8-##$-$-#--|9#-$-#-3#|#4.--##-$--$--#|##3.4-$--$3-#|#4.--10#|8#9-',
    '11-8#|11-#--4.#|12#--4.#|#4-#--$-$3-4.#|#-3$#$--$-#--4.#|#--$5-$-#--4.#|#-$$-#$-$-$8#|#--$-#5-#7-|##-9#7-|#4-#4-##7-|#5-$3-##7-|#--$$#$$--@#7-|#4-#4-##7-|11#8-',
    '8-5#4-|8-#3-5#|8-#-#$##--#|8-#5-$-#|9#-3#3-#|#4.--##-$--$3#|#4.4-$-$$-##-|#4.--##$--$-@#-|9#--$--##-|8-#-$-$--#-|8-3#-##-#-|10-#4-#-|10-6#-')

# Colors
BACK, TEXT = (120,)*3, (42,)*3 
COL = {
    '#': TEXT,           # Wall
    '@': (0, 153, 255),  # Player
    '+': (0, 122, 204),  # Player + Goal
    '$': (172, 115, 57), # Box
    '*': (134, 89, 45),  # Box + Goal
    '.': (70, 185, 70),  # Goal
    '-': BACK,           # Floor
    }

def wait(buttons = range(53)): # Wait for keypress 
    while True:
        for i in buttons:
            if keydown(i):
                sleep(0.1)
                return i

def decompress(rne): # Unpack the level
    grid = ['']
    while rne:
        if rne[0] == '|': grid.append('')
        elif 47 < ord(rne[0]) < 58:
            number = ''
            while 47 < ord(rne[0]) < 58: number += rne[0]; rne = rne[1:]
            grid[-1] += int(number)*rne[0]
        else: grid[-1] += rne[0]
        rne = rne[1:]
    return grid

def sett(x, y, test, valid, default):
    value = valid if grid[y][x] in test else default
    grid[y] = grid[y][:x] + value + grid[y][x+1:]
    fill_rect(X+d*x, Y+d*y, d, d, COL[value])

def win(): # Check for a win
    for y in grid:
        for char in y:
            if char=='$': return False
    return True

def menu(): # Rules, commands
    def display(*t):
        for i in list(range(len(t)))[::3]: draw_string(t[i], t[i+1], t[i+2], TEXT, BACK)
    fill_rect(0, 0, 320, 222, BACK)
    draw_string('SOKOBAN', 125, 6, TEXT, BACK)
    draw_string('Press a button to continue.', 25, 190, TEXT, BACK)
    display("You are a pusher employee in", 20, 40,
        "a store room.", 95, 60,
        "Push the boxes to their goal", 20, 90,
        "while minimizing moves.", 45, 110,
        "You can't push two boxes at", 20, 140,
        "once or pull them.", 70, 160)
    wait()
    fill_rect(0, 40, 320, 140, BACK)
    display("   COMMANDS       VISUALS", 25, 35,
        " Move : ARROWS    Wall :", 25, 60,
        " Undo : DEL     Player :", 25, 80,
        "Reset : EXE     + Goal :", 25, 100,
        " Help : ANS        Box :", 25, 120,
        "Level : +/-     + Goal :", 25, 140,
        " Quit : BACK      Goal :", 25, 160)
    for j in range(6): fill_rect(275, 60 + 20*j + 4, 10, 10, [COL['#'], COL['@'], COL['+'], COL['$'], COL['*'], COL['.']][j])
    wait()

menu()
count, i = len(LEVELS), 0
while True:
    # Initialisation
    grid, moves, his = decompress(LEVELS[i]), 0, ''
    
    # Interface drawing
    fill_rect(0, 0, 320, 222, BACK)
    draw_string('SOKOBAN', 125, 6, TEXT, BACK)
    lev = str(i+1) + '/' + str(count)
    draw_string(lev, 314-10*len(lev), 6, TEXT, BACK)
    draw_string('0', 6, 6, TEXT, BACK)
    X, Y, WIDTH, HEIGTH = 10, 33, 300, 180
    d = max(6, min(WIDTH//len(grid[0]), HEIGTH//len(grid), 12))
    X, Y = X + int((WIDTH - len(grid[0])*d)/2), Y + int((HEIGTH - len(grid)*d)/2)
    for y in range(len(grid)):
        for x in range(len(grid[y])):
            fill_rect(X+d*x, Y+d*y, d, d, COL[grid[y][x]])
            if grid[y][x] in '+@': XP, YP = x, y

    # Main loop
    while True:
        key = wait((0, 1, 2, 3, 4, 17, 45, 46, 51))
        if key in (0, 1, 2, 3, 17):
            if key == 17:
                if not(his): continue
                XN, YN = XP + (his[-1] in ('l', 'L')) - (his[-1] in ('r', 'R')), YP + (his[-1] in ('u', 'U')) - (his[-1] in ('d', 'D'))
                XN2, YN2 = 2*XP - XN, 2*YP - YN
            else:
                XN, YN = XP + (key==3) - (key==0), YP + (key==2) - (key==1)
                XN2, YN2 = 2*XN - XP, 2*YN - YP
                if (grid[YN][XN] == '#') or ((grid[YN][XN] in ('$', '*')) and (grid[YN2][XN2] in ('#', '$', '*'))): continue
                move = 'l'*(key==0) + 'u'*(key==1) + 'd'*(key==2) + 'r'*(key==3)
                if grid[YN][XN] in ('$', '*'): move = move.upper()
            
            if key != 17 or his[-1].islower(): sett(XP, YP, '+', '.', '-')
            else: sett(XP, YP, '+', '*', '$'); sett(XN2, YN2, '*', '.', '-')
            XP, YP = XN, YN
            if key != 17 and grid[YP][XP] in ('*', '$'): sett(XN2, YN2, '.', '*', '$')
            sett(XP, YP, '.*', '+', '@')
            
            moves = moves + 1 - 2*(key==17)
            his = his[:-1] if key==17 else (his + move)[-100:]
            draw_string(str(moves)+' ', 6, 6, TEXT, BACK)
            if win(): draw_string('YOU WON !', 115, 6, TEXT, BACK)
            continue
        elif key == 51: menu()
        else: i = (i + (key==45) - (key==46))%count
        break

During your visit to our site, NumWorks needs to install "cookies" or use other technologies to collect data about you in order to:

With the exception of Cookies essential to the operation of the site, NumWorks leaves you the choice: you can accept Cookies for audience measurement by clicking on the "Accept and continue" button, or refuse these Cookies by clicking on the "Continue without accepting" button or by continuing your browsing. You can update your choice at any time by clicking on the link "Manage my cookies" at the bottom of the page. For more information, please consult our cookies policy.