def setUp(self) -> None: self.tiles = [] # these have to be added in the order of bottom to top, left to right in order for get_tile(x, y) to work # so (0, 0), (1, 0), .... (COLUMNS_NUMBER-1, 0), (0, 1), (1, 1), ... # this is a linear map with, from left to right, a column of water, plains, hills, plains, mountains for y in range(5): self.tiles.append(Tile(0, y, 1, 0)) self.tiles.append(Tile(1, y, 1, 1)) self.tiles.append(Tile(2, y, 1, 2)) self.tiles.append(Tile(3, y, 1, 1)) self.tiles.append(Tile(4, y, 1, 3)) self.game_logic = GameLogic(self.tiles, 5, 5, players=[("one", CIV_ONE, "gray"), ("two", CIV_TWO, "red")], my_nick="one") self.game_logic.add_unit(2, 2, "one", 'Settler', 1) self.settler = self.game_logic.get_tile(2, 2).occupant self.area = [] self.area.append(Tile(0, 1, 1, 0)) self.area.append(Tile(1, 1, 1, 0)) self.area.append(Tile(2, 1, 1, 1)) self.area.append(Tile(2, 2, 1, 2)) self.area.append(Tile(0, 0, 1, 2)) self.area.append(Tile(1, 2, 1, 3)) self.game_logic.build_city(self.settler, "test_name") self.tile = self.settler.tile self.city = self.tile.city
def __init__(self, width: int, height: int, tiles: list, client): """ :param width: Max screen width. :param height: Max screen height. :param tiles: A 2D list of integer values representing tile types. :param client: A client object for server communication. """ super().__init__() self.client = client self.my_turn = False self.cur_enemy = "" self.SCREEN_WIDTH = width self.SCREEN_HEIGHT = height self.SCROLL_STEP_X = SCROLL_STEP * width self.SCROLL_STEP_Y = SCROLL_STEP * height self.TILE_ROWS = len(tiles) self.TILE_COLS = len(tiles[0]) self.zoom = 0 self.top_bar = TopBar(None, TOP_BAR_SIZE) self.unit_popup = UnitPopup(4 * TOP_BAR_SIZE, 4 * TOP_BAR_SIZE) self.update_popup = False # used to only update pop-up once per opponent's move, otherwise game is laggy self.update_topbar = False # used to update top bar if my city has been taken self.update_diplo = None # will be updated to a tuple of (message, sender, is_rejectable) self.diplo_answered = threading.Event() self.city_popup = CityCreationPopup(4 * TOP_BAR_SIZE, 5 * TOP_BAR_SIZE) self.diplo_popup = DiplomaticPopup(9 * TOP_BAR_SIZE, 3 * TOP_BAR_SIZE, self.diplo_answered) self.end_popup = EndingPopup(6 * TOP_BAR_SIZE, 6 * TOP_BAR_SIZE) self.ranking = None self.tiles = tiles self.tile_sprites = arcade.SpriteList() # needs to be smarter tbh but depends on the size of a real map self.tile_size = int((height - self.top_bar.height) / self.TILE_ROWS) - MARGIN # in order to center the tiles vertically and horizontally self.centering_x = (width - self.TILE_COLS * (self.tile_size + MARGIN)) / 2 self.centering_y = ((height - self.top_bar.height) - self.TILE_ROWS * (self.tile_size + MARGIN)) / 2 for row in range(self.TILE_ROWS): for col in range(self.TILE_COLS): tile = Tile(col, row, self.tile_size, tiles[row][col]) tile.center_x = col * (self.tile_size + MARGIN) + (self.tile_size / 2) + MARGIN + self.centering_x tile.center_y = row * (self.tile_size + MARGIN) + (self.tile_size / 2) + MARGIN + self.centering_y self.tile_sprites.append(tile) self.game_logic = GameLogic(self.tile_sprites, self.TILE_ROWS, self.TILE_COLS, self.client.players, self.client.nick) self.top_bar.me = self.game_logic.me self.city_view = CityView(self.top_bar) self.enemy_city_view = EnemyCityView(self.top_bar, self.city_view.app, self.client, self.game_logic.me) self.top_bar.update_treasury() threading.Thread(target=self.wait_for_my_turn).start()
class CityAreaTest(unittest.TestCase): def setUp_bigger(self): self.tiles = [] for x in range(20): for y in range(20): self.tiles.append(Tile(x, y, 1, 1)) self.game_logic = GameLogic(self.tiles, 20, 20, players=[("one", CIV_ONE, "gray"), ("two", CIV_TWO, "red")], my_nick="one") self.game_logic.add_unit(10, 10, "one", 'Settler', 1) settler = self.game_logic.get_tile(10, 10).occupant self.game_logic.build_city(settler, "stub") def test_city_resize(self): self.setUp_bigger() x_c, y_c = 10, 10 city = self.game_logic.get_tile(x_c, y_c).city owner = city.owner for r in range(2, 9): self.game_logic.increase_area(x_c, y_c) self.assertEqual(city.current_radius, r) for x in range(x_c - r, x_c + r + 1): for y in range(y_c - r, y_c + r + 1): tile = self.game_logic.get_tile(x, y) if sqrt((x_c - x)**2 + (y_c - y)**2) <= r: self.assertIn(tile, city.area) self.assertEqual(tile.owner, owner) else: self.assertNotIn(tile, city.area) self.assertIsNone(tile.owner)
def setUp(self) -> None: self.tiles = [] # these have to be added in the order of bottom to top, left to right in order for get_tile(x, y) to work # so (0, 0), (1, 0), .... (COLUMNS_NUMBER-1, 0), (0, 1), (1, 1), ... # this is a linear map with, from left to right, a column of water, plains, hills, plains, mountains for y in range(5): self.tiles.append(Tile(0, y, 1, 0)) self.tiles.append(Tile(1, y, 1, 1)) self.tiles.append(Tile(2, y, 1, 2)) self.tiles.append(Tile(3, y, 1, 1)) self.tiles.append(Tile(4, y, 1, 3)) self.game_logic = GameLogic(self.tiles, 5, 5, players=[("one", CIV_ONE, "gray"), ("two", CIV_TWO, "red")], my_nick="one")
def setUp_bigger(self): self.tiles = [] for x in range(20): for y in range(20): self.tiles.append(Tile(x, y, 1, 1)) self.game_logic = GameLogic(self.tiles, 20, 20, players=[("one", CIV_ONE, "gray"), ("two", CIV_TWO, "red")], my_nick="one") self.game_logic.add_unit(10, 10, "one", 'Settler', 1) settler = self.game_logic.get_tile(10, 10).occupant self.game_logic.build_city(settler, "stub")
class BuildingsTest(unittest.TestCase): def setUp(self) -> None: self.tiles = [] # these have to be added in the order of bottom to top, left to right in order for get_tile(x, y) to work # so (0, 0), (1, 0), .... (COLUMNS_NUMBER-1, 0), (0, 1), (1, 1), ... # this is a linear map with, from left to right, a column of water, plains, hills, plains, mountains for y in range(5): self.tiles.append(Tile(0, y, 1, 0)) self.tiles.append(Tile(1, y, 1, 1)) self.tiles.append(Tile(2, y, 1, 2)) self.tiles.append(Tile(3, y, 1, 1)) self.tiles.append(Tile(4, y, 1, 3)) self.game_logic = GameLogic(self.tiles, 5, 5, players=[("one", CIV_ONE, "gray"), ("two", CIV_TWO, "red")], my_nick="one") self.game_logic.add_unit(2, 2, "one", 'Settler', 1) self.settler = self.game_logic.get_tile(2, 2).occupant self.area = [] self.area.append(Tile(0, 1, 1, 0)) self.area.append(Tile(1, 1, 1, 0)) self.area.append(Tile(2, 1, 1, 1)) self.area.append(Tile(2, 2, 1, 2)) self.area.append(Tile(0, 0, 1, 2)) self.area.append(Tile(1, 2, 1, 3)) self.game_logic.build_city(self.settler, "test_name") self.tile = self.settler.tile self.city = self.tile.city def test_mines(self): before_goods = self.city.calculate_goods() self.city.buildings["Mines"] = True after_goods = self.city.calculate_goods() self.assertEqual(after_goods["stone"], before_goods["stone"] + 20) def test_free_market(self): before_goods = self.city.calculate_goods() self.city.buildings["Free Market"] = True after_goods = self.city.calculate_goods() self.assertEqual(after_goods["gold"], before_goods["gold"] + len(self.city.area) * 3) def test_free_market_2(self): self.city.area.append(Tile(0, 0, 1, 0)) before_goods = self.city.calculate_goods() self.city.buildings["Free Market"] = True after_goods = self.city.calculate_goods() self.assertEqual(after_goods["gold"], before_goods["gold"] + len(self.city.area) * 3) def test_astronomic_tower(self): self.game_logic.increase_area(*self.tile.coords) self.assertEqual(len(self.city.area), 13) self.game_logic.increase_area(*self.tile.coords) self.assertEqual(len(self.city.area), 25)
class MovementTests(unittest.TestCase): def setUp(self) -> None: self.tiles = [] # these have to be added in the order of bottom to top, left to right in order for get_tile(x, y) to work # so (0, 0), (1, 0), .... (COLUMNS_NUMBER-1, 0), (0, 1), (1, 1), ... # this is a linear map with, from left to right, a column of water, plains, hills, plains, mountains for y in range(5): self.tiles.append(Tile(0, y, 1, 0)) self.tiles.append(Tile(1, y, 1, 1)) self.tiles.append(Tile(2, y, 1, 2)) self.tiles.append(Tile(3, y, 1, 1)) self.tiles.append(Tile(4, y, 1, 3)) self.game_logic = GameLogic(self.tiles, 5, 5, players=[("one", CIV_ONE, "gray"), ("two", CIV_TWO, "red")], my_nick="one") self.game_logic.add_unit(2, 2, "one", 'Settler', 1) self.settler = self.game_logic.get_tile(2, 2).occupant self.area = [] self.area.append(Tile(0, 1, 1, 0)) self.area.append(Tile(1, 1, 1, 0)) self.area.append(Tile(2, 1, 1, 1)) self.area.append(Tile(2, 2, 1, 2)) self.area.append(Tile(0, 0, 1, 2)) self.area.append(Tile(1, 2, 1, 3)) def test_city_building(self): # a city is created # the settler should be deleted and the tile unoccupied # the city should be owned by the owner of the settler # and should have a proper name and tile set player = self.settler.owner self.game_logic.build_city(self.settler, "test_name") tile = self.settler.tile self.assertNotIn(self.settler, player.units) self.assertFalse(tile.occupied()) city = tile.city self.assertIsNotNone(city) self.assertEqual(city.owner, player) self.assertEqual(city.name, "test_name") def test_city_area(self): # a city is created # if all tiles around it are not owned, it gets a 3x3 area where the middle point is the city self.game_logic.build_city(self.settler, "test_name") player = self.settler.owner tile = self.settler.tile city = tile.city expected_area = {(x, y) for x in range(1, 4) for y in range(1, 4)} real_area = {t.coords for t in city.area} self.assertEqual(expected_area, real_area) for x, y in expected_area: self.assertEqual(self.game_logic.get_tile(x, y).owner, player) def test_two_cities_area(self): # two cities are created, and the second one is so close that the 3x3 square around it contains # the territory of the first one # we'll make sure that this territory won't be taken by the new city self.game_logic.add_unit(2, 0, "two", 'Settler', 1) self.game_logic.build_city(self.settler, "one_city") self.game_logic.build_opponents_city(2, 0, "two_city") expected_area = {(1, 0), (2, 0), (3, 0)} city = self.game_logic.get_tile(2, 0).city player = city.owner real_area = {t.coords for t in city.area} self.assertEqual(expected_area, real_area) for x, y in expected_area: self.assertEqual(self.game_logic.get_tile(x, y).owner, player) def test_simple_border(self): # a city is created # since the basic area for a city is a 3x3 square, it's borders are all it's tiles except the middle point self.game_logic.build_city(self.settler, "one_city") expected_border = {(x, y) for x in range(1, 4) for y in range(1, 4) if not x == y == 2} player = self.settler.owner real_border = {border.tile.coords for border in player.borders} self.assertEqual(expected_border, real_border) def test_separated_border(self): # two cities whose borders don't touch # and they are on screen edges, so their areas are 2x3, which means all their tiles should be borders self.game_logic.add_unit(1, 0, "one", 'Settler', 1) self.game_logic.build_opponents_city(1, 0, "city1") self.game_logic.add_unit(3, 4, "one", 'Settler', 1) self.game_logic.build_opponents_city(3, 4, "city2") city1 = self.game_logic.get_tile(1, 0).city city2 = self.game_logic.get_tile(3, 4).city player = city1.owner expected_border = { tile.coords for city in [city1, city2] for tile in city.area } real_border = {border.tile.coords for border in player.borders} self.assertEqual(expected_border, real_border) def test_border_update(self): # we will create two cities so that their borders connect # some tiles should stop being borders then player = self.settler.owner self.game_logic.build_city(self.settler, "city1") self.assertIn((2, 1), [border.tile.coords for border in player.borders]) # another city that's very close so the borders touch self.game_logic.add_unit(2, 0, "one", 'Settler', 1) self.game_logic.build_opponents_city(2, 0, "city2") expected_border = {(x, y) for x in range(1, 4) for y in range(0, 4) if not (x == 2 and 0 < y < 3)} # no (2, 1)! real_border = {border.tile.coords for border in player.borders} self.assertEqual(expected_border, real_border) def test_calculating_goods_no_city(self): # just check based on known output self.assertEqual({ 'gold': 2, 'wood': 25, 'stone': 5, 'food': 27 }, City.calculate_goods_no_city(self.area)) def test_calculating_goods_based_on_city(self): # building city and calculating based on his area. self.game_logic.build_city(self.settler, "test_name") city = self.settler.tile.city assert city is not None city.set_area(self.area) self.assertEqual({ 'gold': 2, 'wood': 25, 'stone': 5, 'food': 27 }, city.calculate_goods()) def test_city_capture(self): # player two's forces have made it to the players one's stub_city and are ready to capture it self.setUp() x, y = self.settler.tile.coords player1 = self.settler.owner player2 = self.game_logic.players['two'] self.game_logic.build_city(self.settler, "stub_city") city = player1.cities[0] self.assertNotIn(city, player2.cities) self.game_logic.give_opponents_city(x, y, 'two') self.assertNotIn(city, player1.cities) self.assertIn(city, player2.cities) for t in city.area: self.assertEqual(t.owner, player2) # fortunately, it was just player one's sneaky trap and he immediately recovered his city self.game_logic.give_opponents_city(x, y, 'one') for t in city.area: self.assertEqual(t.owner, player1)
class MyTestCase(unittest.TestCase): def setUp(self) -> None: self.tiles = [] # these have to be added in the order of bottom to top, left to right in order for get_tile(x, y) to work # so (0, 0), (1, 0), .... (COLUMNS_NUMBER-1, 0), (0, 1), (1, 1), ... # this is a linear map with, from left to right, a column of water, plains, hills, plains, mountains for y in range(5): self.tiles.append(Tile(0, y, 1, 0)) self.tiles.append(Tile(1, y, 1, 1)) self.tiles.append(Tile(2, y, 1, 2)) self.tiles.append(Tile(3, y, 1, 1)) self.tiles.append(Tile(4, y, 1, 3)) self.game_logic = GameLogic(self.tiles, 5, 5, players=[("one", CIV_ONE, "gray"), ("two", CIV_TWO, "red")], my_nick="one") def test_only_fight(self): # test only method invoking combat between military units self.game_logic.add_unit(2, 2, 'one', 'Poor Infantry', 10) self.game_logic.add_unit(2, 3, 'two', 'Archers', 2) u1 = self.game_logic.get_tile(2, 2).occupant u2 = self.game_logic.get_tile(2, 3).occupant winner = u1.attack(u2, seed=10) self.assertEqual(winner, u1) self.assertEqual(winner.health, 130) self.assertLessEqual(u2.health, 0) def test_move_and_fight(self): # test combat resulting from one unit moving to the place occupied by another self.setUp() self.game_logic.add_unit(2, 2, 'one', 'Poor Infantry', 10) self.game_logic.add_unit(2, 3, 'two', 'Archers', 2) loser = self.game_logic.players['two'] u1 = self.game_logic.get_tile(2, 2).occupant u2 = self.game_logic.get_tile(2, 3).occupant self.assertIn(u2, loser.units) self.game_logic.move_unit(u1, 2, 3, 0) self.assertNotIn(u2, loser.units) def test_slaughter_helpless_settlers(self): # test combat against non military units self.setUp() self.game_logic.add_unit(2, 2, 'one', 'Poor Infantry', 10) self.game_logic.add_unit(2, 3, 'two', 'Settler', 1) loser = self.game_logic.players['two'] u1 = self.game_logic.get_tile(2, 2).occupant u2 = self.game_logic.get_tile(2, 3).occupant self.assertIn(u2, loser.units) self.game_logic.move_unit(u1, 2, 3, 0) self.assertNotIn(u2, loser.units)
class MovementTests(unittest.TestCase): def setUp(self) -> None: self.tiles = [] # these have to be added in the order of bottom to top, left to right in order for get_tile(x, y) to work # so (0, 0), (1, 0), .... (COLUMNS_NUMBER-1, 0), (0, 1), (1, 1), ... # this is a linear map with, from left to right, a column of water, plains, hills, plains, mountains for y in range(5): self.tiles.append(Tile(0, y, 1, 0)) self.tiles.append(Tile(1, y, 1, 1)) self.tiles.append(Tile(2, y, 1, 2)) self.tiles.append(Tile(3, y, 1, 1)) self.tiles.append(Tile(4, y, 1, 3)) self.game_logic = GameLogic(self.tiles, 5, 5, players=[("one", CIV_ONE, "gray"), ("two", CIV_TWO, "red")], my_nick="one") self.game_logic.add_unit(2, 2, "one", 'Poor Infantry', 1) self.unit = self.game_logic.get_tile(2, 2).occupant self.unit.max_movement = 2 self.unit.reset_movement() def test_cost_assignment(self): # Tile(x, y, n) should have a cost of n if 0 < n < 3, else inf # this means that water and mountains have a cost of inf # plains cost 1 and hills cost 2 to move to them # let's check if these costs have been correctly assigned for y in range(5): self.assertEqual(self.game_logic.get_tile(0, y).cost, inf) self.assertEqual(self.game_logic.get_tile(1, y).cost, 1) self.assertEqual(self.game_logic.get_tile(2, y).cost, 2) self.assertEqual(self.game_logic.get_tile(3, y).cost, 1) self.assertEqual(self.game_logic.get_tile(4, y).cost, inf) def test_unit_placement(self): # a unit has been placed on tile (2, 2) in the setup # let's check if it is set as the occupant tile = self.game_logic.get_tile(2, 2) self.assertEqual(tile.occupant, self.unit) # and it has the correct tile assigned self.assertEqual(self.unit.tile, tile) def test_movement_range(self): # the unit is standing on the central tile # the map, labeling each tile with its individual cost, looks like this # inf 1 2 1 inf # inf 1 2 1 inf # inf 1 * 1 inf # inf 1 2 1 inf # inf 1 2 1 inf # we have 2 movement points to spare # the unit should be able to move one tile up and down at cost 2 (hill cost) # and one tile left and right at cost 1 (plains cost) # and one tile diagonally at cost 2 (1 to move left/right + 1 to move up/down because these are plains too) # and nowhere else expected_range = {(x, y): 2 for x in range(1, 4) for y in range(1, 4)} expected_range[2, 2] = 0 # also it's free to stay where it is expected_range[1, 2] = expected_range[3, 2] = 1 unit_range = self.game_logic.get_unit_range(self.unit) self.assertEqual(unit_range, expected_range) def test_simple_move(self): # we move one up unit_range = self.game_logic.get_unit_range(self.unit) self.game_logic.move_unit(self.unit, 2, 3, unit_range[2, 3]) # the unit should have the cost subtracted from its movement points self.assertEqual(self.unit.movement, self.unit.max_movement - unit_range[2, 3]) # the previous tile should be unoccupied self.assertFalse(self.game_logic.get_tile(2, 2).occupied()) # and the occupant of the new tile should be out unit # and the unit should have this tile assigned new_tile = self.game_logic.get_tile(2, 3) self.assertEqual(new_tile.occupant, self.unit) self.assertEqual(self.unit.tile, new_tile) def test_sequential_move(self): # let's assume we move one tile left # then we should be able to move again - one tile up or down, and nowhere else unit_range = self.game_logic.get_unit_range(self.unit) self.game_logic.move_unit(self.unit, 1, 2, unit_range[1, 2]) new_range = self.game_logic.get_unit_range(self.unit) self.assertEqual(len(new_range), 3) # up, down, or stay in place self.assertEqual(self.unit.movement, 1) # let's move one down now self.assertIn((1, 1), new_range) self.assertEqual(new_range[1, 1], 1) self.game_logic.move_unit(self.unit, 1, 1, new_range[1, 1]) self.assertEqual(self.unit.movement, 0) new_tile = self.game_logic.get_tile(1, 1) self.assertEqual(new_tile.occupant, self.unit) self.assertEqual(self.unit.tile, new_tile)
class GameView(arcade.View): """ The view of the map of the game. """ def __init__(self, width: int, height: int, tiles: list, client): """ :param width: Max screen width. :param height: Max screen height. :param tiles: A 2D list of integer values representing tile types. :param client: A client object for server communication. """ super().__init__() self.client = client self.my_turn = False self.cur_enemy = "" self.SCREEN_WIDTH = width self.SCREEN_HEIGHT = height self.SCROLL_STEP_X = SCROLL_STEP * width self.SCROLL_STEP_Y = SCROLL_STEP * height self.TILE_ROWS = len(tiles) self.TILE_COLS = len(tiles[0]) self.zoom = 0 self.top_bar = TopBar(None, TOP_BAR_SIZE) self.unit_popup = UnitPopup(4 * TOP_BAR_SIZE, 4 * TOP_BAR_SIZE) self.update_popup = False # used to only update pop-up once per opponent's move, otherwise game is laggy self.update_topbar = False # used to update top bar if my city has been taken self.update_diplo = None # will be updated to a tuple of (message, sender, is_rejectable) self.diplo_answered = threading.Event() self.city_popup = CityCreationPopup(4 * TOP_BAR_SIZE, 5 * TOP_BAR_SIZE) self.diplo_popup = DiplomaticPopup(9 * TOP_BAR_SIZE, 3 * TOP_BAR_SIZE, self.diplo_answered) self.end_popup = EndingPopup(6 * TOP_BAR_SIZE, 6 * TOP_BAR_SIZE) self.ranking = None self.tiles = tiles self.tile_sprites = arcade.SpriteList() # needs to be smarter tbh but depends on the size of a real map self.tile_size = int((height - self.top_bar.height) / self.TILE_ROWS) - MARGIN # in order to center the tiles vertically and horizontally self.centering_x = (width - self.TILE_COLS * (self.tile_size + MARGIN)) / 2 self.centering_y = ((height - self.top_bar.height) - self.TILE_ROWS * (self.tile_size + MARGIN)) / 2 for row in range(self.TILE_ROWS): for col in range(self.TILE_COLS): tile = Tile(col, row, self.tile_size, tiles[row][col]) tile.center_x = col * (self.tile_size + MARGIN) + (self.tile_size / 2) + MARGIN + self.centering_x tile.center_y = row * (self.tile_size + MARGIN) + (self.tile_size / 2) + MARGIN + self.centering_y self.tile_sprites.append(tile) self.game_logic = GameLogic(self.tile_sprites, self.TILE_ROWS, self.TILE_COLS, self.client.players, self.client.nick) self.top_bar.me = self.game_logic.me self.city_view = CityView(self.top_bar) self.enemy_city_view = EnemyCityView(self.top_bar, self.city_view.app, self.client, self.game_logic.me) self.top_bar.update_treasury() threading.Thread(target=self.wait_for_my_turn).start() def relative_to_absolute(self, x: float, y: float): """ Converts relative coordinates to absolute ones. Coordinates provided by mouse events are relative to the current zoom, so for some uses (like determining what tile has been clicked) they need to be scaled and shifted. """ current = arcade.get_viewport() real_y = y * (current[3] - current[2]) / self.SCREEN_HEIGHT + current[2] - self.centering_y real_x = x * (current[1] - current[0]) / self.SCREEN_WIDTH + current[0] - self.centering_x return real_x, real_y def absolute_to_tiles(self, x: float, y: float): """ Converts absolute coordinates to map matrix indices of the tile they lay on. """ return map(lambda a: int(a // (self.tile_size + MARGIN)), (x, y)) def get_tile(self, x, y): return self.tile_sprites[y * self.TILE_COLS + x] def on_show(self): arcade.set_background_color(arcade.color.BLACK) self.top_bar.adjust() self.unit_popup.adjust() def on_update(self, delta_time: float): self.game_logic.update() if self.update_popup: self.unit_popup.update() self.update_popup = False if self.update_topbar: self.top_bar.update_treasury() self.update_topbar = False if self.update_diplo: self.diplo_popup.display(*self.update_diplo) self.update_diplo = None if self.ranking: self.top_bar.game_ended() self.end_popup.display(self.ranking) self.ranking = None def on_draw(self): self.top_bar.turn_change(self.cur_enemy) arcade.start_render() # units, cities, move ranges self.game_logic.draw() # top bar self.top_bar.draw_background() # unit popup self.unit_popup.draw_background() self.city_popup.draw_background() self.diplo_popup.draw_background() self.end_popup.draw_background() def on_mouse_scroll(self, x, y, scroll_x, scroll_y): if self.city_popup.visible() or self.diplo_popup.visible() or self.end_popup.visible(): return if 0 <= self.zoom + scroll_y < MAX_ZOOM: self.zoom += scroll_y current = arcade.get_viewport() new_width = (current[1] - current[0]) - 2 * scroll_y * self.SCROLL_STEP_X new_height = (current[3] - current[2]) - 2 * scroll_y * self.SCROLL_STEP_Y # we need to check if zooming will cross the borders of the map, if so - snap them back x, y = self.relative_to_absolute(x, y) new_left = x - new_width / 2 if new_left > 0: x_shift = self.SCREEN_WIDTH - (new_left + new_width) if x_shift < 0: new_left += x_shift else: new_left = 0 new_bottom = y - new_height / 2 if new_bottom > 0: # now, the size of the top bar changes and the zoom has to adjust # this means that in no zoom we can move closer to camera_top = screen_height than if we zoom in # and that's because the height of the top bar in pixels is the largest when zoomed out # actually, in no zoom we can move all the way up there # so the max camera_top for when zoomed in n times is: # screen_height - (max_top_bar_height - top_bar_height_after_zoom_change) y_shift = (self.SCREEN_HEIGHT - (self.zoom * 2 * self.SCROLL_STEP_Y) * self.top_bar.size_y) - ( new_bottom + new_height) if y_shift < 0: new_bottom += y_shift else: new_bottom = 0 arcade.set_viewport(new_left, new_left + new_width, new_bottom, new_bottom + new_height) self.top_bar.adjust() self.unit_popup.adjust() def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers): if self.city_popup.visible() or self.diplo_popup.visible() or self.end_popup.visible(): return if buttons == 4: current = arcade.get_viewport() # slow the movement down a lil bit dx /= 4 dy /= 4 if current[0] - dx < 0 or current[1] - dx > self.SCREEN_WIDTH: dx = 0 # max_top = zoomed out map height + current top bar height, # cause we want to be able to see the edge of the map max_top = self.SCREEN_HEIGHT - self.top_bar.max_height + self.top_bar.height if current[2] - dy < 0 or current[3] - dy > max_top: dy = 0 arcade.set_viewport(current[0] - dx, current[1] - dx, current[2] - dy, current[3] - dy) self.top_bar.adjust() self.unit_popup.adjust() def on_mouse_press(self, x, y, button, modifiers): if self.city_popup.visible() or self.diplo_popup.visible() or self.end_popup.visible(): return if button == 1: # don't let the player click through the unit pop-up or the top bar if self.unit_popup.is_hit(x, y) or self.top_bar.is_hit(x, y): pass else: # the x and y arguments are relative to the current zoom, so we need to scale and shift them x, y = self.relative_to_absolute(x, y) # aaand then turn them into grid coords tile_col, tile_row = self.absolute_to_tiles(x, y) if tile_col < self.TILE_COLS and tile_row < self.TILE_ROWS: # sprite list is 1d so we need to turn coords into a single index tile = self.tile_sprites[tile_row * self.TILE_COLS + tile_col] if self.unit_popup.visible(): unit = self.unit_popup.unit cost = self.game_logic.can_unit_move(unit, tile_col, tile_row) if self.my_turn and cost: # if it's my turn and my unit and i clicked within its range, move it old_tile = unit.tile participants = self.game_logic.move_unit(unit, tile_col, tile_row, cost) if len(participants) > 1: # if there was some combat, update healths for participant, (x, y) in participants: messages = self.client.update_health(x, y, participant.health) self.handle_additional_messages(messages) if unit.health > 0: # if my unit survived, move it messages = self.client.move_unit(*old_tile.coords, tile_col, tile_row, cost) self.handle_additional_messages(messages) self.unit_popup.update() city = unit.tile.city if city and city.owner != self.game_logic.me: opponent = city.owner # if i moved to a city that's wasn't mine, it's mine now uwu self.game_logic.give_city(city, self.game_logic.me) messages = self.client.get_city(city) self.handle_additional_messages(messages) self.top_bar.update_treasury() # if the city was the user's last, they're defeated if len(opponent.cities) == 0: messages = self.client.kill(opponent.nick) self.handle_additional_messages(messages) # if that was the last opponent, the game is over if len(self.game_logic.players) - len( self.game_logic.disconnected_players) == 1: self.game_logic.hide_unit_range() self.unit_popup.hide() messages = self.client.end_game_by_host() self.handle_additional_messages(messages) else: # i died :( self.game_logic.hide_unit_range() self.unit_popup.hide() else: # otherwise it means that i "unclicked" the popup self.unit_popup.hide() self.game_logic.hide_unit_range() elif tile.occupied(): unit = tile.occupant # hide the range cause we might have clicked another unit while it was displayed self.game_logic.hide_unit_range() if self.my_turn and self.game_logic.is_unit_mine(unit): # show me where can i move it self.unit_popup.display(unit, mine=True) self.game_logic.display_unit_range(unit) else: # just show the info self.unit_popup.display(unit, mine=False) elif self.my_turn: if tile.city: print("inside") if tile.city.owner == self.game_logic.me: self.city_view.set_city(tile.city) self.window.show_view(self.city_view) else: self.enemy_city_view.set_city(tile.city) self.window.show_view(self.enemy_city_view) # some cheats, TODO make them activate by typing 'AEZAKMI' else: if modifiers & arcade.key.MOD_SHIFT: # shift + click creates a settler on the tile self.game_logic.add_unit(tile_col, tile_row, self.client.nick, 'Settler', 1) messages = self.client.add_unit(tile_col, tile_row, "Settler", 1) else: # regular click creates some garrison (see GameLogic.add_unit) self.game_logic.add_unit(tile_col, tile_row, self.client.nick, 'Cavalry', 10) messages = self.client.add_unit(tile_col, tile_row, "Cavalry", 10) self.handle_additional_messages(messages) def on_key_press(self, symbol, modifiers): if self.my_turn: if self.city_popup.visible(): # ACCEPTING OR DECLINING TO BUILD A CITY if symbol == arcade.key.ESCAPE: self.city_popup.hide() elif symbol == arcade.key.ENTER: city_name = self.city_popup.hide() unit = self.unit_popup.unit messages = self.client.add_city(*unit.tile.coords, city_name) self.handle_additional_messages(messages) self.game_logic.build_city(self.unit_popup.unit, city_name) self.top_bar.update_treasury() self.unit_popup.hide() self.game_logic.hide_unit_range() elif self.diplo_popup.visible(): pass else: # END TURN if symbol == arcade.key.SPACE: self.my_turn = False self.unit_popup.hide() self.game_logic.end_turn() units = self.game_logic.get_deployed_units() for u in units: x, y = u.tile.coords messages = self.client.add_unit(x, y, u.type, u.count) self.handle_additional_messages(messages) self.game_logic.update_building() cities = self.game_logic.get_enhanced_cities() for c in cities: messages = self.client.enhance_city_area(c[0], c[1]) self.handle_additional_messages(messages) for city in self.game_logic.me.cities: city.goods = city.calculate_goods() self.top_bar.update_treasury() for player in self.game_logic.players.values(): # forgetting that message was sent player.deputation = None messages = self.client.end_turn() self.handle_additional_messages(messages) threading.Thread(target=self.wait_for_my_turn, daemon=True).start() # BUILD A CITY elif symbol == arcade.key.N and self.unit_popup.can_build_city(): unit = self.unit_popup.unit if self.game_logic.is_unit_mine(unit): area = self.game_logic.get_city_area(unit) stats = City.calculate_goods_no_city(area) self.city_popup.display(unit, stats) # TODO DELETE THIS # END GAME EXAMPLE elif symbol == arcade.key.P: messages = self.client.end_game_by_host() self.handle_additional_messages(messages) # DEFEAT PLAYER EXAMPLE elif symbol == arcade.key.D and self.unit_popup.visible(): player_to_kill = self.unit_popup.unit.owner if player_to_kill != self.game_logic.me: messages = self.client.kill(player_to_kill.nick) self.handle_additional_messages(messages) def handle_additional_messages(self, messages): """ A method that allows players to disconnect anytime, sort of a "panic mode". Has to be called everytime a request is sent to the server! Reads and handles all messages that the client didn't expect at this particular moment (for now just disconnecting). For instance, a client sends a request to add a unit to the map, and they expect to receive that same message as confirmation. Before they sent their request, another player has disconnected. This method will remove the disconnected player and then allow the game to go back to normal. :param messages: a generator of unexpected messages (see Client.unexpected_messages) """ ranking = [] for mes in messages: print(mes) if mes[0] == "DISCONNECT" or mes[0] == "DEFEAT": nick = mes[1] self.game_logic.disconnected_players.append(nick) if self.unit_popup.visible() and self.unit_popup.unit.owner.nick == nick: self.unit_popup.hide() elif mes[0] == "RANK": ranking.append((mes[1], int(mes[2]))) elif mes[0] == "END_GAME": self.my_turn = False self.ranking = ranking def wait_for_my_turn(self): """ A prototype function for handling server messages about other players' actions. Will probably be renamed and maybe split later on. """ ranking = [] while True: self.diplo_answered.clear() message = self.client.get_opponents_move() print(message) if message[0] == "TURN": if message[1] == self.client.nick: self.my_turn = True self.cur_enemy = "" return else: self.cur_enemy = message[1] self.game_logic.reset_movement(self.cur_enemy) elif message[0] == "ADD_UNIT": nick = message[1] x, y = eval(message[2]) unit_type = message[3] count = int(message[4]) self.game_logic.add_unit(x, y, nick, unit_type, count) elif message[0] == "MOVE_UNIT": x0, y0 = eval(message[1]) x1, y1 = eval(message[2]) cost = int(message[3]) self.game_logic.move_opponents_unit(x0, y0, x1, y1, cost) self.update_popup = True elif message[0] == "HEALTH": x, y = eval(message[1]) health = int(message[2]) if health <= 0: self.game_logic.kill_opponents_unit(x, y) self.unit_popup.hide_if_on_tile(x, y) else: tile = self.game_logic.get_tile(x, y) if tile: tile.occupant.health = health elif message[0] == "ADD_CITY": nick = message[1] x, y = eval(message[2]) city_name = message[3] self.game_logic.build_opponents_city(x, y, city_name) self.unit_popup.hide_if_on_tile(x, y) elif message[0] == "GIVE_CITY": x, y = eval(message[1]) recipient = message[2] self.game_logic.give_opponents_city(x, y, recipient) """ "Niech tak będzie" ~ PM """ self.update_topbar = True elif message[0] == "MORE_AREA": x, y = eval(message[1]) self.game_logic.increase_area(x, y) elif message[0] == "DIPLOMACY_ANSWER": involved = False sender_is_me = False other = None new_message = message.copy() if message[2] == self.client.nick: involved = sender_is_me = True other = message[3] new_message[2] = "You" elif message[3] == self.client.nick: involved = True other = message[2] new_message[3] = "you" if involved: if message[1] == "DECLARE_WAR": self.game_logic.me.enemies.append(self.game_logic.players[other]) elif message[1] == "ALLIANCE" and eval(message[-1]): self.game_logic.me.allies.append(self.game_logic.players[other]) elif message[1] == "END_ALLIANCE": self.game_logic.me.allies.remove(self.game_logic.players[other]) elif message[1] == "TRUCE" and eval(message[-1]): self.game_logic.me.enemies.remove(self.game_logic.players[other]) elif message[1] == "BUY_CITY": x, y = eval(message[4]) new_message[4] = self.game_logic.get_tile(x, y).city.name if eval(message[-1]): self.game_logic.give_opponents_city(x, y, message[3]) elif message[3] == self.client.nick: self.game_logic.me.granary.change_resource('gold', int(message[5])) self.update_topbar = True elif message[1] == "BUY_RESOURCE": if message[3] == self.client.nick: self.game_logic.me.granary.change_resource('gold', int(message[5])) self.game_logic.me.granary.change_resource(message[4], int(message[6])) self.update_topbar = True self.update_diplo = new_message, sender_is_me self.diplo_answered.wait() else: if message[1] == "BUY_CITY" and eval(message[-1]): x, y = eval(message[4]) new_message[4] = self.game_logic.get_tile(x, y).city.name self.game_logic.give_opponents_city(*eval(message[4]), message[3]) if message[1] in ["DECLARE_WAR", "END_ALLIANCE"] or message[1] != "BUY_RESOURCE" and eval(message[-1]): self.update_diplo = new_message, sender_is_me self.diplo_answered.wait() elif message[0] == "DIPLOMACY": if message[3] == self.client.nick: if message[1] == "BUY_RESOURCE": resource = message[4] price = float(message[5]) quantity = int(message[6]) max_price = ceil(price * quantity) in_storage = self.game_logic.me.granary.get_resource(resource) actual_quantity = min(quantity, in_storage) actual_price = ceil(price * actual_quantity) change = max_price - actual_price new_message = message.copy() new_message[5] = actual_price new_message[6] = actual_quantity self.update_diplo = new_message, False self.diplo_answered.wait() response = self.diplo_popup.accepted if response: self.game_logic.me.granary.change_resource('gold', actual_price) self.game_logic.me.granary.change_resource(resource, -actual_quantity) self.update_topbar = True self.client.only_send( f"DIPLOMACY_ANSWER:{message[1]}:{message[3]}:{message[2]}:{message[4]}:{change if response else max_price}:{actual_quantity if response else 0}:{response}") elif message[1] == "BUY_CITY": x, y = eval(message[4]) city_name = self.game_logic.get_tile(x, y).city.name price = int(message[5]) new_message = message.copy() new_message[4] = city_name self.update_diplo = new_message, False self.diplo_answered.wait() response = self.diplo_popup.accepted if response: self.game_logic.me.granary.change_resource('gold', price) self.update_topbar = True self.client.only_send( f"DIPLOMACY_ANSWER:{message[1]}:{message[3]}:{message[2]}:" + ":".join( message[4:]) + f":{response}") else: self.update_diplo = message, False self.diplo_answered.wait() response = self.diplo_popup.accepted self.client.only_send(f"DIPLOMACY_ANSWER:{message[1]}:{message[3]}:{message[2]}:{response}") elif message[0] == "DISCONNECT" or message[0] == "DEFEAT": nick = message[1] self.game_logic.disconnected_players.append(nick) if self.unit_popup.visible() and self.unit_popup.unit.owner.nick == nick: self.unit_popup.hide() elif message[0] == "RANK": ranking.append((message[1], int(message[2]))) elif message[0] == "END_GAME": self.ranking = ranking return elif message[0] == "DISCONNECTED": return