sokoban_demo.py

Created by cent20

Created on April 24, 2021

5.23 KB


# Sokoban
# https://workshop.numworks.com/python/arthurjacquin/sokoban
# arthur@jacquin.xyz

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

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 decompress(rne): # Décompresse le niveau
    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): # Actualise la valeur d'une case
    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(): # Vérifie le niveau est terminé
    for y in grid:
        for char in y:
            if char=='$': return False
    return True

def wait(buttons = range(53)): # Attends qu'une touche soit pressée 
    while True:
        for i in buttons:
            if keydown(i):
                sleep(0.1)
                return i

def menu(): # Règles, commandes, visuels
    def iter(*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)
    iter("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)
    iter("   COMMANDS       VISUALS", 25, 35,
        " Move : ARROWS    Wall :", 25, 60,
        " Undo : CLEAR   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()

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#-')

menu()
i, count = 0, 5
while True:
    level = LEVELS[i]
    grid, moves, his = decompress(level), 0, '' # Initialisation
    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 # Cadre de jeu
    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]]) # Tracage initial
            if grid[y][x] in '+@': XP, YP = x, y
    while True:
        key = wait((0, 1, 2, 3, 17, 45, 46, 51, 52))
        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
        del level
        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.