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") })