def any_ships_alive(self, locations_grid: GameGrid, opponents_guesses_grid: GameGrid): for row_idx in range(self.num_rows): for col_idx in range(self.num_cols): if (locations_grid.read_grid(row_idx, col_idx) != SHIP_LOCATION_EMPTY and opponents_guesses_grid.read_grid( row_idx, col_idx) != LOCATION_GUESS_HIT): return True return False
def check_ship_alive(self, locations_grid: GameGrid, opponents_guesses_grid: GameGrid, ship_value): assert ship_value != SHIP_LOCATION_EMPTY for row_idx in range(self.num_rows): for col_idx in range(self.num_cols): if (locations_grid.read_grid(row_idx, col_idx) == ship_value and opponents_guesses_grid.read_grid( row_idx, col_idx) != LOCATION_GUESS_HIT): return True return False
def clear_ship_placement(self, locations_grid: GameGrid, ship_value, ship_dims: Tuple[int, int]): print( f"clearing ship placement: value = {ship_value}, dims = {ship_dims}" ) for row_idx in range(self.num_rows): for col_idx in range(self.num_cols): if locations_grid.read_grid(row_idx, col_idx) == ship_value: locations_grid.update_grid(row_idx, col_idx, new_value=SHIP_LOCATION_EMPTY)
def _is_valid_ship_placement(self, ship_locations_grid: GameGrid, ship_value, ship_dims: Tuple[int, int]): # ship dimensions must not be 0 assert ship_dims[0] > 0 and ship_dims[1] > 0 # make sure the ship is located, and the dimensions match # 1. check the count of locations vs. dimensions # 2. check the bounds of dimensions (min and max, x and y) min_row_idx, max_row_idx = None, None min_col_idx, max_col_idx = None, None count_ship_value = 0 for row_idx in range(self.num_rows): for col_idx in range(self.num_cols): if ship_locations_grid.read_grid(row_idx, col_idx) == ship_value: count_ship_value += 1 if min_row_idx is None or row_idx < min_row_idx: min_row_idx = row_idx if max_row_idx is None or row_idx > max_row_idx: max_row_idx = row_idx if min_col_idx is None or col_idx < min_col_idx: min_col_idx = col_idx if max_col_idx is None or col_idx > max_col_idx: max_col_idx = col_idx # make sure the correct number of squares are labeled as the ship and the dimension boundaries match return ship_dims[0] * ship_dims[1] == count_ship_value and ( (max_row_idx - min_row_idx + 1 == ship_dims[0] and max_col_idx - min_col_idx + 1 == ship_dims[1]) or (max_row_idx - min_row_idx + 1 == ship_dims[1] and max_col_idx - min_col_idx + 1 == ship_dims[0]))
def get_move(self, current_grid : GameGrid, current_state): available_moves = [move for move in self._moves_list if current_grid.canMove(move, current_state)] # self._logger.debug('From state %s : available moves : %s', current_state, available_moves) if len(available_moves) == 1: # self._logger.debug("One move available : %s", available_moves[0]) return available_moves[0] # Don't waste time running AI if len(available_moves) == 0: # self._logger.debug("No move available.") return self._moves_list[0] # whatever, it wont't move ! if (self.epsilon > 0) and (random.uniform(0, 1) < self.epsilon): self._logger.debug("Randomly choose move") return random.choice(available_moves) current_q_val = self.q_values.iloc[current_state, :][available_moves] max_val = current_q_val.max() optimal_moves = current_q_val[current_q_val == max_val].index.tolist() self._logger.debug("Optimal moves : %s", optimal_moves) if len(optimal_moves) == 0: # shouldn't happen raise Exception("No optimal move in Get move function, %s", max_val) elif len(optimal_moves) == 1: return optimal_moves[0] else: return random.choice(optimal_moves)
def __init__( self, num_rows: int = 10, num_cols: int = 10, is_my_turn: bool = True, our_ship_locations: Optional[List[List]] = None, opponent_ship_locations: Optional[List[List]] = None, our_guesses: Optional[List[List]] = None, opponent_guesses: Optional[List[List]] = None, ships_dimensions: Optional[List[Tuple[int, int]]] = None, ): self.is_my_turn = is_my_turn self.is_game_over = False self.num_rows = num_rows self.num_cols = num_cols self.our_ship_locations = GameGrid(num_rows=num_rows, num_cols=num_cols, initial_value=SHIP_LOCATION_EMPTY) if our_ship_locations is not None: self.our_ship_locations.update_entire_grid(our_ship_locations) self.opponent_ship_locations = GameGrid( num_rows=num_rows, num_cols=num_cols, initial_value=SHIP_LOCATION_EMPTY) if opponent_ship_locations is not None: self.opponent_ship_locations.update_entire_grid( opponent_ship_locations) self.our_guesses = GameGrid(num_rows=num_rows, num_cols=num_cols, initial_value=LOCATION_NOT_GUESSED) if our_guesses is not None: self.our_guesses.update_entire_grid(our_guesses) self.opponent_guesses = GameGrid(num_rows=num_rows, num_cols=num_cols, initial_value=LOCATION_NOT_GUESSED) if opponent_guesses is not None: self.opponent_guesses.update_entire_grid(opponent_guesses) # track which ships are still alive for us and our opponent if ships_dimensions is None: ship_dimensions_to_use = STANDARD_SHIP_DIMENSIONS else: ship_dimensions_to_use = ships_dimensions # lists of tuples: ship value in the grid, and the dimensions of the ship self.our_ships = [] self.opponent_ships = [] next_ship_number = 1 for ship_dims in ship_dimensions_to_use: self.our_ships.append((next_ship_number, ship_dims)) self.opponent_ships.append((next_ship_number, ship_dims)) next_ship_number += 1 self.ships_placed = self.check_placements_ready()
def _check_rectangular_region_values( grid: GameGrid, top_row_idx: int, left_col_idx: int, height: int, width: int, region_value, other_value, ): for row_idx in range(grid.num_rows): for col_idx in range(grid.num_rows): if ( top_row_idx <= row_idx < top_row_idx + height and left_col_idx <= col_idx < left_col_idx + width ): assert grid.read_grid(row_idx, col_idx) == region_value else: assert grid.read_grid(row_idx, col_idx) == other_value
def attempt_strike( self, struck_locations_grid: GameGrid, strikers_guesses_grid: GameGrid, square_row_idx: int, square_col_idx: int, ) -> bool: """ Returns True if the guess hit a ship """ # Did the guess hit a ship? (check struck_locations_grid) if (struck_locations_grid.read_grid( square_row_idx, square_col_idx) == SHIP_LOCATION_EMPTY): # The guess missed! (update strikers_guesses_grid with a miss) strikers_guesses_grid.update_grid(square_row_idx, square_col_idx, LOCATION_GUESS_MISS) return False # The guess hit! (update strikers_guesses_grid with a hit) strikers_guesses_grid.update_grid(square_row_idx, square_col_idx, LOCATION_GUESS_HIT) # Did the hit sink a ship? (check struck_locations_grid again) ship_that_was_hit = struck_locations_grid.read_grid( square_row_idx, square_col_idx) if not self.check_ship_alive(struck_locations_grid, strikers_guesses_grid, ship_that_was_hit): # The guess sunk an ship! # TODO update the struck player's alive ships # Did the guess end the game? if not self.any_ships_alive(struck_locations_grid, strikers_guesses_grid): print("game is now over!") self.is_game_over = True return True
def __init__(self, manager, white_player='human', black_player='human'): # game state members self.game_manager = manager self.game_running = False self.needs_redraw = True self.screen = None self.clock = pygame.time.Clock() # game components self.game_logic = GameLogic(self) self.game_grid = GameGrid(self) self.game_board = None self.white_player = None self.black_player = None self.initialize_players(white_player, black_player) self.turn_manager = TurnManager(self) self.highlighter = Highlighter(self) self.logger = Logger(self) self.buttons = {}
def rotate_ship_placement(self, locations_grid: GameGrid, ship_value) -> bool: """ Rotate the ship around its top left corner. Returns False if the rotation isn't possible (and doesn't change the game state). """ min_row_idx, max_row_idx = None, None min_col_idx, max_col_idx = None, None count_ship_value = 0 for row_idx in range(self.num_rows): for col_idx in range(self.num_cols): if locations_grid.read_grid(row_idx, col_idx) == ship_value: count_ship_value += 1 if min_row_idx is None or row_idx < min_row_idx: min_row_idx = row_idx if max_row_idx is None or row_idx > max_row_idx: max_row_idx = row_idx if min_col_idx is None or col_idx < min_col_idx: min_col_idx = col_idx if max_col_idx is None or col_idx > max_col_idx: max_col_idx = col_idx ship_width, ship_height = ( max_col_idx - min_col_idx + 1, max_row_idx - min_row_idx + 1, ) self.clear_ship_placement(locations_grid, ship_value, ship_dims=(ship_width, ship_height)) rotate_success = self.place_ship( min_row_idx, min_col_idx, ship_width=ship_height, ship_height=ship_width, ship_value=ship_value, is_our_ship=True, ) if not rotate_success: self.place_ship( min_row_idx, min_col_idx, ship_width=ship_width, ship_height=ship_height, ship_value=ship_value, is_our_ship=True, ) return False else: return True
def start(): global grid # create the game grid grid = GameGrid(grid_h, grid_w) # create the first tetromino to enter the game grid # by using the create_tetromino function defined below current_tetromino = create_tetromino(grid_h, grid_w) # print("next tetromino:") next_tetromino = create_tetromino(grid_h, grid_w) grid.current_tetromino = current_tetromino grid.next_tetromino = next_tetromino stddraw.clearKeysTyped() pause = False # main game loop (keyboard interaction for moving the tetromino) while True: if not pause: mx, my = stddraw.getPosition() tileX = grid.current_tetromino.bottom_left_corner.x ax = int(mx / 42.35) - 1 # print(ax, tileX) if ax > tileX: for i in range(ax - tileX): grid.current_tetromino.move("right", grid) elif ax < tileX: for i in range(tileX - ax): grid.current_tetromino.move("left", grid) # check user interactions via the keyboard if stddraw.hasNextKeyTyped(): key_typed = stddraw.nextKeyTyped() # Pause if key_typed == 'p': print("Pause") if pause: pause = False else: pause = True elif not pause: # if the left arrow key has been pressed if key_typed == "left": # move the tetromino left by one # print("Left Typed") current_tetromino.move(key_typed, grid) # if the right arrow key has been pressed elif key_typed == "right": # print("Right Typed") # move the tetromino right by one current_tetromino.move(key_typed, grid) # if the down arrow key has been pressed elif key_typed == "down": # move the tetromino down by one # (causes the tetromino to fall down faster) current_tetromino.move(key_typed, grid) # piece drop elif key_typed == 'space': for i in range(grid_h): current_tetromino.move('down', grid) # Speed Increase elif key_typed == 'w': if grid.delta_time > 50: grid.delta_time -= 40 # Speed Decrease elif key_typed == 's': if grid.delta_time < 500: grid.delta_time += 40 elif key_typed == 'e': current_tetromino.rotate(grid) elif key_typed == 'q': current_tetromino.rotate_ccw(grid) if key_typed == 'r': print("restart") start() # clear the queue that stores all the keys pressed/typed stddraw.clearKeysTyped() # move (drop) the tetromino down by 1 at each iteration if not pause: success = current_tetromino.move("down", grid) # place the tetromino on the game grid when it cannot go down anymore if not success and not pause: # get the tile matrix of the tetromino tiles_to_place = current_tetromino.tile_matrix # update the game grid by adding the tiles of the tetromino game_over = grid.update_grid(tiles_to_place) # end the main game loop if the game is over if game_over: if display_game_over(grid_h, grid_w + 5): pause = True start() # create the next tetromino to enter the game grid # by using the create_tetromino function defined below current_tetromino = next_tetromino grid.current_tetromino = current_tetromino print("next tetromino:") next_tetromino = create_tetromino(grid_h, grid_w) grid.next_tetromino = next_tetromino next_tetromino.draw_dummy() # display the game grid and as well the current tetromino grid.display(pause) print("Game over")
def clear_all_ship_placements(self, locations_grid: GameGrid): for row_idx in range(self.num_rows): for col_idx in range(self.num_cols): locations_grid.update_grid(row_idx, col_idx, new_value=SHIP_LOCATION_EMPTY)
class GameScene(Scene): def __init__(self, rect, levels): super().__init__(rect) self.levels = levels self.curr_level_index = 0 self.num_levels = len(levels) def build(self, level=None): level = self.levels[self.curr_level_index] if level is None else level self.level_name = level['level_name'] self.grid = GameGrid(glb.GAMEGRIDRECT, glb.CELL_SIZE) # place head of the snake in the center and align to the grid snake_x, snake_y = self.grid.cell2xy(*self.grid.xy2cell( glb.GAMEGRIDRECT.centerx, glb.GAMEGRIDRECT.centery)) # create snake parts head = SnakePart(snake_x, snake_y, head_image, glb.DIRECTION_UP, glb.DIRECTION_UP, dir2img_head) neck = SnakePart(snake_x, snake_y + glb.SNAKE_PART_HEIGHT, body_straight_image, glb.DIRECTION_UP, glb.DIRECTION_UP, dir2img_body) body = SnakePart(snake_x, snake_y + glb.SNAKE_PART_HEIGHT * 2, body_straight_image, glb.DIRECTION_UP, glb.DIRECTION_UP, dir2img_body) tail = SnakePart(snake_x, snake_y + glb.SNAKE_PART_HEIGHT * 3, tail_image, glb.DIRECTION_UP, glb.DIRECTION_UP, dir2img_tail) parts = [head, neck, body, tail] self.snake = Snake(parts, speed=level['game_speed'], wrap_around=level.get('wrap_around', glb.SNAKE_CAN_WRAP_AROUND), bounding_rect=glb.GAMEGRIDRECT) self.group_snake = Group([self.snake]) for part in self.snake.parts: self.grid.occupy_cell(*self.grid.xy2cell(*part.getpos())) # create some food self.food = Group() food_table = level.get('food_table', default_food_table) self.food_factory = FoodFactory(food_table) for _ in range(level['num_starting_food']): fc = self.grid.get_random_free_cell() if fc: food_x, food_y = self.grid.cell2xy(*fc) self.grid.occupy_cell(*fc) self.food.append(self.food_factory.make_random(food_x, food_y)) # create some walls self.walls = Group() for _ in range(level['num_starting_walls']): fc = self.grid.get_random_free_cell() if fc: wall_x, wall_y = self.grid.cell2xy(*fc) self.grid.occupy_cell(*fc) self.walls.append(Wall(wall_x, wall_y, wall_image)) self.score = 0 self.score_needed = level['score_needed'] # tile background with sand texture for cix in range(self.grid.n_cells_x): for ciy in range(self.grid.n_cells_y): self.background.blit(ground_image, self.grid.cell2xy(cix, ciy)) # make bounding fence with electricity for cix in range(self.grid.n_cells_x): self.background.blit(fence_horiz_image, self.grid.cell2xy(cix, -1, False)) self.background.blit( fence_horiz_image_flip_v, self.grid.cell2xy(cix, self.grid.n_cells_y, False)) for ciy in range(self.grid.n_cells_y): self.background.blit(fence_vert_image, self.grid.cell2xy(-1, ciy, False)) self.background.blit( fence_vert_image_flip_h, self.grid.cell2xy(self.grid.n_cells_x, ciy, False)) self.background.blit(fence_corner_image, self.grid.cell2xy(-1, -1, False)) self.background.blit(fence_corner_image_flip_h, self.grid.cell2xy(self.grid.n_cells_x, -1, False)) self.background.blit(fence_corner_image_flip_v, self.grid.cell2xy(-1, self.grid.n_cells_y, False)) self.background.blit( fence_corner_image_flip_hv, self.grid.cell2xy(self.grid.n_cells_x, self.grid.n_cells_y, False)) # reset timer self.time_elapsed = 0 self.max_time = level['max_time'] if self.timer is not None and self.timer.is_running: self.timer.stop() self.timer = RepeatedTimer(1, self.increase_time, None) # reset texts self.text_score = ScreenTextBitmapFont('', 10, 20, bitmap_font) self.text_health = ScreenTextBitmapFont('', 630, 20, bitmap_font) self.text_time = ScreenTextBitmapFont('', glb.WIDTH // 2, 20, bitmap_font) self.text_level_name = ScreenTextBitmapFont(f'{self.level_name}', glb.WIDTH // 4 + 32, 20, bitmap_font) self.texts = Group([ self.text_score, self.text_health, self.text_time, self.text_level_name ]) # reset user events self.EVENT_PLAYER_LOOSE_LEVEL = pygame.USEREVENT + 1 self.EVENT_PLAYER_WIN_LEVEL = pygame.USEREVENT + 2 self.up = 0 self.right = 0 self.blinking_rect_fx = BlinkingRectFX() self.particles = [] self.built = True def increase_time(self, *args, **kwargs): # TODO: VERY UGLY CODE!! self.time_elapsed += 1 if self.time_is_out(10): snd_clock_tick.play() def update(self): snake = self.snake grid = self.grid food = self.food for part in snake.parts: grid.release_cell(*grid.xy2cell(*part.getpos())) snake.move(self.up, self.right, self.walls) for part in snake.parts: grid.occupy_cell(*grid.xy2cell(*part.getpos())) if snake.intersect_itself or not snake.within_world: snake.kill() if snake.wrap_around and self.time_elapsed - self.wrap_around_time > 15: self.snake.wrap_around = False # grow snake if it eats food for f in food: food_rect = grid.get_cell_rect(*grid.xy2cell(*f.rect.center)) if snake.head.rect.colliderect(food_rect): snake.add_part(grid) if f.food_type == 'portal': snake.wrap_around = True self.wrap_around_time = self.time_elapsed if f.food_type == 'potion': snd_eat_potion.play() elif f.food_type == 'mushroom': snd_eat_bad_food.play() else: snd_eat_good_food.play() if f.health > 0: self.score += f.score snake.health += f.health food_cix, food_ciy = grid.xy2cell(f.rect.x, f.rect.y) grid.release_cell(food_cix, food_ciy) food.remove(f) fc = grid.get_random_free_cell() if fc is not None: food_x, food_y = grid.cell2xy(*fc) grid.occupy_cell(*fc) new_food = self.food_factory.make_random(food_x, food_y) food.append(new_food) # FX: create new pretty color bubble that will expand around created food for _ in range(1): x = new_food.rect.centerx y = new_food.rect.centery color = food_type_to_bubble_color.get( new_food.food_type, glb.PINK) gravity = 0 vx = 0 vy = 0 radius = 1 border_size = 2 lifetime = 25 rect = new_food.rect.inflate(10, 10) particle = Particle( x, y, vx, vy, color, radius, lifetime, gravity, border_size, size_func=lambda size: size + 3.5, border_size_func=spawn_bubble_border_size_func, #rect=rect ) self.particles.append(particle) if not self.snake.alive or self.time_is_out(): pygame.event.post(pygame.event.Event( self.EVENT_PLAYER_LOOSE_LEVEL)) return if self.score >= self.score_needed: pygame.event.post(pygame.event.Event(self.EVENT_PLAYER_WIN_LEVEL)) snake.update() for p in self.particles: p.update() self.particles = [p for p in self.particles if p.alive] # update texts self.text_score.set_text(f'SCORE: {self.score}/{self.score_needed}') self.text_health.set_text(f'HEALTH: {snake.health}') if self.max_time: self.text_time.set_text( f'TIME: {self.time_elapsed}/{self.max_time}') else: self.text_time.set_text(f'TIME: {self.time_elapsed}') def clear_screen(self, screen): dirtyrects.clear() self.group_snake.clear(screen, self.background) self.walls.clear(screen, self.background) self.food.clear(screen, self.background) self.texts.clear(screen, self.background) def draw(self, screen): self.group_snake.draw(screen) self.walls.draw(screen) self.food.draw(screen) self.texts.draw(screen) if self.snake.wrap_around: self.blinking_rect_fx.draw(screen) pygame.display.update(dirtyrects) for particle in self.particles: particle.draw(screen) def time_is_out(self, delta=0): if self.max_time <= 0: return False return self.time_elapsed >= self.max_time - delta def handle_transitions(self, event): if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: self.scene_manager.pop_scene() return if event.type == self.EVENT_PLAYER_LOOSE_LEVEL: self.build() # here we should rebuild current level self.scene_manager.push_scene(you_loose_splash_screen) return if event.type == self.EVENT_PLAYER_WIN_LEVEL: if self.curr_level_index < self.num_levels - 1: self.curr_level_index += 1 self.build() # here we should build next level self.scene_manager.push_scene(you_win_splash_screen) else: self.scene_manager.push_scene(final_splash_screen) def handle_events(self, event): keystate = pygame.key.get_pressed() # handle input self.up = keystate[pygame.K_UP] - keystate[pygame.K_DOWN] self.right = keystate[pygame.K_RIGHT] - keystate[pygame.K_LEFT] def enter(self): if self.timer is not None and not self.timer.is_running: self.timer.start() def leave(self): if self.timer is not None and self.timer.is_running: self.timer.stop() def destroy(self): self.curr_level_index = 0 self.built = False
def start(): # set the dimensions of the game grid grid_h, grid_w = 17, 12 # set the size of the drawing canvas canvas_h, canvas_w = 40 * grid_h, 40 * grid_w + 100 stddraw.setCanvasSize(canvas_w, canvas_h) # set the scale of the coordinate system stddraw.setXscale(-0.5, grid_w + 3) stddraw.setYscale(-0.5, grid_h - 0.5) # create the game grid grid = GameGrid(grid_h, grid_w) # create the first tetromino to enter the game grid # by using the create_tetromino function defined below current_tetromino = create_tetromino(grid_h, grid_w) grid.current_tetromino = current_tetromino # display a simple menu before opening the game display_game_menu(grid_h, grid_w) # initial score score = 0 speed = 250 #initial speed # main game loop (keyboard interaction for moving the tetromino) while True: # check user interactions via the keyboard if stddraw.hasNextKeyTyped(): key_typed = stddraw.nextKeyTyped() # if the left arrow key has been pressed if key_typed == "left": # move the tetromino left by one current_tetromino.move(key_typed, grid) # if the right arrow key has been pressed elif key_typed == "right": # move the tetromino right by one current_tetromino.move(key_typed, grid) # if the down arrow key has been pressed elif key_typed == "down": # move the tetromino down by one # (causes the tetromino to fall down faster) current_tetromino.move(key_typed, grid) elif key_typed == "up": # rotate the tetromino 90 degree clock-wise current_tetromino.rotate(grid) elif key_typed == "space": # drop the tetromino for i in range(grid_h): current_tetromino.move("down", grid) # clear the queue that stores all the keys pressed/typed stddraw.clearKeysTyped() # move (drop) the tetromino down by 1 at each iteration success = current_tetromino.move("down", grid) grid.connected_4() # place the tetromino on the game grid when it cannot go down anymore if not success: # get the tile matrix of the tetromino tiles_to_place = current_tetromino.tile_matrix # update the game grid by adding the tiles of the tetromino game_over = grid.update_grid(tiles_to_place) indv_score = 0 # starting value for a full row's score ind_score = 0 # starting value for a merged tiles score # check is_row_full for all rows for i in range(grid_h): grid.check_2048(grid.tile_matrix) # score from merged tiles ind_score = grid.update_score(grid.tile_num2) if grid.is_row_full(i, grid.tile_matrix): # score from deleted full rows indv_score = grid.update_score(grid.tile_num) grid.tile_num2 = np.zeros(100) # for merged score score_val = ind_score + indv_score score += int(score_val) print(score) # end the main game loop if the game is over if game_over: break # increasing difficulty by increasing speed as the game process if score > 450: speed = 10 elif score > 250: speed = 50 elif score > 150: speed = 100 if score > 10000: break # create the next tetromino to enter the game grid # by using the create_tetromino function defined below current_tetromino = create_tetromino(grid_h, grid_w) grid.current_tetromino = current_tetromino # display the game grid and as well the current tetromino grid.display(score, speed) # finish the game and display game over finish_game(grid_h, grid_w) print("Game over")
def build(self, level=None): level = self.levels[self.curr_level_index] if level is None else level self.level_name = level['level_name'] self.grid = GameGrid(glb.GAMEGRIDRECT, glb.CELL_SIZE) # place head of the snake in the center and align to the grid snake_x, snake_y = self.grid.cell2xy(*self.grid.xy2cell( glb.GAMEGRIDRECT.centerx, glb.GAMEGRIDRECT.centery)) # create snake parts head = SnakePart(snake_x, snake_y, head_image, glb.DIRECTION_UP, glb.DIRECTION_UP, dir2img_head) neck = SnakePart(snake_x, snake_y + glb.SNAKE_PART_HEIGHT, body_straight_image, glb.DIRECTION_UP, glb.DIRECTION_UP, dir2img_body) body = SnakePart(snake_x, snake_y + glb.SNAKE_PART_HEIGHT * 2, body_straight_image, glb.DIRECTION_UP, glb.DIRECTION_UP, dir2img_body) tail = SnakePart(snake_x, snake_y + glb.SNAKE_PART_HEIGHT * 3, tail_image, glb.DIRECTION_UP, glb.DIRECTION_UP, dir2img_tail) parts = [head, neck, body, tail] self.snake = Snake(parts, speed=level['game_speed'], wrap_around=level.get('wrap_around', glb.SNAKE_CAN_WRAP_AROUND), bounding_rect=glb.GAMEGRIDRECT) self.group_snake = Group([self.snake]) for part in self.snake.parts: self.grid.occupy_cell(*self.grid.xy2cell(*part.getpos())) # create some food self.food = Group() food_table = level.get('food_table', default_food_table) self.food_factory = FoodFactory(food_table) for _ in range(level['num_starting_food']): fc = self.grid.get_random_free_cell() if fc: food_x, food_y = self.grid.cell2xy(*fc) self.grid.occupy_cell(*fc) self.food.append(self.food_factory.make_random(food_x, food_y)) # create some walls self.walls = Group() for _ in range(level['num_starting_walls']): fc = self.grid.get_random_free_cell() if fc: wall_x, wall_y = self.grid.cell2xy(*fc) self.grid.occupy_cell(*fc) self.walls.append(Wall(wall_x, wall_y, wall_image)) self.score = 0 self.score_needed = level['score_needed'] # tile background with sand texture for cix in range(self.grid.n_cells_x): for ciy in range(self.grid.n_cells_y): self.background.blit(ground_image, self.grid.cell2xy(cix, ciy)) # make bounding fence with electricity for cix in range(self.grid.n_cells_x): self.background.blit(fence_horiz_image, self.grid.cell2xy(cix, -1, False)) self.background.blit( fence_horiz_image_flip_v, self.grid.cell2xy(cix, self.grid.n_cells_y, False)) for ciy in range(self.grid.n_cells_y): self.background.blit(fence_vert_image, self.grid.cell2xy(-1, ciy, False)) self.background.blit( fence_vert_image_flip_h, self.grid.cell2xy(self.grid.n_cells_x, ciy, False)) self.background.blit(fence_corner_image, self.grid.cell2xy(-1, -1, False)) self.background.blit(fence_corner_image_flip_h, self.grid.cell2xy(self.grid.n_cells_x, -1, False)) self.background.blit(fence_corner_image_flip_v, self.grid.cell2xy(-1, self.grid.n_cells_y, False)) self.background.blit( fence_corner_image_flip_hv, self.grid.cell2xy(self.grid.n_cells_x, self.grid.n_cells_y, False)) # reset timer self.time_elapsed = 0 self.max_time = level['max_time'] if self.timer is not None and self.timer.is_running: self.timer.stop() self.timer = RepeatedTimer(1, self.increase_time, None) # reset texts self.text_score = ScreenTextBitmapFont('', 10, 20, bitmap_font) self.text_health = ScreenTextBitmapFont('', 630, 20, bitmap_font) self.text_time = ScreenTextBitmapFont('', glb.WIDTH // 2, 20, bitmap_font) self.text_level_name = ScreenTextBitmapFont(f'{self.level_name}', glb.WIDTH // 4 + 32, 20, bitmap_font) self.texts = Group([ self.text_score, self.text_health, self.text_time, self.text_level_name ]) # reset user events self.EVENT_PLAYER_LOOSE_LEVEL = pygame.USEREVENT + 1 self.EVENT_PLAYER_WIN_LEVEL = pygame.USEREVENT + 2 self.up = 0 self.right = 0 self.blinking_rect_fx = BlinkingRectFX() self.particles = [] self.built = True
class BattleshipGameState: def __init__( self, num_rows: int = 10, num_cols: int = 10, is_my_turn: bool = True, our_ship_locations: Optional[List[List]] = None, opponent_ship_locations: Optional[List[List]] = None, our_guesses: Optional[List[List]] = None, opponent_guesses: Optional[List[List]] = None, ships_dimensions: Optional[List[Tuple[int, int]]] = None, ): self.is_my_turn = is_my_turn self.is_game_over = False self.num_rows = num_rows self.num_cols = num_cols self.our_ship_locations = GameGrid(num_rows=num_rows, num_cols=num_cols, initial_value=SHIP_LOCATION_EMPTY) if our_ship_locations is not None: self.our_ship_locations.update_entire_grid(our_ship_locations) self.opponent_ship_locations = GameGrid( num_rows=num_rows, num_cols=num_cols, initial_value=SHIP_LOCATION_EMPTY) if opponent_ship_locations is not None: self.opponent_ship_locations.update_entire_grid( opponent_ship_locations) self.our_guesses = GameGrid(num_rows=num_rows, num_cols=num_cols, initial_value=LOCATION_NOT_GUESSED) if our_guesses is not None: self.our_guesses.update_entire_grid(our_guesses) self.opponent_guesses = GameGrid(num_rows=num_rows, num_cols=num_cols, initial_value=LOCATION_NOT_GUESSED) if opponent_guesses is not None: self.opponent_guesses.update_entire_grid(opponent_guesses) # track which ships are still alive for us and our opponent if ships_dimensions is None: ship_dimensions_to_use = STANDARD_SHIP_DIMENSIONS else: ship_dimensions_to_use = ships_dimensions # lists of tuples: ship value in the grid, and the dimensions of the ship self.our_ships = [] self.opponent_ships = [] next_ship_number = 1 for ship_dims in ship_dimensions_to_use: self.our_ships.append((next_ship_number, ship_dims)) self.opponent_ships.append((next_ship_number, ship_dims)) next_ship_number += 1 self.ships_placed = self.check_placements_ready() def place_ship( self, top_row_idx: int, left_col_idx: int, ship_width: int, ship_height: int, ship_value, is_our_ship: bool, ) -> bool: # ship dimensions must not be 0 assert ship_width > 0 and ship_height > 0 # check that (ship value, ship dims) match the expected ship values if is_our_ship and ((ship_value, (ship_height, ship_width)) not in self.our_ships and (ship_value, (ship_width, ship_height)) not in self.our_ships): return False elif not is_our_ship and ( (ship_value, (ship_height, ship_width)) not in self.opponent_ships and (ship_value, (ship_width, ship_height)) not in self.opponent_ships): return False # if the ship wasn't already placed, clear its old position first before placing again if is_our_ship and self._is_valid_ship_placement( self.our_ship_locations, ship_value, (ship_height, ship_width)): self.clear_ship_placement(self.our_ship_locations, ship_value, (ship_height, ship_width)) elif not is_our_ship and self._is_valid_ship_placement( self.opponent_ship_locations, ship_value, (ship_height, ship_width)): self.clear_ship_placement(self.opponent_ship_locations, ship_value, (ship_height, ship_width)) # check if the ship placement overlaps with a buffer! (can do neighboring check for each) bottom_row_idx = top_row_idx + ship_height - 1 right_col_idx = left_col_idx + ship_width - 1 # check if ship placement has an invalid coordinates for row_idx in range(top_row_idx, bottom_row_idx + 1): for col_idx in range(left_col_idx, right_col_idx + 1): if is_our_ship and not self.our_ship_locations.are_indexes_valid( row_idx, col_idx): return False elif (not is_our_ship and not self.opponent_ship_locations.are_indexes_valid( row_idx, col_idx)): return False top_buffer_row_idx = max(0, top_row_idx - 1) bottom_buffer_row_idx = min(self.num_rows - 1, bottom_row_idx + 1) left_buffer_row_idx = max(0, left_col_idx - 1) right_buffer_row_idx = min(self.num_cols - 1, right_col_idx + 1) for row_idx in range(top_buffer_row_idx, bottom_buffer_row_idx + 1): for col_idx in range(left_buffer_row_idx, right_buffer_row_idx + 1): if is_our_ship: ship_loc_value = self.our_ship_locations.read_grid( row_idx, col_idx) else: ship_loc_value = self.opponent_ship_locations.read_grid( row_idx, col_idx) if ship_loc_value != SHIP_LOCATION_EMPTY: # can't place ship because another ship is overlapping with the buffer return False # now that the ship placement is verified, we can safely update the locations grid for row_idx in range(top_row_idx, bottom_row_idx + 1): for col_idx in range(left_col_idx, right_col_idx + 1): if is_our_ship: self.our_ship_locations.update_grid(row_idx, col_idx, new_value=ship_value) else: self.opponent_ship_locations.update_grid( row_idx, col_idx, new_value=ship_value) self.ships_placed = self.check_placements_ready() return True def check_placements_ready(self) -> bool: # not only check if placements are valid, but check that both players have placed all available ships for ship_value, ship_dims in self.our_ships: if not self._is_valid_ship_placement(self.our_ship_locations, ship_value, ship_dims): return False for ship_value, ship_dims in self.opponent_ships: if not self._is_valid_ship_placement(self.opponent_ship_locations, ship_value, ship_dims): return False return True def _is_valid_ship_placement(self, ship_locations_grid: GameGrid, ship_value, ship_dims: Tuple[int, int]): # ship dimensions must not be 0 assert ship_dims[0] > 0 and ship_dims[1] > 0 # make sure the ship is located, and the dimensions match # 1. check the count of locations vs. dimensions # 2. check the bounds of dimensions (min and max, x and y) min_row_idx, max_row_idx = None, None min_col_idx, max_col_idx = None, None count_ship_value = 0 for row_idx in range(self.num_rows): for col_idx in range(self.num_cols): if ship_locations_grid.read_grid(row_idx, col_idx) == ship_value: count_ship_value += 1 if min_row_idx is None or row_idx < min_row_idx: min_row_idx = row_idx if max_row_idx is None or row_idx > max_row_idx: max_row_idx = row_idx if min_col_idx is None or col_idx < min_col_idx: min_col_idx = col_idx if max_col_idx is None or col_idx > max_col_idx: max_col_idx = col_idx # make sure the correct number of squares are labeled as the ship and the dimension boundaries match return ship_dims[0] * ship_dims[1] == count_ship_value and ( (max_row_idx - min_row_idx + 1 == ship_dims[0] and max_col_idx - min_col_idx + 1 == ship_dims[1]) or (max_row_idx - min_row_idx + 1 == ship_dims[1] and max_col_idx - min_col_idx + 1 == ship_dims[0])) def clear_ship_placement(self, locations_grid: GameGrid, ship_value, ship_dims: Tuple[int, int]): print( f"clearing ship placement: value = {ship_value}, dims = {ship_dims}" ) for row_idx in range(self.num_rows): for col_idx in range(self.num_cols): if locations_grid.read_grid(row_idx, col_idx) == ship_value: locations_grid.update_grid(row_idx, col_idx, new_value=SHIP_LOCATION_EMPTY) def clear_all_ship_placements(self, locations_grid: GameGrid): for row_idx in range(self.num_rows): for col_idx in range(self.num_cols): locations_grid.update_grid(row_idx, col_idx, new_value=SHIP_LOCATION_EMPTY) def randomize_ship_placements( self, ship_dims: List[Tuple[int, int]], our_ships: bool, ): available_squares = [] for r in range(self.num_rows): row = [] for c in range(self.num_cols): row.append(True) available_squares.append(row) random_ship_placements = random_ships_placement( ship_dims, available_squares=available_squares, num_rows=self.num_rows, num_cols=self.num_cols, rotate_allowed=True, ) for idx in range(len(random_ship_placements)): ship_placement = random_ship_placements[idx] top_row_idx, left_col_idx, ship_height, ship_width = ship_placement self.place_ship( top_row_idx=top_row_idx, left_col_idx=left_col_idx, ship_width=ship_width, ship_height=ship_height, ship_value=idx + 1, is_our_ship=our_ships, ) print( f"placed ship {idx + 1}, dims {ship_height}, {ship_width} at {top_row_idx}, {left_col_idx}" ) def rotate_ship_placement(self, locations_grid: GameGrid, ship_value) -> bool: """ Rotate the ship around its top left corner. Returns False if the rotation isn't possible (and doesn't change the game state). """ min_row_idx, max_row_idx = None, None min_col_idx, max_col_idx = None, None count_ship_value = 0 for row_idx in range(self.num_rows): for col_idx in range(self.num_cols): if locations_grid.read_grid(row_idx, col_idx) == ship_value: count_ship_value += 1 if min_row_idx is None or row_idx < min_row_idx: min_row_idx = row_idx if max_row_idx is None or row_idx > max_row_idx: max_row_idx = row_idx if min_col_idx is None or col_idx < min_col_idx: min_col_idx = col_idx if max_col_idx is None or col_idx > max_col_idx: max_col_idx = col_idx ship_width, ship_height = ( max_col_idx - min_col_idx + 1, max_row_idx - min_row_idx + 1, ) self.clear_ship_placement(locations_grid, ship_value, ship_dims=(ship_width, ship_height)) rotate_success = self.place_ship( min_row_idx, min_col_idx, ship_width=ship_height, ship_height=ship_width, ship_value=ship_value, is_our_ship=True, ) if not rotate_success: self.place_ship( min_row_idx, min_col_idx, ship_width=ship_width, ship_height=ship_height, ship_value=ship_value, is_our_ship=True, ) return False else: return True def check_ship_alive(self, locations_grid: GameGrid, opponents_guesses_grid: GameGrid, ship_value): assert ship_value != SHIP_LOCATION_EMPTY for row_idx in range(self.num_rows): for col_idx in range(self.num_cols): if (locations_grid.read_grid(row_idx, col_idx) == ship_value and opponents_guesses_grid.read_grid( row_idx, col_idx) != LOCATION_GUESS_HIT): return True return False def any_ships_alive(self, locations_grid: GameGrid, opponents_guesses_grid: GameGrid): for row_idx in range(self.num_rows): for col_idx in range(self.num_cols): if (locations_grid.read_grid(row_idx, col_idx) != SHIP_LOCATION_EMPTY and opponents_guesses_grid.read_grid( row_idx, col_idx) != LOCATION_GUESS_HIT): return True return False def get_player_home_grid(self) -> List[List]: grid_symbols = [] for row_idx in range(self.num_rows): grid_row = [] for col_idx in range(self.num_cols): ship_loc_value = self.our_ship_locations.read_grid( row_idx, col_idx) opponent_guess_value = self.opponent_guesses.read_grid( row_idx, col_idx) grid_symbol = " " if ship_loc_value != SHIP_LOCATION_EMPTY: # check if opponent has struck our ship here: if opponent_guess_value == LOCATION_GUESS_HIT: # check if the ship is sunk if self.check_ship_alive( self.our_ship_locations, self.opponent_guesses, ship_loc_value, ): grid_symbol = "X" else: grid_symbol = "S" else: grid_symbol = str(ship_loc_value) else: # check if opponent has missed here if opponent_guess_value == LOCATION_GUESS_MISS: grid_symbol = "." grid_row.append(grid_symbol) grid_symbols.append(grid_row) return grid_symbols def get_player_tracking_grid(self) -> List[List]: grid_symbols = [] for row_idx in range(self.num_rows): grid_row = [] for col_idx in range(self.num_cols): guess_value = self.our_guesses.read_grid(row_idx, col_idx) grid_symbol = " " if guess_value == LOCATION_GUESS_HIT: # check if we struck a ship here: ship_loc_value = self.opponent_ship_locations.read_grid( row_idx, col_idx) assert ship_loc_value != SHIP_LOCATION_EMPTY # check if the ship is sunk if self.check_ship_alive( self.opponent_ship_locations, self.our_guesses, ship_loc_value, ): grid_symbol = "X" else: grid_symbol = "S" elif guess_value == LOCATION_GUESS_MISS: grid_symbol = "." grid_row.append(grid_symbol) grid_symbols.append(grid_row) return grid_symbols def is_game_over(self): return self.any_ships_alive( self.our_ship_locations, self.opponent_guesses) or self.any_ships_alive( self.opponent_ship_locations, self.our_guesses) def attempt_strike( self, struck_locations_grid: GameGrid, strikers_guesses_grid: GameGrid, square_row_idx: int, square_col_idx: int, ) -> bool: """ Returns True if the guess hit a ship """ # Did the guess hit a ship? (check struck_locations_grid) if (struck_locations_grid.read_grid( square_row_idx, square_col_idx) == SHIP_LOCATION_EMPTY): # The guess missed! (update strikers_guesses_grid with a miss) strikers_guesses_grid.update_grid(square_row_idx, square_col_idx, LOCATION_GUESS_MISS) return False # The guess hit! (update strikers_guesses_grid with a hit) strikers_guesses_grid.update_grid(square_row_idx, square_col_idx, LOCATION_GUESS_HIT) # Did the hit sink a ship? (check struck_locations_grid again) ship_that_was_hit = struck_locations_grid.read_grid( square_row_idx, square_col_idx) if not self.check_ship_alive(struck_locations_grid, strikers_guesses_grid, ship_that_was_hit): # The guess sunk an ship! # TODO update the struck player's alive ships # Did the guess end the game? if not self.any_ships_alive(struck_locations_grid, strikers_guesses_grid): print("game is now over!") self.is_game_over = True return True def call_square(self, square_row_idx: int, square_col_idx: int) -> bool: """ Returns True if the guess hit a ship """ if self.is_my_turn: did_hit = self.attempt_strike( self.opponent_ship_locations, self.our_guesses, square_row_idx, square_col_idx, ) else: did_hit = self.attempt_strike( self.our_ship_locations, self.opponent_guesses, square_row_idx, square_col_idx, ) # if the guess missed, then the turn passes to the other player if not did_hit and not self.is_game_over: self.is_my_turn = not self.is_my_turn return did_hit
def _check_all_grid_squares_equal(grid: GameGrid, expected_value): for row_idx in range(grid.num_rows): for col_idx in range(grid.num_rows): assert grid.read_grid(row_idx, col_idx) == expected_value
def start(): # set the dimensions of the game grid grid_h, grid_w = 20, 12 # set the size of the drawing canvas canvas_h, canvas_w = 40 * grid_h, 40 * grid_w stddraw.setCanvasSize(canvas_w, canvas_h) # set the scale of the coordinate system stddraw.setXscale(-0.5, grid_w - 0.5) stddraw.setYscale(-0.5, grid_h - 0.5) # create the game grid grid = GameGrid(grid_h, grid_w) # create the first tetromino to enter the game grid # by using the create_tetromino function defined below current_tetromino = create_tetromino(grid_h, grid_w) next_tetromino = create_tetromino(grid_h, grid_w) grid.current_tetromino = current_tetromino # display a simple menu before opening the game display_game_menu(grid_h, grid_w) # main game loop (keyboard interaction for moving the tetromino) while True: # check user interactions via the keyboard if stddraw.hasNextKeyTyped(): key_typed = stddraw.nextKeyTyped() # if the left arrow key has been pressed if key_typed == "left": # move the tetromino left by one current_tetromino.move(key_typed, grid) # if the right arrow key has been pressed elif key_typed == "right": # move the tetromino right by one current_tetromino.move(key_typed, grid) # if the down arrow key has been pressed elif key_typed == "down": # move the tetromino down by one # (causes the tetromino to fall down faster) current_tetromino.move(key_typed, grid) # clear the queue that stores all the keys pressed/typed elif key_typed == "up": current_tetromino.rotateTetromino() elif key_typed == "space": temp = current_tetromino.move("down", grid) while (temp): temp = current_tetromino.move("down", grid) stddraw.clearKeysTyped() # move (drop) the tetromino down by 1 at each iteration success = current_tetromino.move("down", grid) # place the tetromino on the game grid when it cannot go down anymore if not success: # get the tile matrix of the tetromino tiles_to_place = current_tetromino.tile_matrix # update the game grid by adding the tiles of the tetromino game_over = grid.update_grid(tiles_to_place) rowSet = rowsToCheck(tiles_to_place) grid.rowCheck(rowSet) columnSet = columnsToCheck(tiles_to_place) grid.sumCheck(columnSet, current_tetromino) # end the main game loop if the game is over if game_over: break # create the next tetromino to enter the game grid # by using the create_tetromino function defined below current_tetromino = next_tetromino grid.current_tetromino = current_tetromino next_tetromino = create_tetromino(grid_h, grid_w) print("Score = " + str(grid.score)) print("Next tetromino type is: " + next_tetromino.type) # display the game grid and as well the current tetromino grid.display() print("Game over")
class Game(object): def __init__(self, manager, white_player='human', black_player='human'): # game state members self.game_manager = manager self.game_running = False self.needs_redraw = True self.screen = None self.clock = pygame.time.Clock() # game components self.game_logic = GameLogic(self) self.game_grid = GameGrid(self) self.game_board = None self.white_player = None self.black_player = None self.initialize_players(white_player, black_player) self.turn_manager = TurnManager(self) self.highlighter = Highlighter(self) self.logger = Logger(self) self.buttons = {} def init(self): self.screen = pygame.display.get_surface() self.game_running = True self.screen.fill(BLACK) # initialize game components self.game_board = GameBoard(self, self.game_grid) self.highlighter.init() self.initialize_buttons() def initialize_buttons(self): restart_button = Button(restart_coord, 'Restart', self.restart, restart_anchor, False) end_button = Button(end_coord, 'End Game', self.exit_game, end_anchor) self.buttons['restart'] = restart_button self.buttons['end'] = end_button def initialize_players(self, white, black): players = [] sides = ['white', 'black'] constructors = [] for p_type in (white, black): if p_type == 'human': constructors.append(Player) else: constructors.append(AIPlayer) for i in (0, 1): players.append(constructors[i](self, sides[i])) self.white_player = players[0] self.black_player = players[1] # main game loop def main(self): while self.game_running: self.handle_input() self.run_logic() if self.needs_redraw: self.draw_all() self.update_display() self.reset_redraw() self.tick() def handle_input(self): for event in pygame.event.get(): if event.type == QUIT: self.exit_program() elif event.type == MOUSEBUTTONDOWN: if self.active_player.is_human() and self.mouse_over_grid(): self.active_player.try_to_place_piece( self.mouse_grid_position()) else: for button in self.buttons.itervalues(): if button.active and button.mouse_is_over(): button.on_click() @property def active_player(self): return self.turn_manager.active_player def mouse_over_grid(self): mx, my = pygame.mouse.get_pos() return my > BOARD_Y_OFFSET def mouse_grid_position(self): mouse_pos = pygame.mouse.get_pos() return self.game_grid.translate_mouse_to_grid_coord(mouse_pos) def exit_game(self): self.game_running = False def exit_program(self): self.game_running = False self.game_manager.exit_game() print 'here' def run_logic(self): self.turn_manager.run() if not self.active_player.is_human( ) and self.turn_manager.game_running: self.active_player.run_logic() def draw_all(self): self.game_board.draw(self.screen) self.highlighter.draw(self.screen) self.logger.draw_scores(self.screen) self.logger.draw_log(self.screen) for button in self.buttons.itervalues(): if button.active: button.draw(self.screen) def update_display(self): pygame.display.update() def request_redraw(self): self.needs_redraw = True def reset_redraw(self): self.needs_redraw = False def tick(self): self.clock.tick(FPS) def black_score(self): return self.game_grid.get_score(BLACK_PIECE) def white_score(self): return self.game_grid.get_score(WHITE_PIECE) def activate_button(self, key): self.buttons[key].set_active() def hide_button(self, key): self.buttons[key].hide() def restart(self): self.game_grid.reset_state() self.turn_manager.reset_state() self.request_redraw() self.hide_button('restart')