def __create_map(self): self.map = MapPanel(0, 0, self.MAP_WIDTH, self.MAP_HEIGHT + 1, self.EMPTY, border=PanelBorder.create(bottom="-")) self.panels += [self.map] self.place_objects(self.TREE, self.NUM_OF_ROCKS_START) self.place_objects(self.ROCK, self.NUM_OF_TREES_START) self.place_objects(self.SNOWMAN, self.NUM_OF_SNOWMAN_START) self.place_objects(self.COIN, self.NUM_OF_COINS_START) self.place_objects(self.HEART, self.NUM_OF_HEARTS_START) self.place_objects(self.JUMP, self.NUM_OF_JUMPS_START) # make a clearing for the player for y in range(8): for x in range(self.MAP_WIDTH): self.map[(x, self.MAP_HEIGHT - 1 - y)] = self.EMPTY # place decorative trees self.map[(self.player_pos[0] + 5, self.player_pos[1])] = self.TREE self.map[(self.player_pos[0] - 5, self.player_pos[1])] = self.TREE # place initial hearts self.map[(self.player_pos[0], self.player_pos[1] - 2)] = self.HEART self.map[(self.player_pos[0], self.player_pos[1] - 3)] = self.HEART
def init_board(self): self.map = MapPanel(0, 0, self.MAP_WIDTH, self.MAP_HEIGHT, self.EMPTY, border=PanelBorder.create(bottom=self.MAP_BOTTOM)) self.panels.append(self.map)
def init_board(self): self.map = MapPanel(0, 0, self.MAP_WIDTH, self.MAP_HEIGHT, self.EMPTY, border=PanelBorder.create(bottom="-")) self.panels += [self.map] self.map[(self.player_pos[0], self.player_pos[1])] = self.PLAYER_C self.map[(self.player_right[0], self.player_right[1])] = self.PLAYER_R self.map[(self.player_left[0], self.player_left[1])] = self.PLAYER_L self.draw_level()
def init_board(self): self.map = MapPanel(0, 3, self.MAP_WIDTH, self.MAP_HEIGHT + 1, self.EMPTY) # border=PanelBorder.create(bottom="-")) self.panels += [self.map] mapmap = { '|': self.WALL, '-': self.HYPHEN, 'L': self.L, '7': self.SEVEN, 'J': self.J, 'F': self.F, '.': self.DOT, 'O': self.POWER, '=': self.DOOR, '#': self.FULL, ' ': self.EMPTY, 'B': self.BLINKY, 'P': self.PINKY, 'I': self.INKY, 'C': self.CLYDE } # reset positions first so ghosts not in maze when filled self.reset_positions() # make fruit counter negative to eliminate the case when you # switch levels when a fruit is visible self.fruit_visible = -1 # open map file x = 0 y = 0 with open("map.txt") as f: for line in f: x = 0 for char in line: if char != '\n': self.map[(x, y)] = mapmap[char] x += 1 y += 1 self.map[(self.player_pos[0], self.player_pos[1])] = self.PLAYER self.redraw_ghosts() self.redraw_lives() self.print_ready()
def test_edges(): panel = MapPanel(1, 1, 2, 2) frame = GridFrameBuffer(5, 5, init_value=" ") panel.add("1", (0, 0)) panel.add("2", (1, 0)) panel.add("3", (0, 1)) panel.add("4", (1, 1)) panel.redraw(frame) exp = [" ", " 12 ", " 34 ", " ", " "] exp_frame = GridFrameBuffer.from_string_array(exp) assert frame == exp_frame
def init_board(self): self.map = MapPanel(1, 1, self.MAP_WIDTH, self.MAP_HEIGHT, self.EMPTY, border=PanelBorder.create(bottom=True, left=True, right=True, top=True)) self.panels += [self.map]
def __create_map(self): self.map = MapPanel(0, 0, self.MAP_WIDTH, self.MAP_HEIGHT + 1, self.EMPTY, border=PanelBorder.create(bottom=True, left=True, right=True, top=True)) self.panels += [self.map] self.place_bikes() for i in self.CORRUPTION: self.map[i.pos()] = i.char self.map[self.USER.pos()] = self.USER.char
class SpaceInvaders(GridGame): MAP_WIDTH = 60 MAP_HEIGHT = 25 SCREEN_WIDTH = 60 SCREEN_HEIGHT = MAP_HEIGHT + 6 MSG_START = 20 MAX_MSG_LEN = SCREEN_WIDTH - MSG_START - 1 CHAR_WIDTH = 16 CHAR_HEIGHT = 16 GAME_TITLE = "Space Invaders" CHAR_SET = "resources/terminal16x16_gs_ro.png" NUM_OF_INVADERS = 10 TOTAL_INVADERS = 10 MAX_TURNS = 900 score = 0 # we use these for moving the mothership easily LEFT = -1 RIGHT = 1 mothership_direction = RIGHT MOTHERSHIP_SPEED = 3 mothership_exists = False MOTHERSHIP_L = chr(241) MOTHERSHIP_C = chr(242) MOTHERSHIP_R = chr(243) MOTHERSHIP_POINTS = 300 INVADER0_POINTS = 50 INVADER1_POINTS = 40 INVADER2_POINTS = 30 INVADER0 = chr(244) # worth 50 points INVADER1 = chr(245) # worth 40 points INVADER2 = chr(246) # worth 30 points # create a list of sprite to blit by type on redraw INVADER_SPRITE = [INVADER0, INVADER1, INVADER2] BARRIER_1 = chr(247) BARRIER_2 = chr(248) BARRIER_3 = chr(249) BARRIER_4 = chr(250) MISSILE = chr(251) BULLET = chr(252) PLAYER_L = chr(253) PLAYER_C = chr(254) PLAYER_R = chr(255) EMPTY = ' ' OUT_OF_BOUNDS = chr(240) fire_rate = 2 # the fire rate of invaders def __init__(self, random): self.random = random self.running = True self.centerx = self.MAP_WIDTH // 2 self.centery = self.MAP_HEIGHT // 2 self.player_pos = [self.centerx, (int)(self.MAP_HEIGHT * .99)] self.player_right = [self.centerx + 1, (int)(self.MAP_HEIGHT * .99)] self.player_left = [self.centerx - 1, (int)(self.MAP_HEIGHT * .99)] self.invaders = [] self.drops_eaten = 0 self.invaders_left = 0 self.missiles_left = 0 self.apple_pos = [] self.objects = [] self.turns = 0 self.bullets_fired = 0 self.level = 0 self.gravity_power = 1 self.bullet_speed = 3 self.invader_speed = 1 self.placed_invaders = 0 self.movement_direction = Direction.RIGHT self.msg_panel = MessagePanel(self.MSG_START, self.MAP_HEIGHT + 1, self.SCREEN_WIDTH - self.MSG_START, 5) self.status_panel = StatusPanel(0, self.MAP_HEIGHT + 1, self.MSG_START, 5) self.panels = [self.msg_panel, self.status_panel] self.msg_panel.add("Welcome to " + self.GAME_TITLE + "!!!") self.lives = 3 self.life_lost = False self.at_bottom = False self.debug = False def init_board(self): self.map = MapPanel(0, 0, self.MAP_WIDTH, self.MAP_HEIGHT, self.EMPTY, border=PanelBorder.create(bottom="-")) self.panels += [self.map] self.map[(self.player_pos[0], self.player_pos[1])] = self.PLAYER_C self.map[(self.player_right[0], self.player_right[1])] = self.PLAYER_R self.map[(self.player_left[0], self.player_left[1])] = self.PLAYER_L self.draw_level() def start_game(self): # This is a hack to make sure that the map array is setup before the player makes their first move. self.player.bot_vars = self.get_vars_for_bot() def create_new_player(self, prog): self.player = DefaultGridPlayer(prog, self.get_move_consts()) return self.player def draw_level(self): if self.debug: print("Redrawing map! turn: %d" % (self.turns)) start_barrier = 5 # we want to offset the first barrier barrier_height = 3 barrier_width = 5 set_sb = False self.mothership_exists = False for w in range(0, 60): for h in range(0, 25): self.map[(w,h)] = self.EMPTY for w in range(0, 60): for h in range(0, 25): # generating the invaders -- 5 rows of 11, alternating columns and rows if h < 10 and w >= 20 and w <= 40: if (w % 2 == 0) and (h % 2 == 1): if h == 9 or h == 7: self.invaders.append(Invader((w, h), 2)) self.map[(w, h)] = self.INVADER2 elif h == 5 or h == 3: self.invaders.append(Invader((w, h), 1)) self.map[(w, h)] = self.INVADER1 elif h == 1: self.invaders.append(Invader((w, h), 0)) self.map[(w, h)] = self.INVADER0 # generate the barriers if h >= self.MAP_HEIGHT - 1 - barrier_height and h < self.MAP_HEIGHT - 1: # it's a barrier row if w >= start_barrier and w <= start_barrier + barrier_width: # we draw the barrier self.map[(w, h)] = self.BARRIER_4 if w == start_barrier + barrier_width: set_sb = True if set_sb: start_barrier += 12 # to achieve spacing between barriers...hopefully set_sb = False self.set_bottom_invaders() def set_bottom_invaders(self): # all_invaders = [ x for x in self.invaders ] cols = {} # sort all the invaders into columns for invader in self.invaders: pos = invader.get_pos() if pos[0] in cols: cols[pos[0]].append(invader) else: empty = [] empty.append(invader) cols[pos[0]] = empty # sort each column on y value descending (high y values are "lower") for i in range(0, self.MAP_WIDTH): if i in cols: cols[i] = sorted(cols[i], key=lambda x: x.get_pos()[1], reverse=True) cols[i][0].set_bottom(True) def handle_mothership(self): # if there is a bullet in center_positon, center_position + direction * 1, * 2, or * 3 or * 4 (the right/left requires the *4, because the offset from center position is 1), or any of those positions are out of bounds, remove the entire mothership # move the center location over by 3. if not self.mothership_exists: return old_center = self.map.get_all_pos(self.MOTHERSHIP_C).pop()[0] redraw = True for i in range(0, 5): test_x = old_center + i * self.mothership_direction if test_x < 0 or test_x >= self.MAP_WIDTH: # we fell off the map # remove mothership redraw = False self.mothership_exists = False clear_l = self.map.get_all_pos(self.MOTHERSHIP_L).pop() clear_c = self.map.get_all_pos(self.MOTHERSHIP_C).pop() clear_r = self.map.get_all_pos(self.MOTHERSHIP_R).pop() self.map[clear_l] = self.EMPTY self.map[clear_c] = self.EMPTY self.map[clear_r] = self.EMPTY if redraw: new_l = (clear_l[0] + 3 * self.mothership_direction, 0) new_c = (clear_c[0] + 3 * self.mothership_direction, 0) new_r = (clear_r[0] + 3 * self.mothership_direction, 0) self.map[new_l] = self.MOTHERSHIP_L self.map[new_c] = self.MOTHERSHIP_C self.map[new_r] = self.MOTHERSHIP_R return def launch_mothership(self): if self.turns % 45 == 0: # launch mothership every 45 turns self.mothership_exists = True # launch the ship # if the turns are even, we launch from right and vice versa center_x = 1 # when launching from left we have to leave space for the left element if self.turns % 2 == 0: center_x = (int)(self.MAP_WIDTH * .99) - 1 self.mothership_direction = self.LEFT # launch from right # the top row is 0 else: self.mothership_direction = self.RIGHT if self.debug: print("Launching mothership at %d (turn: %d)" % (center_x, self.turns)) position_l = (center_x - 1, 0) position_c = (center_x, 0) position_r = (center_x + 1, 0) self.map[position_l] = self.MOTHERSHIP_L self.map[position_c] = self.MOTHERSHIP_C self.map[position_r] = self.MOTHERSHIP_R def fire_missiles(self): for invader in self.invaders: if invader.get_bottom() and not invader.get_missile(): # it can fire invader_pos = invader.get_pos() # first we determine if the invader can fire...are there any invaders below it? # second we determine (randomly) if the invader will fire fire = self.random.randint(0, 30 - self.fire_rate) # hacky way of increasing fire percentage if fire == 2: # hacky way to set it to fire at a low percentage only missile_pos = (invader_pos[0], invader_pos[1] + self.gravity_power) # fixed missile collision problem at the bottom if (missile_pos[0] == self.player_pos[0] and missile_pos[1] == self.player_pos[1]) or (missile_pos[0] == self.player_left[0] and missile_pos[1] == self.player_left[1]) or (missile_pos[0] == self.player_right[0] and missile_pos[1] == self.player_right[1]): if self.debug: print("You lost a life!") self.msg_panel.add(["You lost a life!"]) self.map[(self.player_pos[0], self.player_pos[1])] = self.EMPTY self.map[(self.player_right[0], self.player_right[1])] = self.EMPTY self.map[(self.player_left[0], self.player_left[1])] = self.EMPTY self.lives -= 1 # reset to center self.player_pos = [self.centerx, (int)(self.MAP_HEIGHT * .99)] self.player_right = [self.centerx + 1, (int)(self.MAP_HEIGHT * .99)] self.player_left = [self.centerx - 1, (int)(self.MAP_HEIGHT * .99)] # remove all missiles for invader in self.invaders: invader.set_missile(False) self.lost_life = True self.map[(self.player_pos[0], self.player_pos[1])] = self.PLAYER_C self.map[(self.player_left[0], self.player_left[1])] = self.PLAYER_L self.map[(self.player_right[0], self.player_right[1])] = self.PLAYER_R # fixed collision problem at the bottom if missile_pos[1] < self.MAP_HEIGHT: invader.set_missile(missile_pos) def fire_turret(self): # place the bullet one over the position # can only have on bullet on the screen at once if len(self.map.get_all_pos(self.BULLET)) == 0: bullet_pos = (self.player_pos[0], self.player_pos[1] - 1) if self.is_barrier(self.map[bullet_pos]): self.map[bullet_pos] = self.decrement_barrier(self.map[bullet_pos]) elif self.map[bullet_pos] == self.MISSILE: self.map[bullet_pos] = self.EMPTY else: self.map[bullet_pos] = self.BULLET self.bullets_fired += 1 def is_barrier(self, c): if c == self.BARRIER_1 or c == self.BARRIER_2 or c == self.BARRIER_3 or c == self.BARRIER_4: return True return False def decrement_barrier(self, c): if c == self.BARRIER_1: return self.EMPTY elif c == self.BARRIER_2: return self.BARRIER_1 elif c == self.BARRIER_3: return self.BARRIER_2 elif c == self.BARRIER_4: return self.BARRIER_3 else: return self.EMPTY def move_invaders(self): # determine if we can continue moving in the same direction (nothing will fall off the edge) move_down = False positions = None if self.movement_direction == Direction.RIGHT: # sort descending by x value positions = sorted([x.get_pos() for x in self.invaders], key=lambda x: x[0], reverse=True) # TODO: will this ever occur when we are not testing? Like when someone wins? if len(positions) == 0: return if positions[0][0] + 1 >= self.MAP_WIDTH: move_down = True self.movement_direction = Direction.LEFT elif self.movement_direction == Direction.LEFT: positions = sorted([x.get_pos() for x in self.invaders], key=lambda x: x[0], reverse=False) if positions[0][0] - 1 < 0: move_down = True self.fire_rate += 1 # every time they move down, they fire a little bit faster self.movement_direction = Direction.RIGHT # sort ascending by x value if move_down: self.move_invaders_down() self.move_invaders() # to move one in the new direction after going down elif not move_down: movement = self.invader_speed if self.movement_direction == Direction.LEFT: movement *= -1 # go the other direction for invader in self.invaders: pos = invader.get_pos() new_pos = (pos[0] + movement, pos[1]) # if not self.map[new_pos] == self.BULLET: invader.set_pos(new_pos) # else: # #if its a barrier, we need to decrement it # #if its a bullet, we need to remove it # if self.map[new_pos] == self.BULLET: # self.map[new_pos] == self.EMPTY # print("collision with a bullet!") # elif is_barrier(self.map[new_pos]): # self.map[new_pos] = decrement_barrier(self.map[new_pos]) # self.invaders.remove(invader) def move_invaders_down(self): for invader in self.invaders: pos = invader.get_pos() new_pos = (pos[0], pos[1] + 1) if new_pos[1] < self.MAP_HEIGHT: # if self.map[new_pos] != self.BULLET: #it wasn't a hit invader.set_pos(new_pos) # else: #it was hit # self.map[new_pos] = self.EMPTY # self.invaders.remove(invader) else: self.at_bottom = True def move_bullets(self): # there should only be one tbh # we need to get the list of all invader positions invader_positions = [x.get_pos() for x in self.invaders] missile_positions = [x.get_missile() for x in self.invaders] # we generate a list of all the mothership positions that we will encounter mothership_locations = [] if self.mothership_exists: mothership_locations.append(self.map.get_all_pos(self.MOTHERSHIP_L).pop()) mothership_locations.append(self.map.get_all_pos(self.MOTHERSHIP_C).pop()) mothership_locations.append(self.map.get_all_pos(self.MOTHERSHIP_R).pop()) # we add 2 because we need to detect a collision with the left/right, as well as the center. for pos in sorted(self.map.get_all_pos(self.BULLET), key=lambda x: x[1], reverse=False): still_exists = True # we iterate over all the positions that the bullet "warped" through to detect any collisions for i in range(0, self.bullet_speed): # 0 - 1 so we clear the initial position clear = (pos[0], pos[1] - i) if clear[1] >= 0 and still_exists: if clear in invader_positions: # we need to find which invader it was and delete it for invader in self.invaders: if invader.get_pos() == clear: # increment the score for the aliens if invader.sprite == 0: self.score += self.INVADER0_POINTS if self.debug: print("Hit INVADER0! (%d left)" % (len(self.invaders))) if invader.sprite == 1: self.score += self.INVADER1_POINTS if self.debug: print("Hit INVADER1! (%d left)" % (len(self.invaders))) if invader.sprite == 2: self.score += self.INVADER2_POINTS if self.debug: print("Hit INVADER2! (%d left)" % (len(self.invaders))) self.invaders.remove(invader) still_exists = False self.map[clear] = self.EMPTY self.map[pos] = self.EMPTY elif clear in mothership_locations: still_exists = False # remove the mothership from map self.map[self.map.get_all_pos(self.MOTHERSHIP_L).pop()] = self.EMPTY self.map[self.map.get_all_pos(self.MOTHERSHIP_R).pop()] = self.EMPTY self.map[self.map.get_all_pos(self.MOTHERSHIP_C).pop()] = self.EMPTY self.mothership_exists = False self.score += self.MOTHERSHIP_POINTS elif self.map[clear] == self.MISSILE: # we need to track downt he invader which owns this missile for invader in self.invaders: if invader.get_missile() == clear: invader.set_missile(False) self.map[pos] = self.EMPTY self.map[clear] = self.EMPTY still_exists = False elif self.is_barrier(self.map[clear]): self.map[clear] = self.decrement_barrier(self.map[clear]) self.map[pos] = self.EMPTY still_exists = False else: self.map[clear] = self.EMPTY self.map[pos] = self.EMPTY new_pos = (pos[0], pos[1] - self.bullet_speed) if new_pos[1] >= 0 and still_exists: if new_pos in invader_positions: for invader in self.invaders: if invader.get_pos() == new_pos: self.invaders.remove(invader) still_exists = False self.map[clear] = self.EMPTY elif new_pos in missile_positions: for invader in self.invaders: if invader.get_missile() == new_pos: invader.set_missile(False) still_exists = False self.map[new_pos] = self.EMPTY elif self.is_barrier(self.map[new_pos]): self.map[new_pos] = self.decrement_barrier(self.map[new_pos]) still_exists = False if still_exists: self.map[new_pos] = self.BULLET self.map[clear] = self.EMPTY # if not still_exists: # self.map[new_pos] = self.EMPTY def do_turn(self): self.handle_key(self.player.move) self.player.bot_vars = self.get_vars_for_bot() # End of the game if self.turns >= self.MAX_TURNS: self.running = False self.msg_panel.add("You are out of moves.") if self.at_bottom: # Not quite sure why it's not adding this message to the panel or updating the lives counter at the bottom. # It's a UI "feature" self.msg_panel.add(["They have invaded!"]) self.lives = 0 if self.lives == 0: self.running = False msg = "You lost all your lives" self.msg_panel.add(msg) if self.debug: print(msg) if self.life_lost: self.life_lost = False msg = "You lost a life" self.msg_panel.add(msg) if self.debug: print(msg) def handle_key(self, key): self.turns += 1 self.map[(self.player_pos[0], self.player_pos[1])] = self.EMPTY self.map[(self.player_right[0], self.player_right[1])] = self.EMPTY self.map[(self.player_left[0], self.player_left[1])] = self.EMPTY # if key == "w": # self.player_pos[1] -= 1 # if key == "s": # self.player_pos[1] += 1 if key == "a": if self.player_left[0] - 1 >= 0: self.player_pos[0] -= 1 self.player_right[0] -= 1 self.player_left[0] -= 1 if key == "d": if self.player_right[0] + 1 < self.MAP_WIDTH: self.player_pos[0] += 1 self.player_right[0] += 1 self.player_left[0] += 1 if key == "w": self.fire_turret() if key == "Q": self.running = False return # move the invaders self.move_bullets() # we do hits detection first if len(self.invaders) == 0: self.level += 1 if self.debug: print("*************************************************") print("No more invaders! New level: %d" % self.level) print("*************************************************") self.draw_level() return self.move_invaders() self.move_missiles(self.gravity_power) # move all drops down 1 self.handle_mothership() # collision detection position = self.map[(self.player_pos[0], self.player_pos[1])] position_left = self.map[(self.player_left[0], self.player_left[1])] position_right = self.map[(self.player_right[0], self.player_right[1])] collision = False # if position == self.MISSILE or position == self.INVADER2 or position == self.INVADER1 or position == self.INVADER0: # collision = True # if position_left == self.MISSILE or position == self.INVADER2 or position == self.INVADER1 or position == self.INVADER0: # collision = True # if position_right == self.MISSILE or position == self.INVADER2 or position == self.INVADER1 or position == self.INVADER0: # collision = True # fixed collision issue at the bottom for invader in self.invaders: invader_pos = invader.get_pos() if self.player_pos[0] == invader_pos[0] and self.player_pos[1] == invader_pos[1]: collision = True elif self.player_left[0] == invader_pos[0] and self.player_left[1] == invader_pos[1]: collision = True elif self.player_right[0] == invader_pos[0] and self.player_right[1] == invader_pos[1]: collision = True elif position == self.MISSILE or position_left == self.MISSILE or position_right == self.MISSILE: collision = True # fixed collision issue at the bottom # self.msg_panel.remove("You lost a life!") if collision: if self.debug: print("You lost a life!") self.msg_panel.add(["You lost a life!"]) position = self.EMPTY # clear the position self.lives -= 1 # reset to center self.player_pos = [self.centerx, (int)(self.MAP_HEIGHT * .99)] self.player_right = [self.centerx + 1, (int)(self.MAP_HEIGHT * .99)] self.player_left = [self.centerx - 1, (int)(self.MAP_HEIGHT * .99)] # remove all missiles for invader in self.invaders: invader.set_missile(False) self.lost_life = True self.map[(self.player_pos[0], self.player_pos[1])] = self.PLAYER_C self.map[(self.player_left[0], self.player_left[1])] = self.PLAYER_L self.map[(self.player_right[0], self.player_right[1])] = self.PLAYER_R # Fire the missiles self.fire_missiles() self.launch_mothership() # first we clear all the prevoius invaders for old_invader in self.map.get_all_pos(self.INVADER2): self.map[old_invader] = self.EMPTY for old_invader in self.map.get_all_pos(self.INVADER1): self.map[old_invader] = self.EMPTY for old_invader in self.map.get_all_pos(self.INVADER0): self.map[old_invader] = self.EMPTY for old_missile in self.map.get_all_pos(self.MISSILE): self.map[old_missile] = self.EMPTY for invader in self.invaders: self.map[invader.get_pos()] = self.INVADER_SPRITE[invader.sprite] if invader.get_missile(): self.map[invader.get_missile()] = self.MISSILE def move_missiles(self, gravity_power): # gravity power is the number of positions a drop will fall per turn for invader in self.invaders: pos = invader.get_missile() if pos: # drop each by gravity_power new_pos = (pos[0], pos[1] + gravity_power) invader.set_missile(False) if new_pos[1] < self.MAP_HEIGHT: if self.map[new_pos] == self.BULLET: self.map[new_pos] = self.EMPTY elif self.map[new_pos] == self.PLAYER_L or self.map[new_pos] == self.PLAYER_C or self.map[ new_pos] == self.PLAYER_R: self.life_lost() elif self.is_barrier(self.map[new_pos]): self.map[new_pos] = self.decrement_barrier(self.map[new_pos]) else: invader.set_missile(new_pos) self.map[new_pos] = self.MISSILE else: # it fell off the map self.missiles_left -= 1 def is_running(self): return self.running def get_vars_for_bot(self): bot_vars = {} # player x location (center) # mothership x location(center) bonus_ship_x = -1 if self.mothership_exists: bonus_ship_x = self.map.get_all_pos(self.MOTHERSHIP_C).pop()[0] # for these, we send an array where 0 = y and 1 = the character (or self.EMPTY if nothing) # we send -1 if the location is out of bounds (for the left-1 and right+1) player_x = self.player_pos[0] player_left_minus_one = self.EMPTY for h in range(self.MAP_HEIGHT - 2, 0, -1): if player_x - 2 < 0: player_left_minus_one = self.OUT_OF_BOUNDS elif not self.map[(player_x - 2, h)] == self.EMPTY: player_left_minus_one = self.map[(player_x - 2, h)] break player_left = self.EMPTY for h in range(self.MAP_HEIGHT - 2, 0, -1): if not self.map[(player_x - 1, h)] == self.EMPTY: player_left = self.map[(player_x - 1, h)] break player_center = self.EMPTY for h in range(self.MAP_HEIGHT - 2, 0, -1): if not self.map[(player_x, h)] == self.EMPTY: player_center = self.map[(player_x, h)] break player_right = self.EMPTY for h in range(self.MAP_HEIGHT - 2, 0, -1): if not self.map[(player_x + 1, h)] == self.EMPTY: player_right = self.map[(player_x + 1, h)] break player_right_plus_one = self.EMPTY for h in range(self.MAP_HEIGHT - 2, 0, -1): if player_x + 2 >= self.MAP_WIDTH: player_right_plus_one = self.OUT_OF_BOUNDS elif not self.map[(player_x + 2, h)] == self.EMPTY: player_right_plus_one = self.map[(player_x + 2, h)] break bot_vars["bonus_ship_x"] = bonus_ship_x bot_vars["player_x"] = player_x bot_vars["player_left_minus_one"] = ord(player_left_minus_one) bot_vars["player_left"] = ord(player_left) bot_vars["player_center"] = ord(player_center) bot_vars["player_right"] = ord(player_right) bot_vars["player_right_plus_one"] = ord(player_right_plus_one) bot_vars["map_array"] = self.get_map_array_tuple() # TODO: pass in the map to the bot return bot_vars def get_map_array_tuple(self): map_arr = [] for w in range(0, self.MAP_WIDTH): w_arr = [] for h in range(0, self.MAP_HEIGHT): w_arr.append(ord(self.map.p_to_char[(w, h)])) map_arr.append(tuple(w_arr)) return tuple(map_arr) @staticmethod def default_prog_for_bot(language): if language == GameLanguage.LITTLEPY: return open(os.path.join(os.path.dirname(__file__), "resources/sample_bot.lp"), "r").read() @staticmethod def get_intro(): return open(os.path.join(os.path.dirname(__file__), "resources/intro.md"), "r").read() # return "Welcome to Space Invaders" def get_score(self): return self.score def draw_screen(self, frame_buffer): # if not self.running: # self.msg_panel += [""+str(self.drops_eaten)+" drops. Good job!"] # Update Status self.status_panel["Invaders"] = len(self.invaders) self.status_panel["Lives"] = str(self.lives) self.status_panel["Move"] = str(self.turns) + " of " + str(self.MAX_TURNS) self.status_panel["Score"] = str(self.score) for panel in self.panels: panel.redraw(frame_buffer) @staticmethod def get_move_consts(): return ConstMapping({"west": ord("a"), "east": ord("d"), "fire": ord("w"), "stay": ord("s"), "MOTHERSHIP_L": ord(SpaceInvaders.MOTHERSHIP_L), "MOTHERSHIP_C": ord(SpaceInvaders.MOTHERSHIP_C), "MOTHERSHIP_R": ord(SpaceInvaders.MOTHERSHIP_R), "INVADER_0": ord(SpaceInvaders.INVADER0), "INVADER_1": ord(SpaceInvaders.INVADER1), "INVADER_2": ord(SpaceInvaders.INVADER2), "BARRIER_1": ord(SpaceInvaders.BARRIER_1), "BARRIER_2": ord(SpaceInvaders.BARRIER_2), "BARRIER_3": ord(SpaceInvaders.BARRIER_3), "BARRIER_4": ord(SpaceInvaders.BARRIER_4), "MISSILE": ord(SpaceInvaders.MISSILE), "BULLET": ord(SpaceInvaders.BULLET), "PLAYER_L": ord(SpaceInvaders.PLAYER_L), "PLAYER_C": ord(SpaceInvaders.PLAYER_C), "PLAYER_R": ord(SpaceInvaders.PLAYER_R), "EMPTY": ord(' '), "OUT_OF_BOUNDS": ord(SpaceInvaders.OUT_OF_BOUNDS), "MAP_HEIGHT": SpaceInvaders.MAP_HEIGHT, "MAP_WIDTH": SpaceInvaders.MAP_WIDTH, })
class PacBot(GridGame): MAP_WIDTH = 30 MAP_HEIGHT = 33 SCREEN_WIDTH = MAP_WIDTH SCREEN_HEIGHT = MAP_HEIGHT + 6 MSG_START = 20 MAX_MSG_LEN = SCREEN_WIDTH - MSG_START - 1 CHAR_WIDTH = 16 CHAR_HEIGHT = 16 GAME_TITLE = "Pac-Bot" CHAR_SET = "terminal16x16_gs_ro.png" LIVES_START = 4 SENSE_DIST = 20 MAX_TURNS = 5000 # starting positions PLAYER_START_X = 14 PLAYER_START_Y = 24 BLINKY_START_X = 14 BLINKY_START_Y = 12 # start in house for real PINKY_START_X = 14 PINKY_START_Y = 15 INKY_START_X = 12 INKY_START_Y = 15 CLYDE_START_X = 16 CLYDE_START_Y = 15 # Ghost scatter mode targets BLINKY_TARGET_X = 26 BLINKY_TARGET_Y = -2 PINKY_TARGET_X = 3 PINKY_TARGET_Y = -2 INKY_TARGET_X = 28 INKY_TARGET_Y = 34 CLYDE_TARGET_X = 1 CLYDE_TARGET_Y = 34 # # start in hallway for testing # PINKY_START_X = 15 # PINKY_START_Y = 12 # INKY_START_X = 16 # INKY_START_Y = 12 # CLYDE_START_X = 17 # CLYDE_START_Y = 12 PLAYER = '@' EMPTY = '\0' # APPLE = 'O' FULL = chr(224) DOT = chr(225) POWER = chr(226) DOOR = chr(227) WALL = chr(228) HYPHEN = chr(229) J = chr(230) L = chr(231) F = chr(232) SEVEN = chr(233) # ghosts BLINKY = chr(234) PINKY = chr(235) INKY = chr(236) CLYDE = chr(237) EDIBLE_BLINKY = chr(218) EDIBLE_PINKY = chr(219) EDIBLE_INKY = chr(220) EDIBLE_CLYDE = chr(221) EYES = chr(238) EDIBLE = chr(239) # fruit CHERRY = chr(240) STRAWBERRY = chr(241) ORANGE = chr(242) BELL = chr(243) APPLE = chr(244) MELON = chr(245) GALAXIAN = chr(246) KEY = chr(247) STAR = chr(43) FRUITS = [CHERRY, STRAWBERRY, ORANGE, ORANGE, APPLE, APPLE, MELON, MELON, GALAXIAN, GALAXIAN, BELL, BELL, KEY] FRUIT_POINTS = {CHERRY: 100, STRAWBERRY: 300, ORANGE: 500, APPLE: 700, MELON: 1000, GALAXIAN: 2000, BELL: 3000, KEY: 5000} # points DOT_POINTS = 10 POWER_POINTS = 50 GHOST_BASE_POINTS = 200 ENERGIZED_TURNS = 50 TOTAL_PELLETS = 253 # classes of objects for sensors GHOST = chr(254) FRUIT = chr(255) def __init__(self, random): self.sensor_coords = [] # variables for adjustable sensors from LP self.random = random self.running = True self.colliding = False self.energized = 0 # positive means energized for that many turns self.ghost_multiplier = 1 self.lives = 3 self.player_pos = [self.PLAYER_START_X, self.PLAYER_START_Y] self.score = 0 self.extra_life = False self.objects = [] self.turns = 0 self.level = 0 self.score_panel = StatusPanel(0, 0, self.MAP_WIDTH, 3) self.panels = [self.score_panel] self.pellets_eaten = 0 self.fruit_visible = -1 # array of ghost objects # ghosts use the functions in the PacBot object self.ghosts = {} self.ghosts['blinky'] = Ghost("blinky", self.BLINKY, self.EDIBLE_BLINKY, self.BLINKY_START_X, self.BLINKY_START_Y) self.ghosts['pinky'] = Ghost("pinky", self.PINKY, self.EDIBLE_PINKY, self.PINKY_START_X, self.PINKY_START_Y) self.ghosts['inky'] = Ghost("inky", self.INKY, self.EDIBLE_INKY, self.INKY_START_X, self.INKY_START_Y) self.ghosts['clyde'] = Ghost("clyde", self.CLYDE, self.EDIBLE_CLYDE, self.CLYDE_START_X, self.CLYDE_START_Y) # self.__create_map() def get_fruit_for_level(self): if self.level > len(self.FRUITS): return self.KEY else: return self.FRUITS[self.level] def print_ready(self): x = 12 y = 18 for char in "READY!": self.map[(x, y)] = char x += 1 def print_game_over(self): x = 10 y = 18 for char in "GAME OVER": self.map[(x, y)] = char x += 1 def erase_ready(self): x = 12 y = 18 for char in "READY!": self.map[(x, y)] = self.EMPTY x += 1 def redraw_lives(self): # erase status line for x in range(self.MAP_WIDTH - 1): self.map[(1 + x, 33)] = self.EMPTY # redraw lives for x in range(self.LIVES_START): if self.lives > x: self.map[(1 + x, 33)] = self.PLAYER # redraw the lower-right bar of fruits x = self.MAP_WIDTH - 2 - self.level + 1 for level in range(self.level + 1): self.map[(x, 33)] = self.FRUITS[level] x = x + 1 def reset_positions(self): self.map[(self.player_pos[0], self.player_pos[1])] = self.EMPTY self.player_pos[0] = self.PLAYER_START_X self.player_pos[1] = self.PLAYER_START_Y self.map[(self.player_pos[0], self.player_pos[1])] = self.PLAYER self.energized = 0 for g in self.ghosts: ghost = self.ghosts[g] ghost.vulnerable = 0 # remove ghosts from their current locations on the map # make sure to drop anything they're "carrying" if ghost.saved_object: self.map[(ghost.pos[0], ghost.pos[1])] = ghost.saved_object ghost.saved_object = None else: self.map[(ghost.pos[0], ghost.pos[1])] = self.EMPTY ghost.alive = True ghost.mode = "scatter" if ghost.name == "blinky": ghost.pos[0] = ghost.start_x ghost.pos[1] = ghost.start_y ghost.in_house = True elif ghost.name == "pinky": ghost.pos[0] = ghost.start_x ghost.pos[1] = ghost.start_y ghost.in_house = True elif ghost.name == "inky": ghost.pos[0] = ghost.start_x ghost.pos[1] = ghost.start_y ghost.in_house = True elif ghost.name == "clyde": ghost.pos[0] = ghost.start_x ghost.pos[1] = ghost.start_y ghost.in_house = True self.redraw_ghosts() def init_board(self): self.map = MapPanel(0, 3, self.MAP_WIDTH, self.MAP_HEIGHT + 1, self.EMPTY) # border=PanelBorder.create(bottom="-")) self.panels += [self.map] mapmap = {'|': self.WALL, '-': self.HYPHEN, 'L': self.L, '7': self.SEVEN, 'J': self.J, 'F': self.F, '.': self.DOT, 'O': self.POWER, '=': self.DOOR, '#': self.FULL, ' ': self.EMPTY, 'B': self.BLINKY, 'P': self.PINKY, 'I': self.INKY, 'C': self.CLYDE} # reset positions first so ghosts not in maze when filled self.reset_positions() # make fruit counter negative to eliminate the case when you # switch levels when a fruit is visible self.fruit_visible = -1 # open map file x = 0 y = 0 with open("map.txt") as f: for line in f: x = 0 for char in line: if char != '\n': self.map[(x, y)] = mapmap[char] x += 1 y += 1 self.map[(self.player_pos[0], self.player_pos[1])] = self.PLAYER self.redraw_ghosts() self.redraw_lives() self.print_ready() def create_new_player(self, prog): self.player = DefaultGridPlayer(prog, self.get_move_consts()) return self.player def start_game(self): pass def do_turn(self): self.handle_key(self.player.move) self.update_vars_for_player() def place_objects(self, char, count, replace=False): placed_objects = 0 while placed_objects < count: x = self.random.randint(0, self.MAP_WIDTH - 1) y = self.random.randint(0, self.MAP_HEIGHT - 1) if self.map[(x, y)] == self.EMPTY: self.map[(x, y)] = char placed_objects += 1 elif replace: # we can replace objects that exist self.map[(x, y)] = char placed_objects += 1 def is_ghost(self, item): if item == self.BLINKY or item == self.PINKY or item == self.INKY or item == self.CLYDE or item == self.EDIBLE_BLINKY or item == self.EDIBLE_PINKY or item == self.EDIBLE_INKY or item == self.EDIBLE_CLYDE: return True else: return False def is_fruit(self, item): if item in self.FRUITS: return True else: return False def is_blocked(self, item): # returns true if the cell in the map is obstructed if item == self.DOT or item == self.POWER or item == self.EMPTY or self.is_ghost(item) or self.is_fruit(item) or item == self.PLAYER: # since ghosts use is_blocked, players need to be included # in things that do not block motion. Strange but true! return False else: return True def get_ghost_by_xy(self, x, y): for ghost in self.ghosts: this_ghost = self.ghosts[ghost] if this_ghost.pos[0] == x and this_ghost.pos[1] == y: return this_ghost def redraw_ghost(self, ghost): if ghost.alive: if ghost.vulnerable > 10: self.map[(ghost.pos[0], ghost.pos[1])] = ghost.edible_char elif ghost.vulnerable > 0: # blink to "warn" pac-bot of their impending switch if self.turns % 2: self.map[(ghost.pos[0], ghost.pos[1])] = ghost.edible_char else: self.map[(ghost.pos[0], ghost.pos[1])] = ghost.char else: self.map[(ghost.pos[0], ghost.pos[1])] = ghost.char def redraw_ghosts(self): for g in self.ghosts: ghost = self.ghosts[g] self.redraw_ghost(ghost) def check_ghost_collisions(self): # detect collisions -- is there a ghost at player's location? if self.is_ghost(self.map[(self.player_pos[0], self.player_pos[1])]): # figure out which ghost the player has collided with to see # if they are vulnerable ghost = self.get_ghost_by_xy(self.player_pos[0], self.player_pos[1]) if ghost.vulnerable > 0: if (self.player_pos[0] == 14 or self.player_pos[0] == 15) and self.player_pos[1] == 13: # if self.player_pos[1] == 13: self.player_pos[1] = 12 #fixed bug: Eating ghost and door. # if ghost.pos[0] == self.player_pos[0] and ghost.pos[1] == self.player_pos[1]: # ghost.alive = False # self.score += self.ghost_multiplier * self.GHOST_BASE_POINTS # if ghost_saved_object == self.DOT: # self.score += self.DOT_POINTS # self.pellets_eaten += 1 # ghost.saved_object = None # elif ghost.saved_object == self.POWER: # self.score += self.POWER_POINTS # self.pellets_eaten += 1 # ghost.saved_object = None # self.ghost_multiplier += 1 else: ghost.alive = False # ghost has been eaten! self.score += self.ghost_multiplier * self.GHOST_BASE_POINTS # check to see if ghost is "holding" a dot / power if ghost.saved_object == self.DOT: self.score += self.DOT_POINTS self.pellets_eaten += 1 ghost.saved_object = None elif ghost.saved_object == self.POWER: self.score += self.POWER_POINTS self.pellets_eaten += 1 ghost.saved_object = None # increase the score multiplier for ghosts eaten in this # round self.ghost_multiplier += 1 else: # touching ghosts is bad for you self.lives -= 1 self.reset_positions() def move_ghost(self, ghost): # Ghost movement reference: https://gameinternals.com/understanding-pac-man-ghost-behavior # Known deficiencies: # Pinky and Inky do not chase properly # def remove_backwards(dirs): '''Given a list of directions to move remove the option to reverse direction''' if len(dirs) > 1: if ghost.previous_dir == 'w' and 's' in dirs: dirs.remove('s') elif ghost.previous_dir == 's' and 'w' in dirs: dirs.remove('w') elif ghost.previous_dir == 'a' and 'd' in dirs: dirs.remove('d') elif ghost.previous_dir == 'd' and 'a' in dirs: dirs.remove('a') return dirs def dir_to_target(): '''Find the legal moves that are closest to the current target.''' min_dist = self.map_distance(ghost.pos, ghost.target_pos) + 100 dirs = [] for delta in [(1, 0, 'd'), (-1, 0, 'a'), (0, 1, 's'), (0, -1, 'w')]: new_pos = (ghost.pos[0] + delta[0], ghost.pos[1] + delta[1]) item = self.map[new_pos] print(self.is_blocked(item)) # Living ghosts can't go back in the house, but dead ones can if ghost.alive: if (not self.is_blocked(item)) and (not self.is_ghost(item)): distance = self.map_distance(new_pos, ghost.target_pos) if distance < min_dist: min_dist = distance dirs = [delta[2]] elif distance == min_dist: dirs.append(delta[2]) else: if (item == self.DOOR) or (not self.is_blocked and not self.is_ghost(item)): distance = self.map_distance(new_pos, ghost.target_pos) if distance < min_dist: min_dist = distance dirs = [delta[2]] elif distance == min_dist: dirs.append(delta[2]) return dirs def pick_dir(dirs): '''Given a list of legal and equally good moves, pick one according to "up > left > down"''' choice = dirs[0] if len(dirs) > 1: if 'w' in dirs: choice = 'w' elif 'a' in dirs: choice = 'a' elif 's' in dirs: choice = 's' else: choice = 'd' return choice def ghost_replace_item(): '''If ghost saved an object, drop the object before moving the ghost to the new location, otherwise, erase the ghost's current location''' if ghost.saved_object: self.map[(ghost.pos[0], ghost.pos[1])] = ghost.saved_object ghost.saved_object = None else: self.map[(ghost.pos[0], ghost.pos[1])] = self.EMPTY def update_ghost_pos(choice): if choice == 'a': ghost.pos[0] -= 1 elif choice == 'd': ghost.pos[0] += 1 elif choice == 'w': ghost.pos[1] -= 1 elif choice == 's': ghost.pos[1] += 1 ghost.previous_dir = ghost.direction ghost.direction = choice # if the ghost is just north of the door, set it so that # they can't go back into the house if self.map[(ghost.pos[0], ghost.pos[1] + 1)] == self.DOOR: ghost.in_house = False def ghost_pickup_item(): # if there is already something at the ghost's new # location (a fruit, pellet, or energizer), save it by # having the ghost "pick it up" if self.map[(ghost.pos[0], ghost.pos[1])] not in [self.EMPTY, self.PLAYER]: ghost.saved_object = self.map[(ghost.pos[0], ghost.pos[1])] if ghost.alive == False: # If a dead ghost has reached its target it is alive again and in the house if ghost.pos[0] == ghost.start_x and ghost.pos[1] == ghost.start_y: ghost.vulnerable = 0 ghost.alive = True ghost.in_house = True else: # if the ghost is "dead" then it should move back to the house ghost.target_pos = (ghost.start_x, ghost.start_y) dirs = dir_to_target() dirs = remove_backwards(dirs) choice = pick_dir(dirs) update_ghost_pos(choice) if ghost.alive and ghost.mode == "chase": # chase pac-bot if ghost.name == 'blinky': ghost.target_pos = self.player_pos elif ghost.name == 'pinky': # TODO: Pinky should target 4 tiles ahead of player, but need player orientation for that ghost.target_pos = self.player_pos elif ghost.name == 'inky': # TODO: Inky's target is likewise dependent on the orientation of the player ghost.target_pos = self.player_pos elif ghost.name == 'clyde': player_distance = self.map_distance(ghost.pos, self.player_pos) if (player_distance > 8): ghost.target_pos = self.player_pos else: ghost.target_pos = (self.CLYDE_TARGET_X, self.CLYDE_TARGET_Y) dirs = dir_to_target() dirs = remove_backwards(dirs) choice = pick_dir(dirs) ghost_replace_item() update_ghost_pos(choice) elif ghost.alive and ghost.mode == "scatter": # go to individual corners if ghost.name == 'blinky': ghost.target_pos = (self.BLINKY_TARGET_X, self.BLINKY_TARGET_Y) elif ghost.name == 'pinky': ghost.target_pos = (self.PINKY_TARGET_X, self.PINKY_TARGET_Y) elif ghost.name == 'inky': ghost.target_pos = (self.INKY_TARGET_X, self.INKY_TARGET_Y) elif ghost.name == 'clyde': ghost.target_pos = (self.CLYDE_TARGET_X, self.CLYDE_TARGET_Y) print(ghost.name) print(ghost.pos) print("Previous: ", ghost.previous_dir) dirs = dir_to_target() print(dirs) dirs = remove_backwards(dirs) print(dirs) choice = pick_dir(dirs) ghost_replace_item() update_ghost_pos(choice) elif ghost.alive: # mode is frightened # run away from pac-bot dirs = [] # list of directions we can go # determine which directions are open item = self.map[(ghost.pos[0] + 1, ghost.pos[1])] if not self.is_blocked(item) and not self.is_ghost(item): dirs.append("d") item = self.map[(ghost.pos[0] - 1, ghost.pos[1])] if not self.is_blocked(item) and not self.is_ghost(item): dirs.append("a") item = self.map[(ghost.pos[0], ghost.pos[1] + 1)] if not self.is_blocked(item) and not self.is_ghost(item): dirs.append("s") item = self.map[(ghost.pos[0], ghost.pos[1] - 1)] if ghost.in_house and item == self.DOOR or not self.is_blocked(item) and not self.is_ghost(item): dirs.append("w") if len(dirs) > 0: # if current direction is available, keep going that way # (this is historical ghost behavior) if ghost.direction in dirs and ghost.in_house == False: choice = ghost.direction else: direction = self.random.randint(0, len(dirs) - 1) choice = dirs[direction] ghost.direction = choice # if ghost saved an object, drop the object before # moving the ghost to the new location, otherwise, # erase the ghost's current location if ghost.saved_object: self.map[(ghost.pos[0], ghost.pos[1])] = ghost.saved_object ghost.saved_object = None else: self.map[(ghost.pos[0], ghost.pos[1])] = self.EMPTY if choice == 'a': ghost.pos[0] -= 1 elif choice == 'd': ghost.pos[0] += 1 elif choice == 'w': ghost.pos[1] -= 1 elif choice == 's': ghost.pos[1] += 1 # if there is already something at the ghost's new # location (a fruit, pellet, or energizer), save it by # having the ghost "pick it up" if self.map[(ghost.pos[0], ghost.pos[1])] not in [self.EMPTY, self.PLAYER]: ghost.saved_object = self.map[(ghost.pos[0], ghost.pos[1])] # if the ghost is just north of the door, set it so that # they can't go back into the house if self.map[(ghost.pos[0], ghost.pos[1] + 1)] == self.DOOR: ghost.in_house = False if ghost.pos[0] == 0 and ghost.pos[1] == 15: ghost.pos[0] = 28 elif ghost.pos[0] == 29 and ghost.pos[1] == 15: ghost.pos[0] = 1 ghost.vulnerable -= 1 # draw down the time the ghost is vulnerable # draw the ghost into the map spot so that other ghosts # won't share the same spot self.redraw_ghost(ghost) self.check_ghost_collisions() def handle_key(self, key): self.turns += 1 if self.energized > 0: # count down powered turns self.energized -= 1 if self.energized == 0: self.ghost_multiplier = 1 # reset for next time if self.pellets_eaten == 1: self.erase_ready() if DEBUG: print("turn: %d player started at (%d, %d)" % (self.turns, self.player_pos[0], self.player_pos[1])) self.map[(self.player_pos[0], self.player_pos[1])] = self.EMPTY item = self.map[(self.player_pos[0] - 1, self.player_pos[1])] if key == "a" and not self.is_blocked(item): self.player_pos[0] -= 1 item = self.map[(self.player_pos[0] + 1, self.player_pos[1])] if key == "d" and not self.is_blocked(item): self.player_pos[0] += 1 item = self.map[(self.player_pos[0], self.player_pos[1] - 1)] if key == "w" and not self.is_blocked(item): self.player_pos[1] -= 1 item = self.map[(self.player_pos[0], self.player_pos[1] + 1)] if key == "s" and not self.is_blocked(item): self.player_pos[1] += 1 if key == "Q": self.running = False return self.check_ghost_collisions() # add score based on new position if self.map[(self.player_pos[0], self.player_pos[1])] == self.DOT: self.score += self.DOT_POINTS self.pellets_eaten += 1 if self.map[(self.player_pos[0], self.player_pos[1])] == self.POWER: self.score += self.POWER_POINTS self.energized = self.ENERGIZED_TURNS self.pellets_eaten += 1 # make all ghosts vulnerable: for g in self.ghosts: ghost = self.ghosts[g] ghost.vulnerable = self.ENERGIZED_TURNS self.redraw_ghosts() self.pellets_eaten += 1 # make fruit appear if self.pellets_eaten == 70 or self.pellets_eaten == 170: self.map[(14, 18)] = self.get_fruit_for_level() self.fruit_visible = 50 # make fruit disappear if self.fruit_visible > 0: self.fruit_visible = self.fruit_visible - 1 if self.fruit_visible == 0: self.map[(14, 18)] = self.EMPTY # handle eating fruit item = self.map[(self.player_pos[0], self.player_pos[1])] if item in self.FRUITS: self.score += self.FRUIT_POINTS[item] self.fruit_visible = 0 # handle clearing the board if (self.pellets_eaten != 0 and ((self.pellets_eaten % self.TOTAL_PELLETS) == 0)): self.level += 1 self.pellets_eaten = 0 self.init_board() # self.__create_map() # handle extra life if self.score >= 10000 and self.extra_life == False: print ("Gave extra life!") self.extra_life = True self.lives += 1 # handle traveling through tunnels in either direction if self.player_pos[0] == 0 and self.player_pos[1] == 15: self.player_pos[0] = 28 elif self.player_pos[0] == 29 and self.player_pos[1] == 15: self.player_pos[0] = 1 # put the player in the new position self.map[(self.player_pos[0], self.player_pos[1])] = self.PLAYER # draw the life meter at the bottom self.redraw_lives() # move ghosts -- speed based on level if self.level == 0 and self.turns % 3 == 0 or self.level == 1 and self.turns % 2 == 0 or self.level >= 2: for g in self.ghosts: ghost = self.ghosts[g] self.move_ghost(ghost) if DEBUG: print("turn: %d player ended at (%d, %d)" % (self.turns, self.player_pos[0], self.player_pos[1])) # End of the game if self.turns >= self.MAX_TURNS: self.running = False elif self.lives <= 0: self.running = False # vars should be gotten at the end of handle_turn, because vars # affect the *next* turn... def is_running(self): return self.running def read_bot_state(self, state): None def get_map_array_tuple(self): map_arr = [] for w in range(0, self.MAP_WIDTH): w_arr = [] for h in range(0, self.MAP_HEIGHT): # item = self.map[(w,h)] # if self.is_blocked(item): # item = ord(self.WALL) # elif self.is_ghost(item): # item = ord(self.GHOST) # elif self.is_fruit(item): # item = ord(self.FRUIT) # else: # item = ord(item) # # w_arr.append(item) # item = ord(self.map.p_to_char[(w,h)]) # if self.is_blocked(chr(item)): # item = ord(self.WALL) w_arr.append(ord(self.map.p_to_char[(w,h)])) map_arr.append(tuple(w_arr)) return tuple(map_arr) def update_vars_for_player(self): bot_vars = {} # what borders player? dirmod = {'sense_w': [-1, 0], 'sense_e': [1, 0], 'sense_n': [0, -1], 'sense_s': [0, 1]} for sense in dirmod: xmod = dirmod[sense][0] ymod = dirmod[sense][1] obj = self.map[(self.player_pos[0] + xmod, self.player_pos[1] + ymod)] if self.is_blocked(obj): bot_vars[sense] = ord(self.WALL) elif self.is_ghost(obj): bot_vars[sense] = ord(self.GHOST) elif self.is_fruit(obj): bot_vars[sense] = ord(self.FRUIT) elif obj == self.DOT: bot_vars[sense] = ord(self.DOT) elif obj == self.POWER: bot_vars[sense] = ord(self.POWER) else: bot_vars[sense] = ord(self.EMPTY) bot_vars['lives'] = self.lives bot_vars['energized'] = self.energized bot_vars['level'] = self.level bot_vars["dot_x"] = self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.DOT, default=(0, 0))[0] bot_vars["dot_y"] = self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.DOT, default=(0, 0))[1] bot_vars["power_x"] = self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.POWER, default=(0, 0))[0] bot_vars["power_y"] = self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.POWER, default=(0, 0))[1] bot_vars["player_x"] = self.player_pos[0] bot_vars["player_y"] = self.player_pos[1] # get locations for ghosts -- edible or not offset = self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.BLINKY, default=(0, 0)) if offset[0] == 0 and offset[1] == 0: # blinky doesn't exist, so he must be edible_blinky! offset = self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.EDIBLE_BLINKY, default=(0, 0)) bot_vars["blinky_x"] = offset[0] bot_vars["blinky_y"] = offset[1] # rinse and repeat for other ghosts offset = self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.INKY, default=(0, 0)) if offset[0] == 0 and offset[1] == 0: offset = self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.EDIBLE_INKY, default=(0, 0)) bot_vars["inky_x"] = offset[0] bot_vars["inky_y"] = offset[1] offset = self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.PINKY, default=(0, 0)) if offset[0] == 0 and offset[1] == 0: offset = self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.EDIBLE_PINKY, default=(0, 0)) bot_vars["pinky_x"] = offset[0] bot_vars["pinky_y"] = offset[1] offset = self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.CLYDE, default=(0, 0)) if offset[0] == 0 and offset[1] == 0: offset = self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.EDIBLE_CLYDE, default=(0, 0)) bot_vars["clyde_x"] = offset[0] bot_vars["clyde_y"] = offset[1] # map_array is broken so why slow things down? #bot_vars["map_array"] = self.get_map_array_tuple() # find closest fruit -- if one exists cand_x = 0 cand_y = 0 bot_vars['fruit_x'] = 0 bot_vars['fruit_y'] = 0 lastfruit = None for fruit in self.FRUITS: # if we've seen this fruit already, skip it if fruit == lastfruit: continue else: lastfruit = fruit cand_x = self.map.get_x_y_dist_to_foo(tuple(self.player_pos), fruit, default=(0, 0))[0] cand_y = self.map.get_x_y_dist_to_foo(tuple(self.player_pos), fruit, default=(0, 0))[1] if cand_x != 0 or cand_y != 0: bot_vars['fruit_x'] = cand_x bot_vars['fruit_y'] = cand_y break if DEBUG: print("bot_vars:") for key in bot_vars.keys(): if key != "map_array": print("%s : %s" % (key, bot_vars[key])) if DUMP_BOT_VARS: if "map_array" in bot_vars: print("map_array:") for row in bot_vars['map_array']: print(row) self.player.bot_vars = bot_vars @staticmethod def default_prog_for_bot(language): if language == GameLanguage.LITTLEPY: return open("bot.lp", "r").read() @staticmethod def get_intro(): return open("intro.md", "r").read() @staticmethod def get_move_consts(): return ConstMapping({"west": ord("a"), "east": ord("d"), "south": ord("s"), "north": ord("w"), "DOT": ord(PacBot.DOT), "POWER": ord(PacBot.POWER), "WALL": ord(PacBot.WALL), "GHOST": ord(PacBot.GHOST), "INKY": ord(PacBot.INKY), "PINKY": ord(PacBot.PINKY), "BLINKY": ord(PacBot.BLINKY), "CLYDE": ord(PacBot.CLYDE), "EDIBLE": ord(PacBot.EDIBLE), "EDIBLE_INKY": ord(PacBot.EDIBLE_INKY), "EDIBLE_PINKY": ord(PacBot.EDIBLE_PINKY), "EDIBLE_BLINKY": ord(PacBot.EDIBLE_BLINKY), "EDIBLE_CLYDE": ord(PacBot.EDIBLE_CLYDE), "FRUIT": ord(PacBot.FRUIT), "FRUIT": ord(PacBot.CHERRY), "FRUIT": ord(PacBot.STRAWBERRY), "FRUIT": ord(PacBot.ORANGE), "FRUIT": ord(PacBot.BELL), "FRUIT": ord(PacBot.APPLE), "FRUIT": ord(PacBot.MELON), "FRUIT": ord(PacBot.GALAXIAN), "FRUIT": ord(PacBot.KEY), "FRUIT": ord(PacBot.STAR), "EMPTY": ord(PacBot.EMPTY), "PLAYER": ord(PacBot.PLAYER), "map_height": PacBot.MAP_HEIGHT, "map_width": PacBot.MAP_WIDTH, }) @staticmethod def map_distance(pos, target): pos_x, pos_y = pos tar_x, tar_y = target return math.sqrt((tar_x - pos_x) ** 2 + (tar_y - pos_y) ** 2) def get_score(self): return self.score def draw_screen(self, frame_buffer): if not self.running: self.print_game_over() # Update Status self.score_panel["Score"] = self.score for panel in self.panels: panel.redraw(frame_buffer)
class Ski(GridGame): MAP_WIDTH = 60 MAP_HEIGHT = 30 SCREEN_WIDTH = 60 SCREEN_HEIGHT = MAP_HEIGHT + 6 MSG_START = 20 MAX_MSG_LEN = SCREEN_WIDTH - MSG_START - 1 CHAR_WIDTH = 16 CHAR_HEIGHT = 16 GAME_TITLE = "Ski" CHAR_SET = "terminal16x16_gs_ro.png" NUM_OF_SENSORS = 8 SENSE_DIST = 20 LEVELUP_RESPONSES = [ "The forest seems to be getting more dense!", "Are there more trees here or what?", "Watch out!", "Better pay attention!" ] ROBOT_CRASH_RESPONSES = [ "OOF!", "OWWWIE!", "THAT'S GONNA LEAVE A MARK!", "BONK!" ] ROBOT_HEART_RESPONSES = [ "Wow, I feel a lot better!", "Shazam!", "That's the ticket!", "Yes!!!" ] ROBOT_COIN_RESPONSES = [ "Cha-ching!", "Badabing!", "Bling! Bling!", "Wahoo!" ] ROBOT_FLYING_RESPONSES = [ "I'm free as a bird now!", "It's a bird, it's a plane...", "Cowabunga!" ] NUM_OF_ROCKS_START = 30 NUM_OF_TREES_START = 30 NUM_OF_SPIKES_START = 30 NUM_OF_SNOWMAN_START = 30 NUM_OF_COINS_START = 1 NUM_OF_HEARTS_START = 1 NUM_OF_JUMPS_START = 1 MAX_TURNS = 500 MAX_FLYING = 10 FLYING_POINTS = 5 COIN_POINTS = 25 HOUSE_ODDS = 500 # e.g., 1/500 PLAYER = '@' EMPTY = '\0' HEART = chr(3) COIN = chr(4) ROCK = chr(15) SPIKE = chr(16) SNOWMAN = chr(17) TRACKS = chr(29) TREE = chr(30) JUMP = chr(31) DEAD = chr(1) FLY = chr(2) CRASH = chr(8) HOUSE = chr(9) def __init__(self, random): self.random = random self.running = True self.colliding = False self.saved_object = None # stores a map item we're "on top of" self.last_move = 'w' # need this to restore objects self.flying = 0 # set to some value and decrement (0 == on ground) self.hp = 1 self.player_pos = [int(self.MAP_WIDTH / 2), self.MAP_HEIGHT - 4] self.score = 0 self.objects = [] self.turns = 0 self.level = 1 self.msg_panel = MessagePanel(self.MSG_START, self.MAP_HEIGHT + 1, self.SCREEN_WIDTH - self.MSG_START, 5) self.status_panel = StatusPanel(0, self.MAP_HEIGHT + 1, self.MSG_START, 5) self.panels = [self.msg_panel, self.status_panel] self.msg_panel.add("Velkommen to Robot Backcountry Skiing!") self.msg_panel.add("Move left and right! Don't crash!") self.__create_map() def __create_map(self): self.map = MapPanel(0, 0, self.MAP_WIDTH, self.MAP_HEIGHT + 1, self.EMPTY, border=PanelBorder.create(bottom="-")) self.panels += [self.map] self.place_objects(self.TREE, self.NUM_OF_ROCKS_START) self.place_objects(self.ROCK, self.NUM_OF_TREES_START) self.place_objects(self.SNOWMAN, self.NUM_OF_SNOWMAN_START) self.place_objects(self.COIN, self.NUM_OF_COINS_START) self.place_objects(self.HEART, self.NUM_OF_HEARTS_START) self.place_objects(self.JUMP, self.NUM_OF_JUMPS_START) # make a clearing for the player for y in range(8): for x in range(self.MAP_WIDTH): self.map[(x, self.MAP_HEIGHT - 1 - y)] = self.EMPTY # place decorative trees self.map[(self.player_pos[0] + 5, self.player_pos[1])] = self.TREE self.map[(self.player_pos[0] - 5, self.player_pos[1])] = self.TREE # place initial hearts self.map[(self.player_pos[0], self.player_pos[1] - 2)] = self.HEART self.map[(self.player_pos[0], self.player_pos[1] - 3)] = self.HEART def init_board(self): pass def create_new_player(self, prog): self.player = SkiPlayer(prog, self.get_move_consts(), self.NUM_OF_SENSORS) # place player self.map[(self.player_pos[0], self.player_pos[1])] = self.PLAYER self.update_vars_for_player() return self.player def start_game(self): pass def place_objects(self, char, count, replace=False): placed_objects = 0 while placed_objects < count: x = self.random.randint(0, self.MAP_WIDTH - 1) y = self.random.randint(0, self.MAP_HEIGHT - 1) if self.map[(x, y)] == self.EMPTY: self.map[(x, y)] = char placed_objects += 1 elif replace: # we can replace objects that exist self.map[(x, y)] = char placed_objects += 1 def make_new_row(self): for x in range(self.MAP_WIDTH): here = self.random.randint(0, self.MAX_TURNS) if here <= self.turns: which = self.random.randint(0, 2) if which == 0: self.map[(x, 0)] = self.ROCK elif which == 1: self.map[(x, 0)] = self.TREE elif which == 2: self.map[(x, 0)] = self.SNOWMAN if self.random.randint(0, 100) > 33: self.map[(self.random.randint(0, self.MAP_WIDTH - 1), 0)] = self.HEART if self.random.randint(0, 100) > 33: self.map[(self.random.randint(0, self.MAP_WIDTH - 1), 0)] = self.COIN if self.random.randint(0, 100) > 33: self.map[(self.random.randint(0, self.MAP_WIDTH - 1), 0)] = self.JUMP if self.random.randint(0, self.HOUSE_ODDS) == 1: self.map[(self.random.randint(0, self.MAP_WIDTH - 1), 0)] = self.HOUSE def save_object(self, obj): self.saved_object = obj def restore_object_tracks(self): # restore an object you went over or make tracks # where should the object be restored? y = 1 # it's always going to be behind us x = 0 # we will set the x value accordingly if self.last_move == 'a': x = 1 elif self.last_move == 'd': x = -1 elif self.last_move == 'w': x = 0 if self.saved_object: if self.last_move == 't': # if the player previously teleported when on an # obstacle, just destroy the obstacle. We can't put it # back where it was because we don't know (x, y) for the # player due to map shifting, and we can't draw it under # us or we will collide with it twice! self.msg_panel.add("Teleporting destroyed the object!") self.saved_object = None else: # if the player didn't teleport, put object back self.map[(self.player_pos[0] + x, self.player_pos[1] + y)] = self.saved_object self.saved_object = None else: if self.flying < 1: if self.map[(self.player_pos[0] + x, self.player_pos[1] + y)] == self.EMPTY: self.map[(self.player_pos[0] + x, self.player_pos[1] + y)] = self.TRACKS def shift_map(self): # shift all rows down dx = int(self.MAP_WIDTH / 2) - self.player_pos[0] self.map.shift_all((dx, 1), wrap_x=True) self.player_pos[0] += dx self.make_new_row() self.restore_object_tracks() def do_turn(self): self.handle_key(self.player.move) self.update_vars_for_player() def handle_key(self, key): if (DEBUG): print("Move {}".format(self.player.move)) print("Key {}".format(key)) self.turns += 1 if self.flying > 0: self.score += self.FLYING_POINTS self.flying -= 1 self.msg_panel.add("In flight for " + str(self.flying) + " turns...") if self.flying == 0: self.msg_panel.add("Back on the ground!") else: self.score += 1 if self.turns % 30 == 0: self.level += 1 if (DEBUG): print("player_pos[0] {} player_pos[1] {}".format( self.player_pos[0], self.player_pos[1])) self.map[(self.player_pos[0], self.player_pos[1])] = self.EMPTY if key == "a": self.player_pos[0] -= 1 if key == "d": self.player_pos[0] += 1 if key == "w": pass if key == "t": # horizontal-only teleporting code self.msg_panel.add("TELEPORT! (-1 HP)") self.hp -= 1 self.player_pos[0] = self.random.randint(0, self.MAP_WIDTH - 1) if key == "Q": self.running = False return if (DEBUG): print("Key was: %s turn #%d" % (key, self.turns)) self.last_move = key # save last move for saved_object restoration # shift the map self.shift_map() self.colliding = False # reset colliding variable # check for various types of collisions (good and bad) if self.map[(self.player_pos[0], self.player_pos[1])] == self.ROCK: self.save_object(self.ROCK) if self.flying == 0: self.colliding = True self.hp -= 10 self.msg_panel.add( self.random.choice( list( set(self.ROBOT_CRASH_RESPONSES) - set(self.msg_panel.get_current_messages())))) elif self.map[(self.player_pos[0], self.player_pos[1])] == self.TREE: self.save_object(self.TREE) if self.flying == 0: self.colliding = True self.hp -= 2 self.msg_panel.add( self.random.choice( list( set(self.ROBOT_CRASH_RESPONSES) - set(self.msg_panel.get_current_messages())))) elif self.map[(self.player_pos[0], self.player_pos[1])] == self.SNOWMAN: if self.flying == 0: self.colliding = True self.hp -= 1 self.msg_panel.add( self.random.choice( list( set(self.ROBOT_CRASH_RESPONSES) - set(self.msg_panel.get_current_messages())))) else: self.save_object( self.SNOWMAN) # flying over snowmen is nondestructive elif self.map[(self.player_pos[0], self.player_pos[1])] == self.HEART: if self.flying == 0: if self.hp < 10: self.hp += 1 self.msg_panel.add( self.random.choice( list( set(self.ROBOT_HEART_RESPONSES) - set(self.msg_panel.get_current_messages())))) else: self.msg_panel.add("Your HP is already full!") else: self.save_object(self.HEART) elif self.map[(self.player_pos[0], self.player_pos[1])] == self.HOUSE: if self.flying == 0: self.hp = 10 self.msg_panel.add("This cabin was very refreshing!") else: self.save_object(self.HOUSE) elif self.map[(self.player_pos[0], self.player_pos[1])] == self.COIN: if self.flying == 0: self.score += self.COIN_POINTS self.msg_panel.add( self.random.choice( list( set(self.ROBOT_COIN_RESPONSES) - set(self.msg_panel.get_current_messages())))) else: self.save_object(self.COIN) elif self.map[(self.player_pos[0], self.player_pos[1])] == self.JUMP: if self.flying == 0: self.save_object(self.JUMP) self.flying += self.random.randint(2, self.MAX_FLYING) self.msg_panel.add( self.random.choice( list( set(self.ROBOT_FLYING_RESPONSES) - set(self.msg_panel.get_current_messages())))) else: self.save_object(self.JUMP) # draw player if self.flying < 1: if self.colliding: self.map[(self.player_pos[0], self.player_pos[1])] = self.CRASH else: self.map[(self.player_pos[0], self.player_pos[1])] = self.PLAYER else: self.map[(self.player_pos[0], self.player_pos[1])] = self.FLY # Check for game-ending state: if self.turns >= self.MAX_TURNS: self.running = False elif self.hp <= 0: self.running = False self.map[(self.player_pos[0], self.player_pos[1])] = self.DEAD def is_running(self): return self.running def get_map_array_tuple(self): map_arr = [] for w in range(0, self.MAP_WIDTH): w_arr = [] for h in range(0, self.MAP_HEIGHT): w_arr.append(ord(self.map.p_to_char[(w, h)])) map_arr.append(tuple(w_arr)) return tuple(map_arr) def update_vars_for_player(self): bot_vars = { "jump_x": self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.JUMP, default=(0, 0))[0], "jump_y": self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.JUMP, default=(0, 0))[1], "heart_x": self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.HEART, default=(0, 0))[0], "heart_y": self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.HEART, default=(0, 0))[1], "coin_x": self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.COIN, default=(0, 0))[0], "coin_y": self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.COIN, default=(0, 0))[1], "house_x": self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.HOUSE, default=(0, 0))[0], "house_y": self.map.get_x_y_dist_to_foo(tuple(self.player_pos), self.HOUSE, default=(0, 0))[1], "map_width": self.MAP_WIDTH, "map_height": self.MAP_HEIGHT, "hp": 0, "flying": 0, "s1": 0, "s2": 0, "s3": 0, "s4": 0, "s5": 0, "s6": 0, "s7": 0 } # go through self.sensor_coords and retrieve the map item at the # position relative to the player for i in range(self.NUM_OF_SENSORS): if (i < len(self.player.sensor_coords)): sensor = "s" + str(i + 1) x_offset = self.player.sensor_coords[i][0] y_offset = self.player.sensor_coords[i][1] bot_vars[sensor] = ord( self.map[(self.player_pos[0] + int(x_offset), self.player_pos[1] + int(y_offset))]) if (DEBUG): print( "sensor: %s - i: %d - x_off: %s y_off: %s content: %s" % (sensor, i, x_offset, y_offset, bot_vars[sensor])) if bot_vars[sensor] == 64: bot_vars[sensor] = 0 bot_vars['hp'] = self.hp bot_vars['flying'] = self.flying bot_vars['map_array'] = self.get_map_array_tuple() if DEBUG: print("Printing bot_vars:") for key in bot_vars.keys(): if key != "map_array": print("%s ==> %s" % (key, bot_vars[key])) else: # sort of pretty print the map for rownum in range(len(bot_vars['map_array'])): print("%02d: " % (rownum), end='') for val in bot_vars['map_array'][rownum]: print("%02d " % (val), end='') print("") print( "Note: map printed sideways for terminal readability (bottom on right)" ) print( "Note: robot already dead in last frame -- 2nd to last more useful!" ) print("") self.player.bot_vars = bot_vars @staticmethod def default_prog_for_bot(language): if language == GameLanguage.LITTLEPY: return open("bot.lp", "r").read() @staticmethod def get_intro(): return open("intro.md", "r").read() @staticmethod def get_move_consts(): return ConstMapping({ "teleport": ord("t"), "west": ord("a"), "east": ord("d"), "heart": ord(Ski.HEART), "coin": ord(Ski.COIN), "rock": ord(Ski.ROCK), "spikes": ord(Ski.SPIKE), "snowman": ord(Ski.SNOWMAN), "tracks": ord(Ski.TRACKS), "tree": ord(Ski.TREE), "jump": ord(Ski.JUMP), "house": ord(Ski.HOUSE), }) @staticmethod def get_move_names(): names = Game.get_move_names() names.update({ord("t"): "teleport"}) names.update({ord("d"): "east"}) names.update({ord("a"): "west"}) names.update({ord(Ski.HEART): "heart"}) names.update({ord(Ski.COIN): "coin"}) names.update({ord(Ski.ROCK): "rock"}) names.update({ord(Ski.SPIKE): "spikes"}) names.update({ord(Ski.SNOWMAN): "snowman"}) names.update({ord(Ski.TRACKS): "tracks"}) names.update({ord(Ski.TREE): "tree"}) names.update({ord(Ski.JUMP): "jump"}) names.update({ord(Ski.HOUSE): "house"}) return names def get_score(self): return self.score def draw_screen(self, frame_buffer): # End of the game if self.turns >= self.MAX_TURNS: self.msg_panel.add("You are out of moves.") elif self.hp <= 0: self.msg_panel.add("You sustained too much damage!") if not self.running: self.msg_panel.add("GAME 0VER: Score:" + str(self.score)) # Update Status self.status_panel["Score"] = self.score self.status_panel["Move"] = str(self.turns) + " of " + str( self.MAX_TURNS) self.status_panel["HP"] = self.HEART * self.hp for panel in self.panels: panel.redraw(frame_buffer)
class FlappyBird(GridGame): MAP_WIDTH = 40 MAP_HEIGHT = 35 SCREEN_WIDTH = 40 SCREEN_HEIGHT = MAP_HEIGHT + 6 MSG_START = 20 MAX_MSG_LEN = SCREEN_WIDTH - MSG_START - 1 CHAR_WIDTH = 16 CHAR_HEIGHT = 16 GAME_TITLE = "Flappy Robot" EMPTY = ' ' PLAYER = '@' PIPE = chr(11 * 16 + 3) #'|' PIPE_TOP_LEFT = chr(13 * 16 + 4) PIPE_TOP_RIGHT = chr(11 * 16 + 14) PIPE_BUTTOM_LEFT = chr(13 * 16 + 5) PIPE_BUTTOM_RIGHT = chr(11 * 16 + 8) MAP_BOTTOM = chr(12 * 16 + 4) PLAYER_X = 2 MAX_DOWNWARD_SPEED = 4 CHAR_SET = "resources/terminal16x16_gs_ro.png" PASSING_SOUNDS = ['Swish!', 'Whoosh!', 'Swoosh!', 'Chirp', 'Tweet'] def __init__(self, random): self.random = random self.running = True self.player_y = self.MAP_HEIGHT // 2 self.player_v = 0 self.x = 0 self.pipes_passed = 0 self.pipes = [] self.msg_panel = MessagePanel(self.MSG_START, self.MAP_HEIGHT + 1, self.SCREEN_WIDTH - self.MSG_START, 5, padding=PanelPadding.create(top=1, right=1, bottom=1)) self.status_panel = StatusPanel(0, self.MAP_HEIGHT + 1, self.MSG_START, 5, padding=PanelPadding.create(top=1, left=1, bottom=1)) self.panels = [self.status_panel, self.msg_panel] self.msg_panel.add("Welcome to") self.msg_panel.add(" " + self.GAME_TITLE + "!!!") self.msg_panel.add("Don't hit the pipes") def init_board(self): self.map = MapPanel(0, 0, self.MAP_WIDTH, self.MAP_HEIGHT, self.EMPTY, border=PanelBorder.create(bottom=self.MAP_BOTTOM)) self.panels.append(self.map) def create_new_player(self, prog): self.player = DefaultGridPlayer(prog, self.get_move_consts()) self.map[(self.PLAYER_X, self.player_y)] = self.PLAYER self.update_vars_for_player() return self.player def start_game(self): pass def do_turn(self): # print("doing turn") self.handle_key(self.player.move) self.move_pipes() self.draw_pipe_if_needed() self.update_vars_for_player() def handle_key(self, key): if key == 'Q': self.running = False return self.map[(self.PLAYER_X, self.player_y)] = self.EMPTY if key == 'w' or key == ' ': self.player_y -= 2 self.player_v = -1 else: self.player_y += self.player_v self.player_v += 1 if self.player_v < self.MAX_DOWNWARD_SPEED else 0 self.x += 1 self.map.shift_all((-1, 0)) new_pos = (self.PLAYER_X, self.player_y) if not self.map.in_bounds(new_pos) or self.map[new_pos] == self.PIPE: self.running = False # self.msg_panel.add("You flu into a pipe!") else: self.map[(self.PLAYER_X, self.player_y)] = self.PLAYER def move_pipes(self): num_pipes_before = len(self.pipes) self.pipes = [pipe.shift() for pipe in self.pipes if pipe.x > 1] num_pipes_after = len(self.pipes) self.pipes_passed += num_pipes_before - num_pipes_after def draw_pipe_if_needed(self): if not self.pipes: self.pipes.append(self.create_pipe()) self.draw_pipe(self.pipes[-1]) else: last_pipe = self.pipes[-1] dist_from_last_pipe = self.MAP_WIDTH - last_pipe.x print(dist_from_last_pipe) if dist_from_last_pipe > (20 - (self.x // 100)): self.pipes.append(self.create_pipe()) self.draw_pipe(self.pipes[-1]) return # print(f"space: {(20 - (self.x//25))}") # if self.x % (20 - (self.x//100)) == 0: # position = self.random.choice(range(10, self.MAP_HEIGHT-10)) # for i in range(self.MAP_HEIGHT): # if abs(position-i) > 3: # self.map[(self.MAP_WIDTH - 1, i)] = self.PIPE # self.map[(self.MAP_WIDTH - 2, i)] = self.PIPE def draw_pipe(self, pipe): print(f'Drawing {pipe}') for i in range(self.MAP_HEIGHT): if abs(pipe.y - i) > 3: if pipe.y - i == 4: self.map[(pipe.x, i)] = self.PIPE_TOP_LEFT self.map[(pipe.x + 1, i)] = self.PIPE_TOP_RIGHT elif pipe.y - i == -4: self.map[(pipe.x, i)] = self.PIPE_BUTTOM_LEFT self.map[(pipe.x + 1, i)] = self.PIPE_BUTTOM_RIGHT else: self.map[(pipe.x, i)] = self.PIPE self.map[(pipe.x + 1, i)] = self.PIPE def create_pipe(self): print('Creating pipe') y = self.random.choice(range(10, self.MAP_HEIGHT - 10)) return Pipe(self.MAP_WIDTH - 2, y) def update_vars_for_player(self): bot_vars = { "y": self.player_y, "next_pipe_y": self.pipes[0].y if self.pipes else -1, "dist_to_next_pipe": self.pipes[0].x - self.PLAYER_X if self.pipes else 9999, } self.player.bot_vars = bot_vars def is_running(self): return self.running def draw_screen(self, frame_buffer): if not self.running: self.msg_panel.add("Game Over.") if self.pipes_passed == 0: self.msg_panel.add("Better luck") self.msg_panel.add(" next time :(") else: self.msg_panel.add(" Good job!") self.status_panel["Meters Flown"] = self.x self.status_panel["Pipes Passed"] = self.pipes_passed self.status_panel["Score"] = self.get_score() for panel in self.panels: panel.redraw(frame_buffer) def get_score(self): return self.x + (self.pipes_passed * 50) @staticmethod def get_intro(): return open("resources/intro.md", "r").read() @staticmethod def default_prog_for_bot(language): if language == GameLanguage.LITTLEPY: return open("resources/flappy_bot.lp", "r").read() @staticmethod def get_move_consts(): return ConstMapping({ "flap": ord(" "), "up": ord("w"), "down": ord("s"), "glide": ord("d") })