Example - Sokoban
This example shows a b-program implementation for the sokoban game. It uses the pygame package to display the game.
import bppy as bp
import itertools
import pygame
import time
# defining utility functions
def action_to_new_location(action, i, j):
if action == "Up":
return i - 1, j
if action == "Down":
return i + 1, j
if action == "Left":
return i, j - 1
if action == "Right":
return i, j + 1
def event_to_new_location(event):
return action_to_new_location(action=event.name, **event.data)
def event_to_2_steps_trajectory(event):
i, j = event_to_new_location(event)
return event_to_new_location(bp.BEvent(event.name, {"i": i, "j": j}))
def new_location_to_events(i, j):
return [bp.BEvent("Up", {"i": i+1, "j": j}),
bp.BEvent("Down", {"i": i-1, "j": j}),
bp.BEvent("Left", {"i": i, "j": j+1}),
bp.BEvent("Right", {"i": i, "j": j-1})]
def is_adjacent(l1, l2):
terms = list()
terms.append(l1[0] == l2[0] and l1[1] == l2[1]+1)
terms.append(l1[0] == l2[0] and l1[1] == l2[1]-1)
terms.append(l1[0] == l2[0]+1 and l1[1] == l2[1])
terms.append(l1[0] == l2[0]-1 and l1[1] == l2[1])
return sum(terms) == 1
def find_adjacent_objects(list_1, list_2):
return [(l1, l2) for l1 in list_1 for l2 in list_2 if is_adjacent(l1, l2)]
def find_adjacent_boxes(location, l):
return [(location, l2) for l2 in l if is_adjacent(location, l2)]
def block_action(neighbors_list):
def predicate(event):
p1 = event_to_new_location(event)
p2 = event_to_2_steps_trajectory(event)
return (p1, p2) in neighbors_list or (p2, p1) in neighbors_list
return predicate
def find(map, ch):
return [(i, j) for i, row in enumerate(map) for j, c in enumerate(row) if c == ch]
# defining b-threads
@bp.thread
def player(i, j):
directions = ["Up", "Down", "Left", "Right"]
while True:
e = yield bp.sync(request=[bp.BEvent(d, {"i": i, "j": j}) for d in directions])
i, j = event_to_new_location(e)
@bp.thread
def wall():
global walls_list
block_list = list(itertools.chain(*[new_location_to_events(i, j) for i, j in walls_list])) # use event_to_new_location(e)
yield bp.sync(block=block_list)
@bp.thread
def boxes():
global box_list, walls_list, target_list
while True:
neighbors_list = find_adjacent_objects(box_list, walls_list) + \
find_adjacent_objects(box_list, box_list)
double_object_movement = bp.EventSet(block_action(neighbors_list))
e = yield bp.sync(block=double_object_movement, waitFor=bp.All())
new_player_location = event_to_new_location(e)
if new_player_location in box_list:
new_box_location = event_to_2_steps_trajectory(e)
box_list.remove(new_player_location)
box_list.append(new_box_location)
@bp.thread
def box(i, j):
global box_list, walls_list, target_list
while True:
neighbors_list = find_adjacent_boxes((i, j), walls_list) + \
find_adjacent_boxes((i, j), box_list)
double_object_movement = bp.EventSet(block_action(neighbors_list))
e = yield bp.sync(block=double_object_movement, waitFor=bp.All())
new_player_location = event_to_new_location(e)
if new_player_location == (i, j):
new_box_location = event_to_2_steps_trajectory(e)
box_list.remove(new_player_location)
box_list.append(new_box_location)
i, j = new_box_location
@bp.thread
def map_printer(map):
main_surface = pygame.display.set_mode((32 * len(map[0]), 32 * len(map)))
count = 0
while True:
# Look for an event from keyboard, mouse, joystick, etc.
ev = pygame.event.poll()
if ev.type == pygame.QUIT: # Window close button clicked?
break
# Completely redraw the surface, starting with background
main_surface.fill((255, 255, 255))
for i in range(len(map)):
for j in range(len(map[i])):
# Copy map to surface
pygame.draw.rect(main_surface, map_dict[map[i][j]], pygame.Rect(j * 32, i * 32, j * 32 + 32, i * 32 + 32))
# Now that everything is drawn, put it on display!
pygame.display.flip()
time.sleep(0.3)
# print(count)
count += 1
e = yield bp.sync(waitFor=bp.All())
map = ",".join(map).replace("a", " ").split(",")
map = ",".join(map).replace("A", "t").split(",")
i, j = event_to_new_location(e)
if map[i][j] == "b" or map[i][j] == "B":
i2, j2 = event_to_2_steps_trajectory(e)
if map[i2][j2] == "t":
map[i2] = map[i2][:j2] + "B" + map[i2][j2 + 1:]
else:
map[i2] = map[i2][:j2] + "b" + map[i2][j2 + 1:]
if map[i][j] == "b":
map[i] = map[i][:j] + "a" + map[i][j + 1:]
else:
map[i] = map[i][:j] + "A" + map[i][j + 1:]
elif map[i][j] == "t":
map[i] = map[i][:j] + "A" + map[i][j + 1:]
else:
map[i] = map[i][:j] + "a" + map[i][j + 1:]
# definition of lists to store the locations of walls, boxes, and targets in the game map.
walls_list = []
box_list = []
target_list = []
# definition of a dictionary mapping the elements of the game to their color representation in the Pygame window.
map_dict = {
" ": (0,0,0),
"X": (153,76,0),
"b": (255,178,102),
"B": (255,229,204),
"a": (255,255,255),
"A": (204,255,204),
"t": (0,204,0)
}
def init_bprogram(): # initializes the BProgram with the corresponding b-threads for the provided map.
global walls_list, box_list, target_list
map = [
"XXXXXXXX",
"XX t X",
"Xab bX X",
"X b t X",
"XXXXt X",
"XXXXXXXX",
"XXXXXXXX",
"XXXXXXXX"
]
walls_list = find(map, "X")
box_list = find(map, "b") + find(map, "B")
empty_target_list = find(map, "t") + find(map, "A")
full_target_list = find(map, "B")
target_list = empty_target_list + full_target_list
player_locations = find(map, "a") + find(map, "A")
player_location = player_locations[0]
bthreads_list = [map_printer(map)] + [player(*player_location), wall()] + [box(*l) for l in box_list]
return bp.BProgram(bthreads=bthreads_list, event_selection_strategy=bp.SimpleEventSelectionStrategy())
# initialize a b-program instance and run.
init_bprogram().run()