diff --git a/server/hra/game.py b/server/hra/game.py index aa9c26e..08719fb 100644 --- a/server/hra/game.py +++ b/server/hra/game.py @@ -26,6 +26,235 @@ class Logic: @add_logic class Occupy(Logic): - pass + MOVE_VECTORS = { + None: (0, 0), + "left": (0, -1), + "right": (0, 1), + "up": (-1, 0), + "down": (1, 0) + } + def __init__(self, teams_count: int, configuration: Any): + super().__init__(teams_count, configuration) + self.teams_width = configuration["teams_width"] + self.teams_height = configuration["teams_height"] + assert(teams_count == self.teams_width * self.teams_height) + self.map_width = self.teams_width * configuration["width_per_team"] + self.map_height = self.teams_height * configuration["height_per_team"] + + def generate_soldier(self, team_id, soldier_id): + return { + "type": "soldier", + "team": team_id, + "id": soldier_id, + "remaining_rounds": 1 + } + + # Počáteční stav + def zero_state(self) -> Any: + # TODO: Zatím negeneruju "hills" + map = [ + [ + { + "home_for_team": None, + "occupied_by_team": None, + "hill": False, + "members": [] + } + for _ in range(self.map_width) + ] + for _ in range(self.map_height) + ] + team_id = 0 + home_y = self.configuration["height_per_team"] // 2 + for _ in range(self.teams_height): + home_x = self.configuration["width_per_team"] // 2 + for _ in range(self.teams_width): + map[home_y][home_x]["home_for_team"] = team_id + map[home_y][home_x]["occupied_by_team"] = team_id + map[home_y][home_x]["members"].append(self.generate_soldier(team_id, 0)) + team_id += 1 + home_x += self.configuration["width_per_team"] + home_y += self.configuration["height_per_team"] + + return {"map": map} + + # Zatím očekávám, že moves má takovéto prvky: + # { + # team_id: + # members: [ + # id: + # action: + # # None -> čekej + # # "left", "right", "up", "down" + # ] + # } + # VALIDITU MOVES NEOVĚŘUJU + # Metoda funguje velmi primitivně po krocích: + # * zaznamená se poloha objektů na mapě + # * vojáky se pohne podle moves + # * přidají se vojáci v homes + # * odstraní se vojáci podle "soubojů" + # * vygeneruje se json se správnými hodnotami "occupied" a spočítá se skóre + def step(self, state: Any, moves: List[Optional[Any]], round_id: int) -> Tuple[Any, List[int]]: + # souřadnice IDs pro všechny týmy + id_positions = [{} for _ in range(self.teams_count)] + # pohyby IDs pro všechny týmy + id_moves = [{} for _ in range(self.teams_count)] + # všechna IDs týmů + all_ids = [[] for _ in range(self.teams_count)] + # nejvyšší ID každého týmu + highest_ids = [0] * self.teams_count + # homes jednotlivých týmů + home_positions = [None] * self.teams_count + + y = 0 + for row in state["map"]: + x = 0 + for field in row: + if field["home_for_team"] is not None: + home_positions[field["home_for_team"]] = [y, x] + for member in field["members"]: + highest_ids[member["team"]] = max(highest_ids[member["team"]], member["id"]) + all_ids[member["team"]].append(member["id"]) + id_positions[member["team"]][member["id"]] = [y, x] + id_moves[member["team"]][member["id"]] = None + x += 1 + y += 1 + + for move in moves: + team_id = move["team_id"] + for member in move["members"]: + id_moves[team_id][member["id"]] = member["action"] + + # Místo mapy tabulka seznamů vojáků pro každý tým + soldier_lists = [ + [[[] for _ in range(self.teams_count)] for _ in range(self.map_width)] + for _ in range(self.map_height) + ] + for team_id in range(self.teams_count): + for soldier_id in all_ids[team_id]: + move_vect = self.MOVE_VECTORS[id_moves[team_id][soldier_id]] + new_y = (id_positions[team_id][soldier_id][0] + move_vect[0]) % self.map_height + new_x = (id_positions[team_id][soldier_id][1] + move_vect[1]) % self.map_width + soldier_lists[new_y][new_x][team_id].append(soldier_id) + + # Přidáme vojáky v homes: + for team_id in range(self.teams_count): + home_pos = home_positions[team_id] + soldier_lists[home_pos[0]][home_pos[1]][team_id].append(highest_ids[team_id] + 1) + + # Vojáci se pomlátí: + for y in range(self.map_height): + for x in range(self.map_width): + team_strengths = [len(soldier_lists[y][x][tid]) for tid in range(self.teams_count)] + team_strengths.sort() + for tid in range(self.teams_count): + soldier_lists[y][x][tid] = soldier_lists[y][x][tid][team_strengths[-2]:] + + # Vygenerujeme výsledek: + score = [0] * self.teams_count + new_map = [[] for _ in range(self.map_height)] + y = 0 + for row in state["map"]: + x = 0 + for field in row: + new_field = { + "home_for_team": field["home_for_team"], + "occupied_by_team": field["occupied_by_team"], + "hill": field["hill"], + "members": [] + } + for team_id in range(self.teams_count): + for soldier_id in soldier_lists[y][x][team_id]: + new_field["members"].append(self.generate_soldier(team_id, soldier_id)) + new_field["occupied_by_team"] = team_id + if new_field["occupied_by_team"] is not None: + score[new_field["occupied_by_team"]] += 1 + new_map[y].append(new_field) + x += 1 + y += 1 + return ({"map": new_map}, score) + + def generate_warn(self, member_id, description): + return { + "id": member_id, + "description": description + } + + # Předpokládám validní `state`, vadit může + # * "hill" + # * nevalidní ID + # * více tahů jedním soldierem + def validate_move(self, state: Any, team_id: int, move: Any, round_id: int) -> Union[None, Any]: + # nakumulované warningy + warns = [] + # souřadnice IDs daného týmu + id_positions = {} + # 2D array s `True`s tam, kde je hill + hills = [[False] * self.map_width for _ in range(self.map_height)] + # ID soldierů, se kterými tým už hnul + ids_moved = {} + + y = 0 + for row in state["map"]: + x = 0 + for field in row: + hills[y][x] = field["hill"] + for member in field["members"]: + if member["team"] != team_id: + continue + id_positions[member["id"]] = [y, x] + x += 1 + y += 1 + + for member in move["members"]: + if member["id"] not in id_positions: + warns.append(self.generate_warn(member["id"], "Neplatné ID!")) + continue + if member["id"] in ids_moved: + warns.append(self.generate_warn(member["id"], "Duplicitní pohyb vojákem. Jen jeho poslední pohyb se počítá.")) + continue + ids_moved[member["id"]] = True + if member["action"] is None: + continue + move_vect = self.MOVE_VECTORS[member["action"]] + new_position_y = id_positions[member["id"]][0] + move_vect[0] + new_position_x = id_positions[member["id"]][1] + move_vect[1] + if hills[new_position_y][new_position_x]: + warns.append(self.generate_warn(member["id"], "Voják se snaží vylézt na skálu, což nejde.")) + + if len(warns) == 0: + return None + else: + return {"status": "warning", "members": warns} + + def home_position(self, state: Any, team_id): + y = 0 + for row in state["map"]: + x = 0 + for field in row: + if field["home_for_team"] == team_id: + return [y, x] + x += 1 + y += 1 + + # Akorát vycentruju home týmu. + # TODO: Chceme přečíslovat týmy ("můj tým je vždy 0")? + def personalize_state(self, state: Any, team_id: int, round_id: int) -> Any: + # Zjisti pozici domečku tohoto týmu: + home_yx = self.home_position(state, team_id) + y_diff = (self.map_height // 2) - home_yx[0] + x_diff = (self.map_width // 2) - home_yx[1] + + # Vycentruj: + map_personalized = [ + [ + state["map"][(y - y_diff) % self.map_height][(x - x_diff) % self.map_width] + for x in range(self.map_width) + ] + for y in range(self.map_height) + ] + + return {"map": map_personalized}