import pygame, sys, os from pygame.locals import * from collections import deque def to_box(level, index): if level[index] == '-' or level[index] == '@': level[index] = '$' else: level[index] = '*' def to_man(level, i): if level[i] == '-' or level[i] == '$': level[i] = '@' else: level[i] = '+' def to_floor(level, i): if level[i] == '@' or level[i] == '$': level[i] = '-' else: level[i] = '.' def to_offset(d, width): d4 = [-1, -width, 1, width] m4 = ['l', 'u', 'r', 'd'] return d4[m4.index(d.lower())] def b_manto(level, width, b, m, t): maze = list(level) maze[b] = '#' if m == t: return 1 queue = deque([]) queue.append(m) d4 = [-1, -width, 1, width] m4 = ['l', 'u', 'r', 'd'] while len(queue) > 0: pos = queue.popleft() for i in range(4): newpos = pos + d4[i] if maze[newpos] in ['-', '.']: if newpos == t: return 1 maze[newpos] = i queue.append(newpos) return 0 def b_manto_2(level, width, b, m, t): maze = list(level) maze[b] = '#' maze[m] = '@' if m == t: return [] queue = deque([]) queue.append(m) d4 = [-1, -width, 1, width] m4 = ['l', 'u', 'r', 'd'] while len(queue) > 0: pos = queue.popleft() for i in range(4): newpos = pos + d4[i] if maze[newpos] in ['-', '.']: maze[newpos] = i queue.append(newpos) if newpos == t: path = [] while maze[t] != '@': path.append(m4[maze[t]]) t = t - d4[maze[t]] return path return [] class Sokoban: def __init__(self): self.level = list( '----#####--------------#---#--------------#$--#------------###--$##-----------#--$-$-#---------###-#-##-#---#######---#-##-#####--..##-$--$----------..######-###-#@##--..#----#-----#########----#######--------') self.w = 19 self.h = 11 self.man = 163 self.hint = list(self.level) self.solution = [] self.push = 0 self.todo = [] self.auto = 0 self.sbox = 0 self.queue = [] def draw(self, screen, skin): w = skin.get_width() / 4 offset = (w - 4) / 2 for i in range(0, self.w): for j in range(0, self.h): if self.level[j * self.w + i] == '#': screen.blit(skin, (i * w, j * w), (0, 2 * w, w, w)) elif self.level[j * self.w + i] == '-': screen.blit(skin, (i * w, j * w), (0, 0, w, w)) elif self.level[j * self.w + i] == '@': screen.blit(skin, (i * w, j * w), (w, 0, w, w)) elif self.level[j * self.w + i] == '$': screen.blit(skin, (i * w, j * w), (2 * w, 0, w, w)) elif self.level[j * self.w + i] == '.': screen.blit(skin, (i * w, j * w), (0, w, w, w)) elif self.level[j * self.w + i] == '+': screen.blit(skin, (i * w, j * w), (w, w, w, w)) elif self.level[j * self.w + i] == '*': screen.blit(skin, (i * w, j * w), (2 * w, w, w, w)) if self.sbox != 0 and self.hint[j * self.w + i] == '1': screen.blit(skin, (i * w + offset, j * w + offset), (3 * w, 3 * w, 4, 4)) def move(self, d): self._move(d) self.todo = [] def _move(self, d): self.sbox = 0 h = to_offset(d, self.w) h2 = 2 * h if self.level[self.man + h] == '-' or self.level[self.man + h] == '.': # move to_man(self.level, self.man + h) to_floor(self.level, self.man) self.man += h self.solution += d elif self.level[self.man + h] == '*' or self.level[self.man + h] == '$': if self.level[self.man + h2] == '-' or self.level[self.man + h2] == '.': # push to_box(self.level, self.man + h2) to_man(self.level, self.man + h) to_floor(self.level, self.man) self.man += h self.solution += d.upper() self.push += 1 def undo(self): if self.solution.__len__() > 0: self.todo.append(self.solution[-1]) self.solution.pop() h = to_offset(self.todo[-1], self.w) * -1 if self.todo[-1].islower(): # undo a move to_man(self.level, self.man + h) to_floor(self.level, self.man) self.man += h else: # undo a push to_floor(self.level, self.man - h) to_box(self.level, self.man) to_man(self.level, self.man + h) self.man += h self.push -= 1 def redo(self): if self.todo.__len__() > 0: self._move(self.todo[-1].lower()) self.todo.pop() def manto(self, x, y): maze = list(self.level) maze[self.man] = '@' queue = deque([]) queue.append(self.man) d4 = [-1, -self.w, 1, self.w] m4 = ['l', 'u', 'r', 'd'] while len(queue) > 0: pos = queue.popleft() for i in range(4): newpos = pos + d4[i] if maze[newpos] in ['-', '.']: maze[newpos] = i queue.append(newpos) # print str(maze) t = y * self.w + x if maze[t] in range(4): self.todo = [] while maze[t] != '@': self.todo.append(m4[maze[t]]) t = t - d4[maze[t]] # print self.todo self.auto = 1 def automove(self): if self.auto == 1 and self.todo.__len__() > 0: self._move(self.todo[-1].lower()) self.todo.pop() else: self.auto = 0 def boxhint(self, x, y): d4 = [-1, -self.w, 1, self.w] m4 = ['l', 'u', 'r', 'd'] b = y * self.w + x maze = list(self.level) to_floor(maze, b) to_floor(maze, self.man) mark = maze * 4 size = self.w * self.h self.queue = [] head = 0 for i in range(4): if b_manto(maze, self.w, b, self.man, b + d4[i]): if len(self.queue) == 0: self.queue.append((b, i, -1)) mark[i * size + b] = '1' # print self.queue while head < len(self.queue): pos = self.queue[head] head += 1 # print pos for i in range(4): if mark[pos[0] + i * size] == '1' and maze[pos[0] - d4[i]] in ['-', '.']: # print i if mark[pos[0] - d4[i] + i * size] != '1': self.queue.append((pos[0] - d4[i], i, head - 1)) for j in range(4): if b_manto(maze, self.w, pos[0] - d4[i], pos[0], pos[0] - d4[i] + d4[j]): mark[j * size + pos[0] - d4[i]] = '1' for i in range(size): self.hint[i] = '0' for j in range(4): if mark[j * size + i] == '1': self.hint[i] = '1' # print self.hint def boxto(self, x, y): d4 = [-1, -self.w, 1, self.w] m4 = ['l', 'u', 'r', 'd'] om4 = ['r', 'd', 'l', 'u'] b = y * self.w + x maze = list(self.level) to_floor(maze, self.sbox) to_floor(maze, self.man) # make a copy of working maze by removing the selected box and the man for i in range(len(self.queue)): if self.queue[i][0] == b: self.todo = [] j = i while self.queue[j][2] != -1: self.todo.append(om4[self.queue[j][1]].upper()) k = self.queue[j][2] if self.queue[k][2] != -1: self.todo += b_manto_2(maze, self.w, self.queue[k][0], self.queue[k][0] + d4[self.queue[k][1]], self.queue[k][0] + d4[self.queue[j][1]]) else: self.todo += b_manto_2(maze, self.w, self.queue[k][0], self.man, self.queue[k][0] + d4[self.queue[j][1]]) j = k # print self.todo self.auto = 1 return print('not found!') def mouse(self, x, y): if x >= self.w or y >= self.h: return m = y * self.w + x if self.level[m] in ['-', '.']: if self.sbox == 0: self.manto(x, y) else: self.boxto(x, y) elif self.level[m] in ['$', '*']: if self.sbox == m: self.sbox = 0 else: self.sbox = m self.boxhint(x, y) elif self.level[m] in ['-', '.', '@', '+']: self.boxto(x, y) def main(): # start pygame pygame.init() screen = pygame.display.set_mode((400, 300)) # load skin skinfilename = os.path.join('borgar.png') try: skin = pygame.image.load(skinfilename) except pygame.error as msg: print('cannot load skin') raise SystemExit(msg) skin = skin.convert() # print skin.get_at((0,0)) # screen.fill((255,255,255)) screen.fill(skin.get_at((0, 0))) pygame.display.set_caption('sokoban.py') # create Sokoban object skb = Sokoban() skb.draw(screen, skin) # clock = pygame.time.Clock() pygame.key.set_repeat(200, 50) # main game loop while True: clock.tick(60) if skb.auto == 0: for event in pygame.event.get(): if event.type == QUIT: # print skb.solution pygame.quit() sys.exit() elif event.type == KEYDOWN: if event.key == K_LEFT: skb.move('l') skb.draw(screen, skin) elif event.key == K_UP: skb.move('u') skb.draw(screen, skin) elif event.key == K_RIGHT: skb.move('r') skb.draw(screen, skin) elif event.key == K_DOWN: skb.move('d') skb.draw(screen, skin) elif event.key == K_BACKSPACE: skb.undo() skb.draw(screen, skin) elif event.key == K_SPACE: skb.redo() skb.draw(screen, skin) elif event.type == MOUSEBUTTONUP and event.button == 1: mousex, mousey = event.pos mousex /= (skin.get_width() / 4) mousey /= (skin.get_width() / 4) skb.mouse(mousex, mousey) skb.draw(screen, skin) else: skb.automove() skb.draw(screen, skin) pygame.display.update() pygame.display.set_caption(skb.solution.__len__().__str__() + '/' + skb.push.__str__() + ' - sokoban.py') if __name__ == '__main__': main()