def test_is_closed_tail(self): # fmt: off gm = create_map_with([[1, E, 1], [E, E, E], [1, E, E]]) # fmt: on # player without tail player = PlayerState(id=1, name="1", game_map=gm, position=Position(1, 1)) self.assertEqual(player.tail, [Position(1, 1)]) self.assertFalse(GameState.is_closed_tail(player, gm)) # player with tail player.tail = [Position(1, 1), Position(2, 1)] self.assertFalse(GameState.is_closed_tail(player, gm)) player.tail = [Position(1, 1), Position(2, 1), Position(3, 1)] player.direction = Direction(Direction.UP) self.assertTrue(GameState.is_closed_tail(player, gm)) player.tail = [Position(1, 1), Position(2, 1), Position(2, 2)] self.assertFalse(GameState.is_closed_tail(player, gm)) player.tail = [ Position(1, 1), Position(2, 1), Position(2, 2), Position(1, 2), Position(1, 1) ] self.assertTrue(GameState.is_closed_tail(player, gm))
def move_player(self, player: PlayerState, action: Action) -> None: prev_position = player.position.copy() prev_direction = player.direction.copy() player.direction.change_direction(action) player.position.move(player.direction) self.logger.info( f"Player '{player.name_str()}' moving to position: {player.position}." ) player.add_history( self.game_tick, f"Moving {action.value} from {prev_position}-{prev_direction.name()} to {player.position}-{player.direction.name()}.", ) # Append tail if: # - currently on an empty (or other player) tile # - walking on a captured tile and have non captured tiles in the tail if not self.game_map.is_conquered_by( player.position, player.id) or not self.game_map.is_conquered_by( player.tail[-1], player.id): self.logger.debug( f"Player '{player.name_str()}' appending position to tail.") player.tail.append(player.position.copy()) else: player.tail = [player.position.copy()]
def test_fill_planet_to_planet_2(self): # fmt: off game_map = create_map_with([[E, E, E, P1, 1], [E, E, E, 1, P1], [E, E, E, E, E], [E, E, E, E, E], [E, E, E, E, E]]) # fmt: on ps = PlayerState(id=1, name="dummy", game_map=game_map, position=Position(4, 1)) gs = GameState(game_map, [ps]) ps.tail = [ Position(4, 1), Position(3, 1), Position(2, 1), Position(2, 2), Position(2, 3), Position(2, 4), Position(3, 4), Position(4, 4), Position(5, 4), Position(5, 3), Position(5, 2), ] self.assertEqual(len(gs.fill(ps)), 12) # fmt: off self.assertEqual( game_map, create_map_with([[E, 1, 1, P1, 1], [E, 1, 1, 1, P1], [E, 1, 1, 1, 1], [E, 1, 1, 1, 1], [E, E, E, E, E]])) # fmt: on self.assertEqual(ps.score, 12)
def test_fill_island(self): # fmt: off game_map = create_map_with([[1, 1, E, 1], [1, 1, E, 1], [E, E, E, E], [E, E, E, E]]) # fmt: on ps = PlayerState(id=1, name="dummy", game_map=game_map, position=Position(2, 2)) gs = GameState(game_map, [ps]) ps.tail = [ ps.spawn_position, Position(2, 3), Position(3, 3), Position(4, 3), Position(4, 2) ] self.assertEqual(len(gs.fill(ps)), 3) # fmt: off self.assertEqual( game_map, create_map_with([[1, 1, E, 1], [1, 1, E, 1], [E, 1, 1, 1], [E, E, E, E]])) # fmt: on self.assertEqual(ps.score, 3)
def test_fill_kill_other_player_tail(self): # fmt: off game_map = create_map_with([[E, E, E, E], [E, E, E, E], [0, 0, 0, 0], [E, 1, E, E]]) # fmt: on p0 = PlayerState(id=0, name="dummy", game_map=game_map, position=Position(1, 3)) p1 = PlayerState(id=1, name="dummy", game_map=game_map, position=Position(2, 4)) gs = GameState(game_map, [p0, p1]) p0.tail = [ p0.spawn_position.copy(), Position(1, 2), Position(1, 1), Position(2, 1), Position(3, 1), Position(4, 1), Position(4, 2), ] p0.position = p0.tail[-1].copy() p0.direction = Direction(Direction.DOWN) p1.tail = [ p1.spawn_position.copy(), Position(2, 3), Position(2, 2), Position(3, 2), Position(3, 3), Position(3, 4), ] p1.position = p1.tail[-1].copy() p1.direction = Direction(Direction.DOWN) # print(game_state_to_dict(0, 0, gs, 999)['game']['pretty_map']) gs.apply_action(0, p0, Action.FORWARD) # print(game_state_to_dict(0, 0, gs, 999)['game']['pretty_map']) self.assertTrue(p1.killed)
def test_conquer_some_player_tile(self): # fmt: off gm = create_map_with([ [1, 1, E], [1, 2, 2], [E, 2, 2], ]) # fmt: on p1 = PlayerState(id=1, name="p1", game_map=gm, position=Position(1, 1)) p2 = PlayerState(id=2, name="p2", game_map=gm, position=Position(2, 2)) gs = GameState(gm, [p1, p2]) p1.position = Position(2, 2) p1.tail = [Position(1, 2), Position(2, 2)] p2.position = Position(2, 3) p2.tail = [Position(2, 3)] p1.direction = Direction(Direction.RIGHT) gs.apply_action(0, p1, Action.TURN_LEFT) self.assertFalse(p2.killed) self.assertFalse(p1.killed)
def test_respawn_on_someone_tail(self): gm = GameMap(5) p1 = PlayerState(id=1, name="p1", game_map=gm, position=Position(1, 1)) p2 = PlayerState(id=2, name="p2", game_map=gm, position=Position(1, 2)) gs = GameState(gm, [p1, p2]) # p1: spawn position = 1,1, current position=3,1, tail [2,1 and 3,1] # p2: spawn position = 1,2, current position=2,2, tail [1,1, 1,2 and 2,2] p1.position = Position(3, 1) p1.tail = [Position(2, 1), Position(3, 1)] gm.set_tile(Position(1, 1), GameMap.EMPTY, 2) p2.position = Position(2, 2) p2.tail = [Position(1, 1), Position(1, 2), Position(2, 2)] p2.direction = Direction(Direction.UP) gs.apply_action(0, p2, Action.FORWARD) self.assertTrue(p1.killed) self.assertFalse(p2.killed) # respawn gs.respawn_player(p1) self.assertFalse(p1.killed) self.assertTrue(p2.killed)
def test_kill_player(self): # fmt: off gm = create_map_with([[E, E, E], [E, E, E], [1, 1, 1]]) # fmt: on p1 = PlayerState(id=1, name="1", game_map=gm, position=Position(1, 1)) gs = GameState(gm, [p1]) p1.tail = [Position(1, 1), Position(1, 2)] p1.position = p1.tail[-1] gs.kill_player(p1) self.assertEqual(p1.tail, [p1.spawn_position]) self.assertEqual(p1.position, p1.spawn_position) self.assertTrue(p1.killed) self.assertEqual(0, p1.stats.stats[PlayerStats.CONQUERED])
def test_relocate_item(self): # fmt: off gm = create_map_with([[1, E, E], [E, W, E], [E, E, E]]) # fmt: on ps = PlayerState(id=1, name="dummy", game_map=gm, position=Position(1, 1)) gs = GameState(gm, [ps]) ps.position = Position(3, 1) ps.tail = [Position(2, 1), Position(3, 1)] asteroids_pos = Position(2, 2) self.assertTrue(gm.is_asteroids(asteroids_pos)) planet_pos = Position(3, 3) gm.set_tile(planet_pos, GameMap.PLANET) self.assertTrue(gm.is_planet(planet_pos)) black_hole_pos = Position(1, 2) gm.set_tile(black_hole_pos, GameMap.BLACK_HOLE) self.assertTrue(gm.is_black_hole(black_hole_pos)) blitzium_pos = Position(1, 3) gm.set_tile(blitzium_pos, GameMap.BLITZIUM) self.assertTrue(gm.is_blitzium(blitzium_pos)) # Cannot relocate asteroids and planets with self.assertRaises(Exception): gs.relocate_item(asteroids_pos) with self.assertRaises(Exception): gs.relocate_item(planet_pos) # relocate black holes and blitziums for i in range(30): new_black_hole_pos = gs.relocate_item(black_hole_pos) self.assertTrue(new_black_hole_pos not in list( {asteroids_pos, planet_pos, black_hole_pos, blitzium_pos}) + ps.tail) black_hole_pos = new_black_hole_pos new_blitzium_pos = gs.relocate_item(blitzium_pos) self.assertTrue(new_blitzium_pos != blitzium_pos)
def test_fill_with_asteroids(self): # fmt: off game_map = create_map_with([[E, E, E, E, E], [E, 1, E, 1, E], [E, 1, W, 1, E], [E, 1, 1, 1, E], [E, E, E, E, E]]) # fmt: on ps = PlayerState(id=1, name="dummy", game_map=game_map, position=Position(2, 2)) ps.tail = [ps.spawn_position, Position(3, 2), Position(4, 2)] gs = GameState(game_map, [ps]) self.assertEqual(len(gs.fill(ps)), 1) # fmt: off self.assertEqual( game_map, create_map_with([[E, E, E, E, E], [E, 1, 1, 1, E], [E, 1, W, 1, E], [E, 1, 1, 1, E], [E, E, E, E, E]])) # fmt: on self.assertEqual(ps.score, 1)
def dict_to_player_state(data: Dict, game_map: GameMap) -> PlayerState: position = dict_to_position(data["position"]) ps = PlayerState(id=data["id"], name=data["name"], game_map=game_map, position=position, init=False) ps.active = data["active"] ps.killed = data["killed"] ps.position = position ps.spawn_position = dict_to_position(data["spawn_position"]) ps.direction = Direction(data["direction"]) ps.spawn_direction = Direction(data["spawn_direction"]) ps.tail = [dict_to_position(p) for p in data["tail"]] ps.score = data["score"] ps.stats = dict_to_player_stats(data["stats"]) for hist in data["history"]: dt = datetime.fromisoformat(hist["timestamp"]) ps.history.append( HistoryItem(game_tick=hist["tick"], message=hist["message"], ts=dt)) return ps
def fill(self, player: PlayerState) -> Set[Position]: id = player.id # tail have the starting and ending tiles (last tile before going in the empty zone and first captured tile) assert len(player.tail) >= 3 assert self.game_map.is_conquered_by(player.tail[0], id) assert self.game_map.is_conquered_by(player.tail[-1], id) # new captured tiles minus start and end tiles (already captured) new_conquers = set(player.tail[1:-1]) # check if we have a closed loop to fill: find shortest path from start to end of tail while walking only on conquered tiles closed_loop, shortest_path_to_close_tail = self.game_map.find_path( player.tail[0], player.tail[-1], id) if closed_loop: # Conquer the closed shape made by the tail map_size = self.game_map.size flooded_tiles = [[1] * map_size for _ in range(map_size)] for p in player.tail: flooded_tiles[p.y][p.x] = 0 for p in shortest_path_to_close_tail: flooded_tiles[p.y][p.x] = 0 # flooding work by changing the value to 0 for every reachable tiles from the top left of the map (skipping bombs, # walls and other players) The remainder are the tiles that will be flooded. Using the closest path from start # to end of the tail make sure that we are filling only the smallest zone when wrap around and also make sure we # do not re-kill any players walking inside our captured zone. flood(flooded_tiles, target=1, replacement=0) for y in range(map_size): for x in range(map_size): if flooded_tiles[y][x] == 1: p = Position(x, y) # skip already conquered tiles or walls (asteroids) if not self.game_map.is_conquered_by( p, id) and not self.game_map.is_asteroids(p): if self.game_map.is_black_hole(p): # if we happen to surround a black hole, we die instantly self.stepped_on_a_black_hole(p, player) return set() for y in range(map_size): for x in range(map_size): if flooded_tiles[y][x] == 1: p = Position(x, y) # skip already conquered tiles or walls (asteroids) if not self.game_map.is_conquered_by( p, id) and not self.game_map.is_asteroids(p): if self.game_map.is_blitzium(p): # surrounding a coin (blitzium) will give us more points. self.check_if_captured_a_blitzium(p, player) # add tile to new conquered tiles for later scoring new_conquers.add(p) self.game_map.conquer_tile(p, id) # capture the tail (no-op to recapture start and end tiles) for t in player.tail: self.game_map.conquer_tile(t, id) player.tail = [player.position.copy()] # adjust stats nb_conquers = len(new_conquers) self.logger.info( f"Player '{player.name_str()}' is capturing {nb_conquers} new tiles.." ) player.stats.set_stat( PlayerStats.CONQUERED, player.stats.stats[PlayerStats.CONQUERED] + nb_conquers) player.score += GameState.SCORE_NEW_CONQUERED * nb_conquers player.add_history(self.game_tick, f"Conquered {nb_conquers} new tiles.") return new_conquers
def test_game_state_to_json_player(self): gm = create_map_with([[E, E, E], [E, E, E], [E, E, E]]) gm.set_tile(Position(1, 3), GameMap.BLACK_HOLE) gm.set_tile(Position(2, 3), GameMap.BLITZIUM) gm.set_tile(Position(3, 3), GameMap.PLANET) gm.set_tile(Position(3, 2), GameMap.PLANET, 0) p0 = PlayerState(0, "p0", gm, Position(1, 1), direction=Direction(Direction.UP)) p0.killed = True p0.position = Position(3, 1) p0.direction = Direction(Direction.RIGHT) p0.tail = [p0.spawn_position, Position(2, 1), p0.position] p0.score = 123.4 p0.stats.kill_player("p1") p0.stats.killed_by_player("p2") p0.stats.add_stat(PlayerStats.SUICIDES) p0.history.append( HistoryItem(11, "message-11", datetime.datetime(1900, 1, 1, 13, 14, 15, 555))) p0.history.append( HistoryItem(10, "message-10", datetime.datetime(1900, 1, 1, 13, 14, 15, 444))) p1 = PlayerState(1, "p1", gm, position=Position(1, 2), direction=Direction(Direction.DOWN)) p1.active = False p1.direction = Direction(Direction.LEFT) p2 = PlayerState(2, "p2", gm, Position(2, 2), direction=Direction(Direction.RIGHT)) p2.direction = Direction(Direction.LEFT) gs = GameState(gm, [p0, p1, p2]) self.maxDiff = 4000 expected = { "type": "tick", "game": { "map": [ ["W", "W", "W", "W", "W"], ["W", "C-0", " ", " ", "W"], ["W", "C-1", "C-2", "%-0", "W"], ["W", "!", "$", "%", "W"], ["W", "W", "W", "W", "W"], ], "player_id": 1, # fmt: off "pretty_map": '[[W W W W W ]\n' ' [W C0T0 T0 P0 W ]\n' ' [W C1P1 C2P2 %0 W ]\n' ' [W ! $ % W ]\n' ' [W W W W W ]]\n' 'DISCLAMER: this map does not have the same content as ' 'the json game map. Symbols are combined to help you ' 'visualize every spot on this turn. Hyphen are also ' 'removed. See the documentation for the json map ' 'symbol signification.\n' 'Symbols added or modified on this map for ' 'visualization purpose are:\n' 'Px: Position of player x - Cx: Conquered by player x ' '- Tx: Tail of player x', # fmt: on "tick": 123, "ticks_left": 500, }, "players": [ { "active": True, "killed": True, "position": { "x": 3, "y": 1 }, "spawn_position": { "x": 1, "y": 1 }, "direction": "RIGHT", "spawn_direction": "UP", "tail": [{ "x": 1, "y": 1 }, { "x": 2, "y": 1 }, { "x": 3, "y": 1 }], "id": 0, "name": "p0", "score": 123.4, "stats": { PlayerStats.CONQUERED: 1, PlayerStats.KILLS: 1, PlayerStats.KILLED: 1, PlayerStats.SUICIDES: 1, PlayerStats.NEMESIS: "p2", "players_killed": { "p1": 1 }, "killed_by_players": { "p2": 1 }, }, "history": [ { "timestamp": "1900-01-01T13:14:15.000555", "tick": 11, "message": "message-11" }, { "timestamp": "1900-01-01T13:14:15.000444", "tick": 10, "message": "message-10" }, ], }, { "active": False, "killed": False, "position": { "x": 1, "y": 2 }, "spawn_position": { "x": 1, "y": 2 }, "direction": "LEFT", "spawn_direction": "DOWN", "tail": [{ "x": 1, "y": 2 }], "id": 1, "name": "p1", "score": 0, "stats": { PlayerStats.CONQUERED: 1, "players_killed": {}, "killed_by_players": {} }, "history": [], }, { "active": True, "killed": False, "position": { "x": 2, "y": 2 }, "spawn_position": { "x": 2, "y": 2 }, "direction": "LEFT", "spawn_direction": "RIGHT", "tail": [{ "x": 2, "y": 2 }], "id": 2, "name": "p2", "score": 0, "stats": { PlayerStats.CONQUERED: 1, "players_killed": {}, "killed_by_players": {} }, "history": [], }, ], } # players have an id message = game_state_to_json(game_tick=123, player_id=1, game_state=gs, ticks_left=500) # print(message) data = json.loads(message) self.assertEqual(expected, data)