sokoban.py

Created by arthurjacquin

Created on June 30, 2020

4.91 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): # Dé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(): # Vérifie le niveau est terminé
for y in grid:
for char in y:
if char=='\$': return False
return True

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

def menu(): # Rè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()

# Choix de la cartouche et du niveau
cartouche = input('Choose cartridge (default: soko) : ')
try: exec('from ' + cartouche + ' import count')
except:
cartouche = 'soko'; from soko import count
try: i = int(input('Choose level (1 to ' +str(count)+ ') : '))-1; i *= (0<=i<count)
except: i = 0

while True:
exec('from ' + cartouche + ' import l' + str(i+1) + ' as level')
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