├── .gitignore ├── AI.py ├── BT.py ├── Controller.py ├── Model.py ├── Network.py ├── README.md ├── Utils.py ├── graph.py ├── message ├── map_message.py ├── possible_base_message.py └── support_cell_message.py ├── state.py └── tsp_generator.py /.gitignore: -------------------------------------------------------------------------------- 1 | ./.~lock.temp.ods# 2 | ./temp.ods 3 | ./test.py 4 | ./.fuse_hidden000002bb00000001 5 | __pycache__ 6 | build/* 7 | dist/* 8 | Controller.spec 9 | .vscode/* 10 | .idea/* 11 | python-binary 12 | 13 | 14 | # Created by https://www.toptal.com/developers/gitignore/api/pycharm,git 15 | # Edit at https://www.toptal.com/developers/gitignore?templates=pycharm,git 16 | 17 | ### Git ### 18 | # Created by git for backups. To disable backups in Git: 19 | # $ git config --global mergetool.keepBackup false 20 | *.orig 21 | 22 | # Created by git when using merge tools for conflicts 23 | *.BACKUP.* 24 | *.BASE.* 25 | *.LOCAL.* 26 | *.REMOTE.* 27 | *_BACKUP_*.txt 28 | *_BASE_*.txt 29 | *_LOCAL_*.txt 30 | *_REMOTE_*.txt 31 | 32 | ### PyCharm ### 33 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 34 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 35 | 36 | # User-specific stuff 37 | .idea/**/workspace.xml 38 | .idea/**/tasks.xml 39 | .idea/**/usage.statistics.xml 40 | .idea/**/dictionaries 41 | .idea/**/shelf 42 | 43 | # Generated files 44 | .idea/**/contentModel.xml 45 | 46 | # Sensitive or high-churn files 47 | .idea/**/dataSources/ 48 | .idea/**/dataSources.ids 49 | .idea/**/dataSources.local.xml 50 | .idea/**/sqlDataSources.xml 51 | .idea/**/dynamic.xml 52 | .idea/**/uiDesigner.xml 53 | .idea/**/dbnavigator.xml 54 | 55 | # Gradle 56 | .idea/**/gradle.xml 57 | .idea/**/libraries 58 | 59 | # Gradle and Maven with auto-import 60 | # When using Gradle or Maven with auto-import, you should exclude module files, 61 | # since they will be recreated, and may cause churn. Uncomment if using 62 | # auto-import. 63 | # .idea/artifacts 64 | # .idea/compiler.xml 65 | # .idea/jarRepositories.xml 66 | # .idea/modules.xml 67 | # .idea/*.iml 68 | # .idea/modules 69 | # *.iml 70 | # *.ipr 71 | 72 | # CMake 73 | cmake-build-*/ 74 | 75 | # Mongo Explorer plugin 76 | .idea/**/mongoSettings.xml 77 | 78 | # File-based project format 79 | *.iws 80 | 81 | # IntelliJ 82 | out/ 83 | 84 | # mpeltonen/sbt-idea plugin 85 | .idea_modules/ 86 | 87 | # JIRA plugin 88 | atlassian-ide-plugin.xml 89 | 90 | # Cursive Clojure plugin 91 | .idea/replstate.xml 92 | 93 | # Crashlytics plugin (for Android Studio and IntelliJ) 94 | com_crashlytics_export_strings.xml 95 | crashlytics.properties 96 | crashlytics-build.properties 97 | fabric.properties 98 | 99 | # Editor-based Rest Client 100 | .idea/httpRequests 101 | 102 | # Android studio 3.1+ serialized cache file 103 | .idea/caches/build_file_checksums.ser 104 | 105 | ### PyCharm Patch ### 106 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 107 | 108 | # *.iml 109 | # modules.xml 110 | # .idea/misc.xml 111 | # *.ipr 112 | 113 | # Sonarlint plugin 114 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 115 | .idea/**/sonarlint/ 116 | 117 | # SonarQube Plugin 118 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 119 | .idea/**/sonarIssues.xml 120 | 121 | # Markdown Navigator plugin 122 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 123 | .idea/**/markdown-navigator.xml 124 | .idea/**/markdown-navigator-enh.xml 125 | .idea/**/markdown-navigator/ 126 | 127 | # Cache file creation bug 128 | # See https://youtrack.jetbrains.com/issue/JBR-2257 129 | .idea/$CACHE_FILE$ 130 | 131 | # CodeStream plugin 132 | # https://plugins.jetbrains.com/plugin/12206-codestream 133 | .idea/codestream.xml 134 | 135 | # End of https://www.toptal.com/developers/gitignore/api/pycharm,git 136 | 137 | ./*.zip 138 | ./AIC21-Game-main/ 139 | 140 | 141 | # Created by https://www.toptal.com/developers/gitignore/api/python 142 | # Edit at https://www.toptal.com/developers/gitignore?templates=python 143 | 144 | ### Python ### 145 | # Byte-compiled / optimized / DLL files 146 | __pycache__/ 147 | *.py[cod] 148 | *$py.class 149 | 150 | # C extensions 151 | *.so 152 | 153 | # Distribution / packaging 154 | .Python 155 | build/ 156 | develop-eggs/ 157 | dist/ 158 | downloads/ 159 | eggs/ 160 | .eggs/ 161 | parts/ 162 | sdist/ 163 | var/ 164 | wheels/ 165 | pip-wheel-metadata/ 166 | share/python-wheels/ 167 | *.egg-info/ 168 | .installed.cfg 169 | *.egg 170 | MANIFEST 171 | 172 | # PyInstaller 173 | # Usually these files are written by a python script from a template 174 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 175 | *.manifest 176 | *.spec 177 | 178 | # Installer logs 179 | pip-log.txt 180 | pip-delete-this-directory.txt 181 | 182 | # Unit test / coverage reports 183 | htmlcov/ 184 | .tox/ 185 | .nox/ 186 | .coverage 187 | .coverage.* 188 | .cache 189 | nosetests.xml 190 | coverage.xml 191 | *.cover 192 | *.py,cover 193 | .hypothesis/ 194 | .pytest_cache/ 195 | pytestdebug.log 196 | 197 | # Translations 198 | *.mo 199 | *.pot 200 | 201 | # Django stuff: 202 | *.log 203 | local_settings.py 204 | db.sqlite3 205 | db.sqlite3-journal 206 | 207 | # Flask stuff: 208 | instance/ 209 | .webassets-cache 210 | 211 | # Scrapy stuff: 212 | .scrapy 213 | 214 | # Sphinx documentation 215 | docs/_build/ 216 | doc/_build/ 217 | 218 | # PyBuilder 219 | target/ 220 | 221 | # Jupyter Notebook 222 | .ipynb_checkpoints 223 | 224 | # IPython 225 | profile_default/ 226 | ipython_config.py 227 | 228 | # pyenv 229 | .python-version 230 | 231 | # pipenv 232 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 233 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 234 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 235 | # install all needed dependencies. 236 | #Pipfile.lock 237 | 238 | # poetry 239 | #poetry.lock 240 | 241 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 242 | __pypackages__/ 243 | 244 | # Celery stuff 245 | celerybeat-schedule 246 | celerybeat.pid 247 | 248 | # SageMath parsed files 249 | *.sage.py 250 | 251 | # Environments 252 | # .env 253 | .env/ 254 | .venv/ 255 | env/ 256 | venv/ 257 | ENV/ 258 | env.bak/ 259 | venv.bak/ 260 | pythonenv* 261 | 262 | # Spyder project settings 263 | .spyderproject 264 | .spyproject 265 | 266 | # Rope project settings 267 | .ropeproject 268 | 269 | # mkdocs documentation 270 | /site 271 | 272 | # mypy 273 | .mypy_cache/ 274 | .dmypy.json 275 | dmypy.json 276 | 277 | # Pyre type checker 278 | .pyre/ 279 | 280 | # pytype static type analyzer 281 | .pytype/ 282 | 283 | # operating system-related files 284 | *.DS_Store #file properties cache/storage on macOS 285 | Thumbs.db #thumbnail cache on Windows 286 | 287 | # profiling data 288 | .prof 289 | 290 | 291 | # End of https://www.toptal.com/developers/gitignore/api/python 292 | /visualizer/ 293 | /server/ 294 | 295 | -------------------------------------------------------------------------------- /AI.py: -------------------------------------------------------------------------------- 1 | import copy 2 | from Model import * 3 | from Utils import * 4 | from graph import * 5 | from message.map_message import * 6 | from message.possible_base_message import * 7 | from message.support_cell_message import * 8 | from tsp_generator import get_limit, get_number_of_object 9 | from state import * 10 | from BT import * 11 | 12 | 13 | class AI: 14 | game_round = -1 15 | life_cycle = 1 16 | map = None 17 | w, h = -1, -1 18 | id = 0 19 | ids = {} 20 | latest_pos = {} 21 | found_history = set() 22 | worker_state = WorkerState.Null 23 | soldier_state = SoldierState.Null 24 | soldier_init_random_dir = None 25 | last_name_of_object = None 26 | possible_base_cells = [] 27 | soldier_targets = [] 28 | prev_round_resource = 0 29 | soldier_path_neighbors_history = [] 30 | own_cells_history = [] 31 | latest_map = None 32 | cell_target = None 33 | attack_dir = None 34 | out_file = None 35 | output_path = "/media/mh/New Volume/AIC21-Client-Python/output/" 36 | debug = False 37 | born_game_round = -1 38 | prev_hp = 0 39 | prev_es = 0 40 | shot_once = False 41 | # NEW SHIT 42 | chosen_near_base_cell_BK = None 43 | chosen_near_base_cell_BU = None 44 | near_base_safe_cells = [] 45 | shot_default_dir = None 46 | first_id = -1 47 | exploration_target = None 48 | sup_cells = [] 49 | attack_random_target = None 50 | 51 | def __init__(self): 52 | # Current Game State 53 | self.game: Game = None 54 | 55 | # Answer 56 | self.message: str = None 57 | self.direction: int = None 58 | self.value: int = None 59 | 60 | # mine 61 | self.pos = (-1, -1) 62 | self.new_neighbors = {} 63 | self.all_neighbors = {} 64 | self.encoded_neighbors = "" 65 | self.shot = False 66 | self.reached_sup_cell = False 67 | self.visible_bread = [] 68 | self.visible_grass = [] 69 | self.resource_count = 0 70 | 71 | # @time_measure 72 | def search_neighbors(self): 73 | # TODO improve by creating the list of indices instead of all the cells 74 | ant = self.game.ant 75 | cells = ant.visibleMap.cells 76 | neighbor_cells = [j for sub in cells for j in sub if j is not None] 77 | neighbor_nodes = [] 78 | for n in neighbor_cells: 79 | w = n.type == CellType.WALL.value 80 | s = n.type == CellType.SWAMP.value 81 | t = n.type == CellType.TRAP.value 82 | if w: 83 | neighbor_nodes.append(Node((n.x, n.y), True, True)) 84 | elif s: 85 | neighbor_nodes.append(Node((n.x, n.y), True, False, swamp=True)) 86 | elif t: 87 | neighbor_nodes.append(Node((n.x, n.y), True, False, trap=True)) 88 | else: 89 | b = n.resource_value if \ 90 | n.resource_type == ResourceType.BREAD.value else 0 91 | g = n.resource_value if \ 92 | n.resource_type == ResourceType.GRASS.value else 0 93 | 94 | if b > 0 and manhattan_dist(self.pos, (n.x, n.y), *AI.map.dim) <= 3: 95 | self.visible_bread.append({'number': b, 'pos': (n.x, n.y)}) 96 | 97 | if g > 0 and manhattan_dist(self.pos, (n.x, n.y), *AI.map.dim) <= 3: 98 | self.visible_grass.append({'number': g, 'pos': (n.x, n.y)}) 99 | 100 | aw, ally_s, ew, es = [0] * 4 101 | for a in n.ants: 102 | if a.antTeam == AntTeam.ALLIED.value: 103 | if a.antType == AntType.KARGAR.value: 104 | aw += 1 105 | elif a.antType == AntType.SARBAAZ.value: 106 | ally_s += 1 107 | else: 108 | if a.antType == AntType.KARGAR.value: 109 | ew += 1 110 | elif a.antType == AntType.SARBAAZ.value: 111 | es += 1 112 | neighbor_nodes.append(Node((n.x, n.y), True, False, 113 | bread=b, 114 | grass=g, 115 | ally_workers=aw, 116 | ally_soldiers=ally_s, 117 | enemy_workers=ew, 118 | enemy_soldiers=es)) 119 | 120 | self.all_neighbors = {n.pos: n for n in neighbor_nodes} 121 | self.new_neighbors = {n.pos: n for n in neighbor_nodes if 122 | AI.map.nodes[n.pos] != n} 123 | if AI.life_cycle > 1: 124 | self.value = self.determine_value(neighbor_cells, neighbor_nodes) 125 | AI.found_history.update(set(self.new_neighbors.keys())) 126 | 127 | # @time_measure 128 | def determine_value(self, neighbor_cells, neighbor_nodes): 129 | for n in neighbor_cells: 130 | if n.type == CellType.BASE.value and (n.x, n.y) != AI.map.base_pos and \ 131 | AI.map.enemy_base_pos is None: 132 | AI.map.enemy_base_pos = (n.x, n.y) 133 | return VALUES["enemy_base"] 134 | 135 | sum_bg = 0 136 | for n in neighbor_nodes: 137 | if not AI.map.nodes[n.pos].discovered and \ 138 | (n.bread > 0 or n.grass > 0): 139 | sum_bg += n.bread 140 | sum_bg += n.grass 141 | if sum_bg > 0: 142 | return VALUES["bg"] + sum_bg 143 | 144 | total_disc = 0 145 | for n in neighbor_nodes: 146 | if not AI.map.nodes[n.pos].discovered: 147 | total_disc += 1 148 | if total_disc >= 3: 149 | return VALUES["disc_gt_3"] + total_disc 150 | 151 | total_soldiers = 0 152 | for n in neighbor_nodes: 153 | if AI.map.nodes[n.pos].enemy_soldiers < n.enemy_soldiers: 154 | total_soldiers += 1 155 | if total_soldiers > 0: 156 | return VALUES["es"] + total_soldiers 157 | 158 | if 0 < total_disc < 3: 159 | return VALUES["disc_lt_3"] + total_disc 160 | 161 | if self.pos in self.new_neighbors.keys(): 162 | if self.new_neighbors[self.pos].bread > AI.map.nodes[self.pos].bread \ 163 | or self.new_neighbors[self.pos].grass > AI.map.nodes[self.pos].grass: 164 | return VALUES["bg_add"] + abs(self.new_neighbors[self.pos].bread - AI.map.nodes[self.pos].bread) + \ 165 | abs(self.new_neighbors[self.pos].grass - AI.map.nodes[self.pos].grass) 166 | 167 | if self.game.ant.antType == AntType.KARGAR.value and self.pos in self.new_neighbors.keys(): 168 | if self.new_neighbors[self.pos].bread < AI.map.nodes[self.pos].bread \ 169 | or self.new_neighbors[self.pos].grass < AI.map.nodes[self.pos].grass: 170 | if self.game.ant.currentResource.value > AI.prev_round_resource: 171 | return VALUES["bg_sub"] + abs(self.new_neighbors[self.pos].bread - AI.map.nodes[self.pos].bread) + \ 172 | abs(self.new_neighbors[self.pos].grass - AI.map.nodes[self.pos].grass) 173 | 174 | return VALUES["none"] 175 | 176 | # @time_measure 177 | def update_map_from_neighbors(self): 178 | for pos, n in self.all_neighbors.items(): 179 | AI.map.nodes[pos] = copy.deepcopy(n) 180 | # if not self.new_neighbors: 181 | # return 182 | # # just in case. not really needed 183 | # for pos, n in self.new_neighbors.items(): 184 | # AI.map.nodes[pos] = copy.deepcopy(n) 185 | 186 | # @time_measure 187 | def update_map_from_chat_box(self): 188 | maps = [msg for msg in 189 | self.game.chatBox.allChats[-MAX_MESSAGES_PER_TURN:] if '!' in 190 | msg.text and not msg.text.startswith("sc") and not msg.text.startswith("sh") and msg.turn == AI.game_round - 1] 191 | if AI.life_cycle == 1: 192 | if AI.born_game_round > MAX_MESSAGES_INIT: 193 | maps = [msg for msg in self.game.chatBox.allChats[-MAX_MESSAGES_INIT * MAX_MESSAGES_PER_TURN:] if '!' in 194 | msg.text and not msg.text.startswith("sc") and not msg.text.startswith("sh")] 195 | else: 196 | maps = [msg for msg in self.game.chatBox.allChats if '!' in 197 | msg.text] 198 | 199 | for m in maps: 200 | ant_id, ant_pos, ant_dir, \ 201 | ant_shot, \ 202 | nodes, enemy_base_pos = decode_nodes(m.text, 203 | AI.w, AI.h, 204 | self.game.ant.viewDistance) 205 | AI.latest_pos[ant_id] = (ant_pos, m.turn) 206 | for pos, n in nodes.items(): 207 | if n != AI.map.nodes[pos]: 208 | AI.map.nodes[pos] = copy.deepcopy(n) 209 | 210 | if enemy_base_pos is not None: 211 | AI.map.enemy_base_pos = enemy_base_pos 212 | 213 | if ant_shot: 214 | target = add_pos_dir(ant_pos, ant_dir, AI.w, AI.h) 215 | AI.soldier_targets.append(target) 216 | 217 | # @time_measure 218 | def update_ids_from_chat_box(self): 219 | id_msgs = [msg.text for msg in 220 | self.game.chatBox.allChats[-MAX_MESSAGES_PER_TURN:] if 221 | msg.text.startswith("id") and msg.turn == AI.game_round - 1] 222 | if AI.life_cycle <= 3: 223 | AI.ids[AntType.SARBAAZ.value] = [] 224 | AI.ids[AntType.KARGAR.value] = [] 225 | id_msgs = [msg.text for msg in self.game.chatBox.allChats if 226 | msg.text.startswith("id")] 227 | 228 | for m in id_msgs: 229 | msg_type = int(m[2]) 230 | msg_id = int(m[3:]) 231 | if msg_id not in AI.ids[0] and msg_id not in AI.ids[1]: 232 | AI.ids[msg_type].append(msg_id) 233 | 234 | def send_id(self): 235 | self.message = "id" + str(self.game.ant.antType) + str(AI.id) 236 | self.value = VALUES["id"] 237 | 238 | def make_id(self, min_id=1, max_id=220): 239 | all_ids = AI.ids[0] + AI.ids[1] if AI.ids else [] 240 | iid = random.randint(min_id, max_id) 241 | while iid in all_ids: 242 | iid = random.randint(min_id, max_id) 243 | AI.id = iid 244 | 245 | def update_support_cells_from_chat_box(self): 246 | sup_msgs = [msg.text for msg in 247 | self.game.chatBox.allChats[-MAX_MESSAGES_PER_TURN:] if 248 | msg.text.startswith("sc") and msg.turn == AI.game_round - 1] 249 | if AI.life_cycle <= 3: 250 | sup_msgs = [msg.text for msg in self.game.chatBox.allChats if 251 | msg.text.startswith("sc")] 252 | 253 | for m in sup_msgs: 254 | ant_id, ant_pos, resource_count = decode_support_cell(m, AI.w, AI.h) 255 | if ant_pos not in AI.sup_cells: 256 | AI.sup_cells.append(ant_pos) 257 | 258 | 259 | 260 | def get_next_pos(self, cur_pos, move): 261 | if move == 1: 262 | next_pos = (cur_pos[0] + 1, cur_pos[1]) 263 | elif move == 2: 264 | next_pos = (cur_pos[0], cur_pos[1] - 1) 265 | elif move == 3: 266 | next_pos = (cur_pos[0] - 1, cur_pos[1]) 267 | elif move == 4: 268 | next_pos = (cur_pos[0], cur_pos[1] + 1) 269 | else: 270 | return -1, -1 271 | 272 | next_pos = ((next_pos[0] + self.game.mapWidth) % self.game.mapWidth, 273 | (next_pos[1] + self.game.mapHeight) % self.game.mapHeight) 274 | return next_pos 275 | 276 | def fix_pos(self, pos): 277 | return ((pos[0] + self.game.mapWidth) % self.game.mapWidth, 278 | (pos[1] + self.game.mapHeight) % self.game.mapHeight) 279 | 280 | def is_road_to_wall(self, move: int): 281 | for i in [1, 2]: 282 | tf = True 283 | for j in [-1, 0, 1]: 284 | if move == 1: 285 | p = (self.pos[0] + i, self.pos[1] + j) 286 | elif move == 2: 287 | p = (self.pos[0] + j, self.pos[1] - i) 288 | elif move == 3: 289 | p = (self.pos[0] - i, self.pos[1] + j) 290 | else: 291 | p = (self.pos[0] + j, self.pos[1] + i) 292 | node = AI.map.nodes[self.fix_pos(pos=p)] 293 | if not node.wall: 294 | tf = False 295 | if tf: 296 | return True 297 | return False 298 | 299 | # @time_measure 300 | def get_init_ants_next_move(self, preferred_moves, map) -> int: 301 | for m in preferred_moves: 302 | if (not self.is_road_to_wall(m)) and (self.get_next_pos(self.pos, m) != AI.latest_pos[AI.id][0]): 303 | for j in [0, 1, -1]: 304 | if m == 1: 305 | p = (self.pos[0] + 2, self.pos[1] + j) 306 | elif m == 2: 307 | p = (self.pos[0] + j, self.pos[1] - 2) 308 | elif m == 3: 309 | p = (self.pos[0] - 2, self.pos[1] + j) 310 | else: 311 | p = (self.pos[0] + j, self.pos[1] + 2) 312 | path = map.get_path_with_max_length(map.nodes[self.pos], map.nodes[self.fix_pos(p)], 2) 313 | if path is not None: 314 | return Direction.get_value(map.step(self.pos, path[0].pos)) 315 | print_with_debug("error on get_init_ants_next_move", f=AI.out_file) 316 | return Direction.get_random_direction() 317 | 318 | def get_init_ant_explore_move(self): 319 | AI.worker_state = WorkerState.InitCollecting 320 | if AI.id <= Utils.INIT_ANTS_NUM: 321 | if AI.id == Utils.GRASS_ONLY_ID: 322 | m = self.get_init_ants_next_move(Utils.INIT_STRAIGHT_ANTS_MOVES[AI.id - 1], 323 | AI.map.convert_bread_cells_to_wall()) 324 | else: 325 | m = self.get_init_ants_next_move(Utils.INIT_STRAIGHT_ANTS_MOVES[AI.id - 1], AI.map) 326 | else: 327 | m = self.get_init_ants_next_move(Utils.INIT_STRAIGHT_ANTS_MOVES[AI.id % 4], AI.map) 328 | 329 | if m < 5: 330 | return m 331 | else: 332 | print_with_debug("something went wrong, init ants move :", m, "from id:", AI.id, f=AI.out_file) 333 | return Direction.get_random_direction() 334 | 335 | # @time_measure 336 | def get_new_ant_collect_move(self, own_discovered_search=False): 337 | if own_discovered_search: 338 | search_map = Graph((AI.w, AI.h), (self.game.baseX, self.game.baseY)) 339 | for p in self.found_history: 340 | search_map.nodes[p] = AI.map.nodes[p] 341 | else: 342 | search_map = AI.map 343 | if len(AI.possible_base_cells) > 0: 344 | search_map = search_map.convert_base_possible_cells_to_wall(AI.near_base_safe_cells) 345 | 346 | if self.has_resource_in_map(2, 1) is None: 347 | m = self.get_init_ant_explore_move() 348 | elif self.game.ant.currentResource.type == ResourceType.BREAD.value: 349 | print_with_debug("ANT is holding bread", f=AI.out_file) 350 | if self.has_resource_in_map(ResourceType.BREAD.value, 351 | WORKER_MAX_CARRYING_RESOURCE_AMOUNT - self.game.ant.currentResource.value) \ 352 | == ResourceType.BREAD.value: 353 | print_with_debug("state has bread res to find", f=AI.out_file) 354 | m, AI.last_name_of_object, d = search_map.get_resource_best_move( 355 | src_pos=self.pos, 356 | dest_pos=AI.map.base_pos, 357 | name_of_object='bread', 358 | limit=get_limit( 359 | bread_min=WORKER_MAX_CARRYING_RESOURCE_AMOUNT, 360 | grass_min=math.inf 361 | ), 362 | number_of_object=get_number_of_object(self.game.ant.currentResource), 363 | ) 364 | else: 365 | print_with_debug("state has not other res", f=AI.out_file) 366 | path = AI.map.get_path(AI.map.nodes[self.pos], AI.map.nodes[AI.map.base_pos]) 367 | m = Direction.get_value(AI.map.step(self.pos, path[0].pos)) 368 | elif self.game.ant.currentResource.type == ResourceType.GRASS.value: 369 | print_with_debug("ANT is holding grass", f=AI.out_file) 370 | if self.has_resource_in_map(ResourceType.GRASS.value, 371 | WORKER_MAX_CARRYING_RESOURCE_AMOUNT - self.game.ant.currentResource.value) \ 372 | == ResourceType.GRASS.value: 373 | print_with_debug("state has grass res to find", f=AI.out_file) 374 | m, AI.last_name_of_object, d = search_map.get_resource_best_move( 375 | src_pos=self.pos, 376 | dest_pos=AI.map.base_pos, 377 | name_of_object='grass', 378 | limit=get_limit( 379 | bread_min=math.inf, 380 | grass_min=WORKER_MAX_CARRYING_RESOURCE_AMOUNT 381 | ), 382 | number_of_object=get_number_of_object(self.game.ant.currentResource), 383 | ) 384 | else: 385 | print_with_debug("state has not to find", f=AI.out_file) 386 | path = AI.map.get_path(AI.map.nodes[self.pos], AI.map.nodes[AI.map.base_pos]) 387 | m = Direction.get_value(AI.map.step(self.pos, path[0].pos)) 388 | else: 389 | print_with_debug("ANT isn't hold anything", f=AI.out_file) 390 | grass_dir = None 391 | grass_dis = math.inf 392 | bread_dir = None 393 | bread_dis = math.inf 394 | if self.has_resource_in_map(ResourceType.GRASS.value, 395 | 1, 396 | own_discovered_search) \ 397 | == ResourceType.GRASS.value: 398 | grass_dir, AI.last_name_of_object, grass_dis = search_map.get_resource_best_move( 399 | src_pos=self.pos, 400 | dest_pos=AI.map.base_pos, 401 | name_of_object='grass', 402 | limit=get_limit( 403 | bread_min=math.inf, 404 | grass_min=WORKER_MAX_CARRYING_RESOURCE_AMOUNT 405 | ), 406 | number_of_object=get_number_of_object(self.game.ant.currentResource), 407 | ) 408 | if self.has_resource_in_map(ResourceType.BREAD.value, 409 | 1, 410 | own_discovered_search) \ 411 | == ResourceType.BREAD.value: 412 | bread_dir, AI.last_name_of_object, bread_dis = search_map.get_resource_best_move( 413 | src_pos=self.pos, 414 | dest_pos=AI.map.base_pos, 415 | name_of_object='bread', 416 | limit=get_limit( 417 | bread_min=WORKER_MAX_CARRYING_RESOURCE_AMOUNT, 418 | grass_min=math.inf 419 | ), 420 | number_of_object=get_number_of_object( 421 | self.game.ant.currentResource), 422 | ) 423 | print_with_debug("grass_dir:", grass_dir, f=AI.out_file) 424 | print_with_debug("grass_dis:", grass_dis, f=AI.out_file) 425 | print_with_debug("bread_dir:", bread_dir, f=AI.out_file) 426 | print_with_debug("bread_dis:", bread_dis, f=AI.out_file) 427 | if (grass_dis <= bread_dis 428 | or ((AI.id % Utils.NEW_GRASS_PRIORITY_PER_ROUND) == 0 and grass_dis - Utils.PRIORITY_GAP <= bread_dis)) \ 429 | and grass_dis != math.inf: 430 | m = grass_dir 431 | elif bread_dir is not None: 432 | m = bread_dir 433 | else: 434 | m = self.get_init_ant_explore_move() 435 | return m 436 | 437 | # @time_measure 438 | def get_init_ant_collect_move(self, own_discovered_search=False): 439 | if own_discovered_search: 440 | search_map = Graph((AI.w, AI.h), (self.game.baseX, self.game.baseY)) 441 | for p in self.found_history: 442 | search_map.nodes[p] = AI.map.nodes[p] 443 | else: 444 | search_map = AI.map 445 | if len(AI.possible_base_cells) > 0: 446 | search_map = search_map.convert_base_possible_cells_to_wall(AI.near_base_safe_cells) 447 | 448 | if self.game.ant.currentResource.type == ResourceType.BREAD.value: 449 | if self.has_resource_in_map(ResourceType.BREAD.value, 450 | WORKER_MAX_CARRYING_RESOURCE_AMOUNT - self.game.ant.currentResource.value, 451 | own_discovered_search) \ 452 | == ResourceType.BREAD.value: 453 | print_with_debug("state has res to find", f=AI.out_file) 454 | m, AI.last_name_of_object, d = search_map.get_resource_best_move( 455 | src_pos=self.pos, 456 | dest_pos=AI.map.base_pos, 457 | name_of_object='bread', 458 | limit=get_limit( 459 | bread_min=WORKER_MAX_CARRYING_RESOURCE_AMOUNT, 460 | grass_min=math.inf 461 | ), 462 | number_of_object=get_number_of_object(self.game.ant.currentResource), 463 | ) 464 | else: 465 | print_with_debug("state has not other res", f=AI.out_file) 466 | path = AI.map.get_path(AI.map.nodes[self.pos], AI.map.nodes[AI.map.base_pos]) 467 | return Direction.get_value(AI.map.step(self.pos, path[0].pos)) 468 | elif self.game.ant.currentResource.type == ResourceType.GRASS.value: 469 | if self.game.ant.currentResource.value == WORKER_MAX_CARRYING_RESOURCE_AMOUNT: 470 | path = AI.map.get_path(AI.map.nodes[self.pos], AI.map.nodes[AI.map.base_pos]) 471 | return Direction.get_value(AI.map.step(self.pos, path[0].pos)) 472 | if self.has_resource_in_map(ResourceType.GRASS.value, 473 | WORKER_MAX_CARRYING_RESOURCE_AMOUNT - self.game.ant.currentResource.value, 474 | own_discovered_search) \ 475 | == ResourceType.GRASS.value: 476 | print_with_debug("state has res to find", f=AI.out_file) 477 | m, AI.last_name_of_object, d = search_map.get_resource_best_move( 478 | src_pos=self.pos, 479 | dest_pos=AI.map.base_pos, 480 | name_of_object='grass', 481 | limit=get_limit( 482 | bread_min=math.inf, 483 | grass_min=WORKER_MAX_CARRYING_RESOURCE_AMOUNT 484 | ), 485 | number_of_object=get_number_of_object(self.game.ant.currentResource), 486 | ) 487 | else: 488 | print_with_debug("state has not to find", f=AI.out_file) 489 | path = AI.map.get_path(AI.map.nodes[self.pos], AI.map.nodes[AI.map.base_pos]) 490 | return Direction.get_value(AI.map.step(self.pos, path[0].pos)) 491 | else: 492 | grass_dir = None 493 | grass_dis = math.inf 494 | bread_dir = None 495 | bread_dis = math.inf 496 | print_with_debug("ANT isn't hold anything") 497 | if self.has_resource_in_map(ResourceType.GRASS.value, 498 | 1, 499 | own_discovered_search) \ 500 | == ResourceType.GRASS.value: 501 | grass_dir, AI.last_name_of_object, grass_dis = search_map.get_resource_best_move( 502 | src_pos=self.pos, 503 | dest_pos=AI.map.base_pos, 504 | name_of_object='grass', 505 | limit=get_limit( 506 | bread_min=math.inf, 507 | grass_min=WORKER_MAX_CARRYING_RESOURCE_AMOUNT 508 | ), 509 | number_of_object=get_number_of_object(self.game.ant.currentResource), 510 | ) 511 | if self.has_resource_in_map(ResourceType.BREAD.value, 512 | 1, 513 | own_discovered_search) \ 514 | == ResourceType.BREAD.value \ 515 | and AI.id != Utils.GRASS_ONLY_ID: 516 | bread_dir, AI.last_name_of_object, bread_dis = search_map.get_resource_best_move( 517 | src_pos=self.pos, 518 | dest_pos=AI.map.base_pos, 519 | name_of_object='bread', 520 | limit=get_limit( 521 | bread_min=WORKER_MAX_CARRYING_RESOURCE_AMOUNT, 522 | grass_min=math.inf 523 | ), 524 | number_of_object=get_number_of_object( 525 | self.game.ant.currentResource), 526 | ) 527 | print_with_debug("grass_dir:", grass_dir, f=AI.out_file) 528 | print_with_debug("grass_dis:", grass_dis, f=AI.out_file) 529 | print_with_debug("bread_dir:", bread_dir, f=AI.out_file) 530 | print_with_debug("bread_dis:", bread_dis, f=AI.out_file) 531 | if ((grass_dis <= bread_dis and AI.id != BREAD_PRIORITY_ID) 532 | or ((AI.id == GRASS_PRIORITY_ID1 or AI.id == GRASS_PRIORITY_ID2) and grass_dis - PRIORITY_GAP <= bread_dis) 533 | or (AI.id == BREAD_PRIORITY_ID and grass_dis + PRIORITY_GAP < bread_dis)) \ 534 | and grass_dis != math.inf: 535 | m = grass_dir 536 | elif bread_dir is not None: 537 | m = bread_dir 538 | else: 539 | m = self.get_init_ant_explore_move() 540 | return m 541 | 542 | def has_resource_in_map(self, res_type: int, res_num=10, own_discovered_search=False): 543 | if own_discovered_search: 544 | own_map = Graph((AI.w, AI.h), (self.game.baseX, self.game.baseY)) 545 | for p in self.found_history: 546 | own_map.nodes[p] = AI.map 547 | else: 548 | own_map = AI.map 549 | 550 | print_with_debug("total bread num:", own_map.total_bread_number(), f=AI.out_file) 551 | print_with_debug("total grass num:", own_map.total_grass_number(), f=AI.out_file) 552 | if res_type == ResourceType.BREAD.value: 553 | if AI.map.total_bread_number() >= res_num: 554 | return res_type 555 | elif res_type == ResourceType.GRASS.value: 556 | if AI.map.total_grass_number() >= res_num: 557 | return res_type 558 | elif AI.map.total_grass_number() >= res_num: 559 | return ResourceType.BREAD.value 560 | elif AI.map.total_bread_number() >= res_num: 561 | return ResourceType.GRASS.value 562 | else: 563 | return None 564 | 565 | # @handle_exception 566 | # @time_measure 567 | @handle_exception 568 | def turn(self) -> (str, int, int): 569 | if AI.debug and AI.life_cycle > 2 and (AI.ids and (AI.id in AI.ids[0] or AI.id in AI.ids[1])): 570 | t = "soldier" if self.game.ant.antType == AntType.SARBAAZ.value else "worker" 571 | AI.out_file = open(AI.output_path + t + '_' + str(AI.born_game_round) + '_' + str(AI.first_id) + ".txt", "a+") 572 | 573 | self.round_initialization() 574 | 575 | if AI.game_round == 1: 576 | AI.worker_state = WorkerState.InitExploring if self.game.ant.antType == AntType.KARGAR.value else WorkerState.Null 577 | self.direction = self.get_new_ant_collect_move() 578 | 579 | # ************************************************************************************************************************* 580 | # ########################################################KAARGAAAR######################################################## 581 | # ************************************************************************************************************************* 582 | elif self.game.ant.antType == AntType.KARGAR.value: 583 | # print_map(AI.map, self.pos) 584 | if self.game.ant.currentResource.value is not None \ 585 | and self.game.ant.currentResource.value >= (WORKER_MAX_CARRYING_RESOURCE_AMOUNT / 2): 586 | print_with_debug("worker has >= (max carrying resources amount / 2) => back to base with bfs", 587 | f=AI.out_file) 588 | 589 | dir = AI.map.get_first_move_to_base(AI.map.nodes[self.pos], 590 | get_number_of_object(self.game.ant.currentResource)) 591 | if dir is None: 592 | self.direction = Direction.CENTER.value 593 | else: 594 | self.direction = dir 595 | print_with_debug("bfs dir:", self.direction) 596 | elif self.game_round > MAX_TURN_COUNT - 10: 597 | print_with_debug("last rounds => back to base with bfs", 598 | f=AI.out_file) 599 | if self.pos == AI.map.base_pos: 600 | self.direction = Direction.CENTER.value 601 | else: 602 | dir = AI.map.get_first_move_to_base(AI.map.nodes[self.pos], 603 | get_number_of_object(self.game.ant.currentResource)) 604 | if dir is None: 605 | self.direction = Direction.CENTER.value 606 | else: 607 | self.direction = dir 608 | print_with_debug("bfs dir:", self.direction) 609 | else: 610 | if AI.id <= Utils.INIT_ANTS_NUM: 611 | print_with_debug("INIT ANT", f=AI.out_file) 612 | if AI.worker_state == WorkerState.InitExploring: 613 | self.direction = self.get_init_ant_explore_move() 614 | elif AI.worker_state == WorkerState.Null or AI.worker_state == WorkerState.InitCollecting: 615 | self.direction = self.get_init_ant_collect_move() 616 | else: 617 | print_with_debug("NEW ANT", f=AI.out_file) 618 | if AI.worker_state == WorkerState.Null: 619 | self.determine_worker_state() 620 | 621 | self.direction = self.get_new_ant_collect_move() 622 | if self.direction == 0 or self.direction is None: 623 | print_with_debug("new ants at the end:", self.direction, f=AI.out_file) 624 | print_with_debug("=> move like init ant explore", f=AI.out_file) 625 | self.direction = self.get_init_ant_explore_move() 626 | 627 | # ************************************************************************************************************************* 628 | # ########################################################SAARBAAAZ######################################################## 629 | # ************************************************************************************************************************* 630 | elif self.game.ant.antType == AntType.SARBAAZ.value: 631 | # print_map(AI.map, self.pos, f=AI.out_file) 632 | if AI.life_cycle == 1: 633 | self.direction = self.random_valid_dir() 634 | else: 635 | if AI.born_game_round < EXPLORER_SUPPORT_MAX_ROUND: 636 | AI.soldier_state = SoldierState.Explorer_Supporter 637 | else: 638 | AI.exploration_target = None if AI.soldier_state != SoldierState.Null else AI.exploration_target 639 | AI.soldier_state = SoldierState.Null 640 | 641 | if AI.game_round > ATTACKING_SOLDIERS_ROUND: 642 | AI.soldier_state = SoldierState.AllInAttack 643 | 644 | if AI.soldier_state == SoldierState.Explorer_Supporter: 645 | self.direction = self.get_first_move_to_go() 646 | elif AI.soldier_state == SoldierState.AllInAttack: 647 | if AI.map.enemy_base_pos is not None: 648 | self.direction = self.get_first_move_to_target(self.pos, AI.map.enemy_base_pos) 649 | elif AI.possible_base_cells: 650 | if AI.attack_random_target is None: 651 | AI.attack_random_target = random.choice(AI.possible_base_cells) 652 | if self.pos == AI.attack_random_target: 653 | AI.attack_random_target = random.choice(AI.possible_base_cells) 654 | self.direction = self.get_first_move_to_target(self.pos, AI.attack_random_target) 655 | else: 656 | AI.exploration_target = None if AI.soldier_state != SoldierState.Null else AI.exploration_target 657 | AI.soldier_state = SoldierState.Null 658 | else: 659 | AI.exploration_target = None if AI.soldier_state != SoldierState.Null else AI.exploration_target 660 | AI.soldier_state = SoldierState.Null 661 | # else: 662 | # self.handle_base() 663 | # self.handle_shot() 664 | 665 | # ############### DEFAULT SECTION ############### 666 | if AI.soldier_state == SoldierState.Null: 667 | self.direction = self.discover_wrapper() 668 | 669 | self.send_msg() 670 | 671 | self.end_round() 672 | 673 | if self.message is not None and len(self.message) > 32: 674 | self.message = "" 675 | self.value = -100000 676 | 677 | if self.direction is None: 678 | self.direction = self.random_valid_dir() 679 | if self.direction is None or self.direction == Direction.CENTER.value: 680 | self.direction = Direction.get_random_direction() 681 | 682 | print_with_debug("turn", AI.game_round, "id", AI.id, "pos", self.pos, 683 | "worker state", AI.worker_state, 684 | "soldier state", AI.soldier_state, 685 | "dir", Direction.get_string(self.direction), 686 | "map value", self.value, f=AI.out_file) 687 | 688 | return self.message, self.value, self.direction 689 | 690 | def random_valid_dir(self): 691 | d = [(1, 0), (0, -1), (-1, 0), (0, 1)] 692 | nears = [] 693 | for i, dd in enumerate(d): 694 | pos = tuple(map(sum, zip(self.pos, dd))) 695 | pos = fix(pos, AI.w, AI.h) 696 | if not AI.map.nodes[pos].swamp and not AI.map.nodes[pos].wall: 697 | nears.append(pos) 698 | t = random.choice(nears) 699 | return Direction.get_value(AI.map.step(self.pos, t)) 700 | 701 | # @time_measure 702 | def round_initialization(self): 703 | print_with_debug("************************************************************************", f=AI.out_file) 704 | print_with_debug("************************************************************************", f=AI.out_file) 705 | print_with_debug("************************************************************************", f=AI.out_file) 706 | print_with_debug("ROUND START!", f=AI.out_file) 707 | if AI.id == Utils.GRASS_ONLY_ID: 708 | print_with_debug("Grass only ant", f=AI.out_file) 709 | 710 | print_with_debug(AI.found_history, f=AI.out_file) 711 | self.update_ids_from_chat_box() 712 | 713 | if AI.game_round == 2: 714 | prev_id = AI.id 715 | AI.id = sorted(AI.ids[self.game.ant.antType]).index(AI.id) + 1 716 | AI.ids[0] = [x for x in range(1, len(AI.ids[0]) + 1)] 717 | AI.ids[1] = [x for x in range(1, len(AI.ids[1]) + 1)] 718 | AI.latest_pos[AI.id] = AI.latest_pos[prev_id] 719 | 720 | if AI.life_cycle > 1 and AI.id not in AI.ids[0] and \ 721 | AI.id not in AI.ids[1]: 722 | self.send_id() 723 | 724 | if AI.game_round == -1: 725 | if not self.game.chatBox.allChats: 726 | AI.game_round = 1 727 | else: 728 | AI.game_round = self.game.chatBox.allChats[-1].turn + 1 729 | 730 | if AI.life_cycle == 1: 731 | AI.w, AI.h = self.game.mapWidth, self.game.mapHeight 732 | AI.map = Graph((AI.w, AI.h), (self.game.baseX, self.game.baseY)) 733 | AI.latest_map = Graph((AI.w, AI.h), (self.game.baseX, self.game.baseY)) 734 | if AI.game_round > 2: 735 | self.make_id(min_id=INIT_ANTS_NUM + 1) 736 | elif AI.game_round == 1: 737 | self.make_id() 738 | AI.first_id = AI.id 739 | self.send_id() 740 | AI.latest_pos[AI.id] = ((-1, -1), -1) 741 | AI.born_game_round = AI.game_round - 1 742 | 743 | for k, v in AI.map.nodes.items(): 744 | AI.latest_map.nodes[k].wall = v.wall 745 | AI.latest_map.nodes[k].bread = v.bread 746 | AI.latest_map.nodes[k].discovered = v.discovered 747 | AI.latest_map.nodes[k].swamp = v.swamp 748 | AI.latest_map.nodes[k].trap = v.trap 749 | AI.latest_map.nodes[k].grass = v.grass 750 | AI.latest_map.nodes[k].enemy_soldiers = v.enemy_soldiers 751 | 752 | self.pos = (self.game.ant.currentX, self.game.ant.currentY) 753 | print_with_debug("ROUND:", self.game_round, f=AI.out_file) 754 | print_with_debug("POS:", self.pos, f=AI.out_file) 755 | self.search_neighbors() 756 | self.update_map_from_chat_box() 757 | self.update_map_from_neighbors() 758 | 759 | if AI.game_round > 5 and not AI.shot_once: 760 | self.check_for_base() 761 | if self.game.ant.antType == AntType.SARBAAZ.value: 762 | self.update_support_cells_from_chat_box() 763 | self.soldier_update_history() 764 | print_with_debug("soldier history", AI.soldier_path_neighbors_history, f=AI.out_file) 765 | 766 | self.check_for_possible_base_cells() 767 | AI.own_cells_history.append(self.pos) 768 | 769 | print_with_debug("known cells", [k for k, v in AI.map.nodes.items() if v.discovered], f=AI.out_file) 770 | print_with_debug("found history", AI.found_history, f=AI.out_file) 771 | 772 | # @time_measure 773 | def end_round(self): 774 | AI.latest_pos[AI.id] = (self.pos, AI.game_round) 775 | AI.game_round += 1 776 | AI.life_cycle += 1 777 | AI.prev_round_resource = self.game.ant.currentResource.value 778 | AI.prev_hp = self.game.ant.health 779 | AI.prev_es = sum([AI.map.nodes[v].enemy_soldiers for v in 780 | get_view_distance_neighbors(self.pos, AI.w, AI.h, self.game.ant.viewDistance)]) 781 | if not AI.shot_once and self.shot: 782 | AI.shot_once = True 783 | 784 | # @time_measure 785 | def send_msg(self): 786 | if AI.life_cycle > 1 and self.game.ant.antType == AntType.SARBAAZ.value and self.reached_sup_cell: 787 | self.message = encode_support_cell(AI.id, self.pos, AI.w, AI.h, self.resource_count) 788 | self.value = VALUES["sup"] 789 | elif AI.life_cycle > 1 and (not self.shot or self.value == VALUES["enemy_base"]): 790 | if self.direction is None: 791 | print_with_debug("turn", AI.game_round, "id", AI.id, "pos", self.pos, 792 | "worker state", AI.worker_state, 793 | "soldier state", AI.soldier_state, 794 | "map value", self.value, 795 | "enemy base pos", AI.map.enemy_base_pos, f=AI.out_file) 796 | if AI.map is not None: 797 | dis_list = get_view_distance_neighbors(self.pos, AI.w, AI.h, 4) 798 | neighbors = {pos: n for pos, n in AI.map.nodes.items() if 799 | pos in dis_list} 800 | if self.reached_sup_cell: 801 | neighbors = {pos: Node(n.pos, n.discovered, n.wall, n.swamp, n.trap, 0, 0, 0, 0, 0, n.enemy_soldiers) for pos, n in neighbors.items()} 802 | self.encoded_neighbors = encode_graph_nodes(self.pos, 803 | neighbors, 804 | AI.w, AI.h, 805 | self.game.viewDistance, 806 | AI.id, self.direction, 807 | self.shot, 808 | AI.map.enemy_base_pos) 809 | 810 | self.message = self.encoded_neighbors 811 | elif AI.life_cycle > 1 and self.shot and self.game.ant.antType == AntType.SARBAAZ.value and AI.soldier_state == SoldierState.Null: 812 | possible_cells = get_view_distance_neighbors(AI.latest_pos[AI.id][0], AI.w, 813 | AI.h, 6, exact=True) 814 | possible_cells = [p for p in possible_cells if 815 | p not in AI.soldier_path_neighbors_history] 816 | AI.possible_base_cells = list(set(AI.possible_base_cells). 817 | intersection(possible_cells)) 818 | print_with_debug("tell them", AI.latest_pos[AI.id][0], possible_cells, AI.own_cells_history[-3], 819 | f=AI.out_file) 820 | self.message = encode_possible_cells(AI.id, AI.latest_pos[AI.id][0], 821 | AI.own_cells_history[-3], 822 | AI.w, AI.h, possible_cells) 823 | print_with_debug(self.message, f=AI.out_file) 824 | self.direction = solve_bt(AI.map, self.pos, max_distance=5) 825 | AI.soldier_state = SoldierState.HasBeenShot 826 | self.value = VALUES["shot"] 827 | 828 | def determine_worker_state(self): 829 | # TODO discuss the logic and improve 830 | # AI.worker_state = WorkerState.Exploring 831 | total_grass = sum([v.grass for k, v in AI.map.nodes.items()]) 832 | total_bread = sum([v.bread for k, v in AI.map.nodes.items()]) 833 | diff = total_grass - total_bread 834 | if -20 <= diff <= 20 or diff > 20 or total_bread == 0: 835 | AI.worker_state = WorkerState.GrassOnly 836 | elif diff < -20 or total_grass == 0: 837 | AI.worker_state = WorkerState.BreadOnly 838 | else: 839 | AI.worker_state = WorkerState.Exploring 840 | 841 | def worker_explore(self): 842 | # third version (BT) 843 | d = solve_bt(AI.map, self.pos) 844 | return d 845 | 846 | def is_radius_fully_discovered(self, size): 847 | for i in range(self.pos[0] - size, self.pos[0] + size + 1): 848 | for j in range(self.pos[1] - size, self.pos[1] + size + 1): 849 | pos = tuple(map(sum, zip(self.pos, (i, j)))) 850 | pos = fix(pos, AI.w, AI.h) 851 | if not AI.map.nodes[pos].discovered: 852 | return False 853 | return True 854 | 855 | def calculate_score(self, size): 856 | # right -> up -> left -> down 857 | d = [[(0, -size), (size, size)], 858 | [(-size, -size), (size, 0)], 859 | [(-size, -size), (0, size)], 860 | [(-size, 0), (size, size)]] 861 | scores = [0, 0, 0, 0] 862 | # calculate the base scores based on number of non-discovered cells 863 | for k, dd in enumerate(d): 864 | start = tuple(map(sum, zip(self.pos, dd[0]))) 865 | finish = tuple(map(sum, zip(self.pos, dd[1]))) 866 | for i in range(start[0], finish[0] + 1): 867 | for j in range(start[1], finish[1] + 1): 868 | pos = fix((i, j), AI.w, AI.h) 869 | if not AI.map.nodes[pos].discovered: 870 | scores[k] += 1 871 | if AI.map.nodes[pos].discovered and pos != self.pos: 872 | scores[k] -= 1 873 | scores[k] -= int(AI.map.nodes[pos].ally_workers > 0) 874 | 875 | # remove a direction's score if we are facing a wall 876 | # based on path existence (check 3 neighbor walls) 877 | # right -> up -> left -> down 878 | d = [(1, 0), (0, -1), (-1, 0), (0, 1)] 879 | for i, dd in enumerate(d): 880 | pos = tuple(map(sum, zip(self.pos, dd))) 881 | pos = fix(pos, AI.w, AI.h) 882 | if AI.map.nodes[pos].discovered and AI.map.nodes[pos].wall: 883 | scores[(i + 1) % 4] = scores[i] if scores[(i + 1) % 4] != -500 else -500 884 | scores[(i + 3) % 4] = scores[i] if scores[(i + 3) % 4] != -500 else -500 885 | scores[i] = -500 886 | elif not AI.map.nodes[pos].discovered and AI.map.nodes[pos].wall: 887 | print_with_debug("HUGE MOTHERFUCKING ERROR!", f=AI.out_file) 888 | 889 | return scores 890 | 891 | # @time_measure 892 | def check_for_base(self): 893 | hp = self.game.ant.health 894 | neighbors = get_view_distance_neighbors(AI.latest_pos[AI.id][0], AI.w, AI.h, 895 | self.game.ant.viewDistance) 896 | cond = (hp == AI.prev_hp - BASE_DMG and AI.prev_es == 0) or \ 897 | (hp == AI.prev_hp - BASE_DMG - SOLDIER_DMG and AI.prev_es == 1) or \ 898 | (hp == AI.prev_hp - BASE_DMG - 2 * SOLDIER_DMG and AI.prev_es == 2) 899 | print_with_debug("HERE IS ES", [AI.map.nodes[v] for v in neighbors if AI.map.nodes[v].enemy_soldiers > 0], 900 | f=AI.out_file) 901 | if cond: 902 | print_with_debug("YESSSSS I GOT SHOTTTTTTTTTT", f=AI.out_file) 903 | self.shot = True 904 | if AI.soldier_state == SoldierState.Null or AI.soldier_state == SoldierState.Explorer_Supporter: 905 | AI.soldier_state = SoldierState.HasBeenShot 906 | 907 | def find_possible_base_cells(self): 908 | for target in AI.soldier_targets: 909 | possible_cells = Utils.get_view_distance_neighbors(target, AI.w, 910 | AI.h, 6, True) 911 | possible_cells = [p for p in possible_cells if 912 | not AI.map.nodes[p].discovered and 913 | not AI.map.nodes[p].wall] 914 | AI.possible_base_cells = list(set(AI.possible_base_cells) 915 | .intersection(possible_cells)) 916 | if not AI.possible_base_cells: 917 | print_with_debug("HUGE MOTHERFUCKING ERROR!", f=AI.out_file) 918 | 919 | def chosse_best_target(self): 920 | if len(AI.soldier_targets) == 1: 921 | return AI.soldier_targets[0] 922 | 923 | if AI.soldier_targets: 924 | costs = [] 925 | for target in AI.soldier_targets: 926 | costs.append(len(AI.map.get_path(AI.map.nodes[AI.map.base_pos], AI.map.nodes[target]))) 927 | min_i = AI.soldier_targets.index(min(costs)) 928 | return AI.soldier_targets[min_i] 929 | 930 | # @time_measure 931 | def soldier_update_history(self): 932 | if not self.shot: 933 | neighbors = get_view_distance_neighbors(AI.latest_pos[AI.id][0], AI.w, AI.h, 6) 934 | new_neighbors = [p for p in neighbors if p not in AI.soldier_path_neighbors_history] 935 | AI.soldier_path_neighbors_history += new_neighbors.copy() 936 | 937 | # @time_measure 938 | def check_for_possible_base_cells(self): 939 | possible_msgs = [msg.text for msg in 940 | self.game.chatBox.allChats[-MAX_MESSAGES_PER_TURN:] if 941 | msg.text.startswith("sh") and not msg.text.startswith("sc") and 942 | msg.turn == AI.game_round - 1] 943 | if AI.life_cycle == 1: 944 | possible_msgs = [msg.text for msg in self.game.chatBox.allChats if 945 | msg.text.startswith("sh") and not msg.text.startswith("sc")] 946 | 947 | for m in possible_msgs: 948 | print_with_debug("possible msg:", m) 949 | ant_id, pos, prev_pos, possible_cells = decode_possible_cells(m, AI.w, AI.h) 950 | if not AI.possible_base_cells: 951 | AI.possible_base_cells = possible_cells 952 | else: 953 | AI.possible_base_cells = list(set(AI.possible_base_cells). 954 | intersection(possible_cells)) 955 | if (prev_pos, pos) not in AI.near_base_safe_cells: 956 | AI.near_base_safe_cells.append((prev_pos, pos)) 957 | 958 | def get_soldier_first_move_to_discover(self, init=True): 959 | if init: 960 | AI.latest_map.bfs(AI.map.nodes[self.pos]) 961 | move, AI.exploration_target = AI.latest_map.get_first_move_to_discover( 962 | AI.map.nodes[self.pos], self.pos, len(AI.ids[self.game.ant.antType]), AI.id, AI.ids[self.game.ant.antType] 963 | ) 964 | return Direction.get_value(move) 965 | 966 | def get_soldier_first_node_to_support(self): 967 | return Direction.get_value( 968 | AI.map.step(self.pos, AI.map.get_best_node_to_support(self.pos)) 969 | ) 970 | 971 | def get_first_move_to_target(self, src, dest, unsafe_cells=None, name='soldier'): 972 | print_with_debug(src, dest, f=AI.out_file) 973 | print_with_debug(AI.map.get_path_with_non_discovered(AI.map.nodes[src], AI.map.nodes[dest], unsafe_cells), 974 | f=AI.out_file) 975 | return Direction.get_value( 976 | AI.map.step( 977 | src, AI.map.get_path_with_non_discovered(AI.map.nodes[src], AI.map.nodes[dest], unsafe_cells, name) 978 | ) 979 | ) 980 | 981 | # @time_measure 982 | def handle_base(self): 983 | if AI.map.enemy_base_pos is not None and \ 984 | AI.soldier_state == SoldierState.Null: 985 | AI.soldier_state = SoldierState.BK_GoingNearEnemyBase 986 | near_base_cells = get_view_distance_neighbors( 987 | AI.map.enemy_base_pos, AI.w, AI.h, BASE_RANGE + 1, exact=True) 988 | # TODO manhattan dist or bfs? 989 | distances = [manhattan_dist(self.pos, p, AI.w, AI.h) for p in 990 | near_base_cells] 991 | candidates_idx = sorted(enumerate(distances), key=lambda x: x[1])[:5] 992 | # candidates_idx = self.remove_occupied_from_candidates(candidates_idx) 993 | AI.chosen_near_base_cell_BK = near_base_cells[random.choice(candidates_idx)[0]] 994 | print_with_debug("BASE WAS FOUND STATE", 995 | "pos", self.pos, 996 | "near base cells", near_base_cells, 997 | "distances", distances, 998 | "chosen near cell BK", AI.chosen_near_base_cell_BK, 999 | f=AI.out_file) 1000 | 1001 | # going to the chosen cell near enemy base 1002 | if AI.soldier_state == SoldierState.BK_GoingNearEnemyBase: 1003 | if self.pos == AI.chosen_near_base_cell_BK: 1004 | AI.soldier_state = SoldierState.BK_StayingNearBase 1005 | AI.chosen_near_base_cell_BK = None 1006 | print_with_debug("BK GOING NEAR ENEMY BASE STATE", 1007 | "REACHED DEST", 1008 | "pos", self.pos, 1009 | "chosen near cell BK", 1010 | AI.chosen_near_base_cell_BK, 1011 | f=AI.out_file) 1012 | else: 1013 | self.direction = self.get_first_move_to_target(self.pos, 1014 | AI.chosen_near_base_cell_BK) 1015 | print_with_debug("BK GOING NEAR ENEMY BASE STATE", 1016 | "pos", self.pos, 1017 | "chosen near cell BK", 1018 | AI.chosen_near_base_cell_BK, 1019 | f=AI.out_file) 1020 | 1021 | if AI.soldier_state == SoldierState.BK_StayingNearBase: 1022 | # TODO decide to either stay or attack 1023 | # ATTACK 1024 | self.direction = Direction.CENTER.value 1025 | ally_s = len( 1026 | [a for a in self.game.ant.getMapRelativeCell(0, 0).ants if 1027 | a.antType == AntType.SARBAAZ.value and a.antTeam == AntTeam.ALLIED.value]) 1028 | print_with_debug("BK STAYING NEAR BASE STATE", 1029 | "pos", self.pos, 1030 | "allies", ally_s, 1031 | f=AI.out_file) 1032 | # TODO add other attacking conditions 1033 | if ally_s >= ALLIES_REQUIRED_TO_ATTACK: 1034 | AI.soldier_state = SoldierState.AttackingBase 1035 | 1036 | # @time_measure 1037 | def handle_shot(self): 1038 | if AI.soldier_state == SoldierState.HasBeenShot: 1039 | self.direction = Direction.get_value(AI.map.step(AI.latest_pos[AI.id][0], self.pos)) 1040 | if AI.latest_pos[AI.id][0] == self.pos: 1041 | self.direction = Direction.get_random_direction() 1042 | print_with_debug("GOT SHOT STATE", 1043 | "prev pos", AI.latest_pos[AI.id][0], 1044 | "pos", self.pos, 1045 | "dir", self.direction, 1046 | f=AI.out_file) 1047 | 1048 | if AI.soldier_state == SoldierState.Null and AI.near_base_safe_cells: 1049 | AI.soldier_state = SoldierState.BU_GoingNearEnemyBase 1050 | # TODO manhattan dist or bfs? 1051 | distances = [manhattan_dist(self.pos, p[0], AI.w, AI.h) for p in 1052 | AI.near_base_safe_cells] 1053 | candidates_idx = sorted(enumerate(distances), key=lambda x: x[1])[:5] 1054 | # candidates_idx = self.remove_occupied_from_candidates(candidates_idx) 1055 | AI.chosen_near_base_cell_BU = AI.near_base_safe_cells[random.choice(candidates_idx)[0]] 1056 | print_with_debug("FOUND SHOT MSG STATE", 1057 | "pos", self.pos, 1058 | "near base cells", AI.near_base_safe_cells, 1059 | "distances", distances, 1060 | "chosen cell BU", 1061 | AI.chosen_near_base_cell_BU, 1062 | f=AI.out_file) 1063 | 1064 | if AI.soldier_state == SoldierState.BU_GoingNearEnemyBase: 1065 | if self.pos == AI.chosen_near_base_cell_BU[0]: 1066 | AI.soldier_state = SoldierState.BU_StayingNearBase 1067 | print_with_debug("BU GOING NEAR ENEMY BASE STATE", 1068 | "REACHED DEST", 1069 | "pos", self.pos, 1070 | "chosen cell BU", 1071 | AI.chosen_near_base_cell_BU, 1072 | f=AI.out_file) 1073 | else: 1074 | self.direction = self.get_first_move_to_target(self.pos, AI.chosen_near_base_cell_BU[0]) 1075 | print_with_debug("BU GOING NEAR ENEMY BASE STATE", 1076 | "pos", self.pos, 1077 | "dir", self.direction, 1078 | "chosen cell BU", 1079 | AI.chosen_near_base_cell_BU, 1080 | f=AI.out_file) 1081 | 1082 | if AI.soldier_state == SoldierState.BU_StayingNearBase: 1083 | self.direction = Direction.CENTER.value 1084 | ally_s = len([a for a in self.game.ant.getMapRelativeCell(0, 0).ants if 1085 | a.antType == AntType.SARBAAZ.value and a.antTeam == AntTeam.ALLIED.value]) 1086 | print_with_debug("BU STAYING NEAR BASE STATE", 1087 | "pos", self.pos, 1088 | "allies", ally_s, 1089 | f=AI.out_file) 1090 | # TODO add other attacking conditions 1091 | if ally_s >= ALLIES_REQUIRED_TO_ATTACK: 1092 | if AI.possible_base_cells: 1093 | AI.attack_random_target = random.choice(AI.possible_base_cells) 1094 | else: 1095 | # TODO what to do? 1096 | pass 1097 | AI.soldier_state = SoldierState.AttackingBase 1098 | 1099 | if AI.soldier_state == SoldierState.AttackingBase: 1100 | if AI.map.enemy_base_pos is not None: 1101 | if self.pos == AI.map.enemy_base_pos: 1102 | self.direction = Direction.CENTER.value 1103 | else: 1104 | self.direction = self.get_first_move_to_target(self.pos, AI.map.enemy_base_pos) 1105 | print_with_debug("ATT BASE FOUND", 1106 | "pos", self.pos, 1107 | "target", AI.map.enemy_base_pos, 1108 | "dir", self.direction, 1109 | f=AI.out_file) 1110 | else: 1111 | if AI.attack_random_target is not None: 1112 | if self.pos != AI.attack_random_target: 1113 | self.direction = self.get_first_move_to_target(self.pos, AI.attack_random_target) 1114 | else: 1115 | self.direction = Direction.CENTER.value 1116 | print_with_debug("ATT BASE NOT FOUND", 1117 | "dir from random", 1118 | "pos", self.pos, 1119 | "random target", AI.attack_random_target, 1120 | "dir", self.direction, 1121 | f=AI.out_file) 1122 | else: 1123 | pass 1124 | 1125 | def get_first_move_to_go(self): 1126 | bread_number = 0 1127 | grass_number = 0 1128 | AI.map.bfs(AI.map.nodes[self.pos]) 1129 | distance_to_base = AI.map.bfs_info['dist'][AI.map.base_pos] 1130 | reachable_resource = AI.map.get_reachable_resource_from_base() 1131 | if distance_to_base < MIN_DIST_SUPPORT: 1132 | return self.discover_wrapper() 1133 | 1134 | for d in self.visible_bread: 1135 | num, pos = d["number"], d["pos"] 1136 | if AI.map.bfs_info['dist'].get(pos) is not None and pos in reachable_resource: 1137 | bread_number += num 1138 | 1139 | for d in self.visible_grass: 1140 | num, pos = d["number"], d["pos"] 1141 | if AI.map.bfs_info['dist'].get(pos) is not None and pos in reachable_resource: 1142 | grass_number += num 1143 | 1144 | best_dir = Direction.CENTER.value 1145 | 1146 | if self.get_value(bread_number, grass_number, distance_to_base) >= VALUE_TO_SUPPORT: 1147 | visible_cells = get_view_distance_neighbors(self.pos, *AI.map.dim, view=3, exact=False, sort=False) 1148 | best_value = sum( 1149 | [ 1150 | (AI.map.nodes[cell].bread + AI.map.nodes[cell].grass) / (manhattan_dist( 1151 | self.pos, cell, *AI.map.dim 1152 | ) or 1) for cell in visible_cells if AI.map.nodes[cell].bread + AI.map.nodes[cell].grass > 0 1153 | ] 1154 | ) 1155 | AI.exploration_target = None 1156 | if self.pos not in AI.sup_cells: 1157 | AI.sup_cells.append(self.pos) 1158 | self.reached_sup_cell = True 1159 | self.resource_count = bread_number + grass_number 1160 | neighbors = AI.map.get_neighbors(self.pos) 1161 | 1162 | for neighbor in neighbors: 1163 | visible_cells = get_view_distance_neighbors(neighbor, *AI.map.dim, view=3, exact=False, sort=False) 1164 | number_of_resource = sum( 1165 | [ 1166 | ( 1167 | AI.map.nodes[cell].bread + AI.map.nodes[cell].grass 1168 | ) for cell in visible_cells if AI.map.nodes[cell].bread + AI.map.nodes[cell].grass > 0 1169 | ] 1170 | ) 1171 | if number_of_resource < bread_number + grass_number: 1172 | continue 1173 | value = sum( 1174 | [ 1175 | (AI.map.nodes[cell].bread + AI.map.nodes[cell].grass) / (manhattan_dist( 1176 | neighbor, cell, *AI.map.dim 1177 | ) or 1) for cell in visible_cells if AI.map.nodes[cell].bread + AI.map.nodes[cell].grass > 0 1178 | ] 1179 | ) 1180 | if value > best_value: 1181 | best_value = value 1182 | best_dir = Direction.get_value(AI.map.step(self.pos, neighbor)) 1183 | return best_dir 1184 | 1185 | if not AI.exploration_target and AI.sup_cells and self.pos not in AI.sup_cells: 1186 | AI.exploration_target = AI.sup_cells[-1] 1187 | 1188 | return self.discover_wrapper() 1189 | 1190 | def get_value(self, bread_number, grass_number, distance_to_base): 1191 | return bread_number + grass_number + distance_to_base 1192 | 1193 | def discover_wrapper(self): 1194 | if AI.exploration_target is None: 1195 | return self.get_soldier_first_move_to_discover() 1196 | else: 1197 | if self.pos == AI.exploration_target: 1198 | AI.exploration_target = None 1199 | return self.get_soldier_first_move_to_discover() 1200 | else: 1201 | return self.get_first_move_to_target(self.pos, AI.exploration_target) 1202 | 1203 | def remove_occupied_from_candidates(self, candidates_idx): 1204 | temp = candidates_idx.copy() 1205 | for pos in [p[0] for p in AI.latest_pos.values()]: 1206 | if pos not in candidates_idx: 1207 | temp.append(pos) 1208 | return temp 1209 | -------------------------------------------------------------------------------- /BT.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from Model import Direction 4 | from Utils import get_view_distance_neighbors, time_measure 5 | 6 | 7 | class BT: 8 | def __init__(self, graph, cur, max_distance=10, start=None): 9 | self.start = start or time.time() 10 | self.graph = graph 11 | self.best_path = None 12 | self.max_distance = max_distance 13 | self.path = max_distance * [-1] 14 | self.best = None 15 | self.visited = set(get_view_distance_neighbors(cur, self.graph.dim[0], 16 | self.graph.dim[1], 4)) 17 | 18 | for pos, node in graph.nodes.items(): 19 | if node.discovered: 20 | self.visited.add(pos) 21 | 22 | def bt(self, cur, dist): 23 | delay = time.time() - self.start 24 | if delay > 0.08: 25 | return 26 | now = len(self.visited) 27 | if not self.best or now > self.best: 28 | self.best_path = self.path.copy() 29 | self.best = now 30 | if dist == self.max_distance: 31 | return 32 | 33 | neighbors = self.graph.get_neighbors_with_not_discovered_nodes(cur) 34 | for next_node in neighbors: 35 | vis = set(get_view_distance_neighbors(next_node, self.graph.dim[0], 36 | self.graph.dim[1], 4)) 37 | not_changed = self.visited.copy() 38 | self.visited |= vis 39 | self.path[dist] = next_node 40 | self.bt(next_node, dist + 1) 41 | self.visited = not_changed 42 | 43 | 44 | def solve_bt(graph, pos, max_distance=10, start=None): 45 | self = BT(graph, pos, max_distance, start) 46 | self.bt(pos, 0) 47 | self.best_path = [p for p in self.best_path if p != -1] 48 | if not self.best_path: 49 | return 0 50 | return Direction.get_value(self.graph.step(pos, self.best_path[0])) 51 | -------------------------------------------------------------------------------- /Controller.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import threading 4 | import traceback 5 | from queue import Queue 6 | from threading import Thread 7 | from AI import AI 8 | from Model import CurrentState, Direction, Game, GameConfig, ServerConstants 9 | from Network import Network 10 | import json 11 | import time 12 | 13 | 14 | class Controller: 15 | def __init__(self): 16 | self.sending_flag = True 17 | self.gameConfig = None 18 | self.conf = {} 19 | self.network = None 20 | self.queue = Queue() 21 | self.client = AI() 22 | self.argNames = ["AICHostIP", "AICHostPort", "AICToken", "AICRetryDelay"] 23 | self.argDefaults = [ 24 | "127.0.0.1", 25 | 7099, 26 | "00000000000000000000000000000000", 27 | "1000", 28 | ] 29 | self.turn_num = 0 30 | 31 | def handle_message(self, message): 32 | if message[ServerConstants.KEY_TYPE] == ServerConstants.MESSAGE_TYPE_INIT: 33 | self.handle_init_message(message[ServerConstants.KEY_INFO]) 34 | 35 | elif message[ServerConstants.KEY_TYPE] == ServerConstants.MESSAGE_TYPE_TURN: 36 | gameStatus = CurrentState(message[ServerConstants.KEY_INFO]) 37 | threading.Thread(target=self.launch_on_thread, args=([gameStatus])).start() 38 | 39 | elif message[ServerConstants.KEY_TYPE] == ServerConstants.MESSAGE_TYPE_KILL: 40 | exit(4) 41 | 42 | elif message[ServerConstants.KEY_TYPE] == ServerConstants.MESSAGE_TYPE_DUMMY: 43 | pass 44 | 45 | def launch_on_thread(self, world): 46 | # try: 47 | self.handle_turn_message(world) 48 | 49 | # except Exception as e: 50 | # print("Error in client:") 51 | # print(e) 52 | 53 | def send_direction_message(self, direction): 54 | self.network.send({"type": 1, "info": {"direction": direction}}) 55 | 56 | def send_chat_message(self, chat, value): 57 | self.network.send({"type": 2, "info": {"message": chat, "value": value}}) 58 | 59 | def send_end_message(self): 60 | self.network.send({"type": 6, "info": {}}) 61 | 62 | def handle_turn_message(self, currentState): 63 | self.client = AI() 64 | game = Game() 65 | game.initGameConfig(self.gameConfig) 66 | game.setCurrentState(currentState) 67 | self.client.game = game 68 | start = time.time() * 1000 69 | (message, value, direction) = self.client.turn() 70 | diff = time.time() * 1000 - start 71 | if diff > 2000: 72 | pass 73 | elif diff > 1000: 74 | self.send_direction_message(Direction.CENTER.value) 75 | else: 76 | if direction is not None: 77 | self.send_direction_message(direction) 78 | if message is not None and value is not None: 79 | self.send_chat_message(message, value) 80 | self.send_end_message() 81 | 82 | def handle_init_message(self, message): 83 | self.gameConfig = GameConfig(message) 84 | 85 | def start(self): 86 | self.read_settings() 87 | self.network = Network( 88 | ip=self.conf[self.argNames[0]], 89 | port=int(self.conf[self.argNames[1]]), 90 | token=self.conf[self.argNames[2]], 91 | message_handler=self.handle_message, 92 | ) 93 | self.network.connect() 94 | Thread().start() 95 | 96 | def read_settings(self): 97 | if os.environ.get(self.argNames[0]) is None: 98 | for i in range(len(self.argNames)): 99 | self.conf[self.argNames[i]] = self.argDefaults[i] 100 | else: 101 | for i in range(len(self.argNames)): 102 | self.conf[self.argNames[i]] = os.environ.get(self.argNames[i]) 103 | 104 | def terminate(self): 105 | print("finished!") 106 | self.network.close() 107 | self.sending_flag = False 108 | 109 | 110 | if __name__ == "__main__": 111 | c = Controller() 112 | # if len(sys.argv) > 1 and sys.argv[1] == '--verbose': 113 | # DEBUGGING_MODE = True 114 | c.start() 115 | -------------------------------------------------------------------------------- /Model.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import * 3 | import random 4 | from datetime import datetime 5 | 6 | 7 | class Ant: 8 | antType: int 9 | antTeam: int 10 | currentResource: "Resource" 11 | currentX: int 12 | currentY: int 13 | health: int 14 | visibleMap: "Map" 15 | attackDistance: int 16 | viewDistance: int 17 | # near by attacks 18 | attacks: List["Attack"] 19 | 20 | def __init__( 21 | self, 22 | ant_type: int, 23 | ant_team: int, 24 | currentResource: "Resource", 25 | currentX: int, 26 | currentY: int, 27 | health: int, 28 | visibleMap: "Map", 29 | attackDistance: int, 30 | viewDistance: int, 31 | attacks: List["Attack"], 32 | ): 33 | self.antType = ant_type 34 | self.antTeam = ant_team 35 | self.currentX = currentX 36 | self.currentY = currentY 37 | self.currentResource = currentResource 38 | self.visibleMap = visibleMap 39 | self.health = health 40 | self.attackDistance = attackDistance 41 | self.viewDistance = viewDistance 42 | self.attacks = attacks 43 | 44 | @classmethod 45 | def createAntXY(cls, ant_type: int, ant_team: int, currentX: int, currentY: int): 46 | return cls(ant_type, ant_team, None, currentX, currentY, -1, None, -1, -1, []) 47 | 48 | @classmethod 49 | def createCurrentAnt( 50 | cls, 51 | ant_type: int, 52 | ant_team: int, 53 | currentState: "CurrentState", 54 | visibleMap: "Map", 55 | attackDistance: int, 56 | viewDistance: int, 57 | ): 58 | return cls( 59 | ant_type, 60 | ant_team, 61 | Resource( 62 | currentState.current_resource_type, currentState.current_resource_value 63 | ), 64 | currentState.current_x, 65 | currentState.current_y, 66 | currentState.health, 67 | visibleMap, 68 | attackDistance, 69 | viewDistance, 70 | currentState.attacks, 71 | ) 72 | 73 | def getMapRelativeCell(self, x, y): 74 | return self.visibleMap.getRelativeCell(x, y) 75 | 76 | def getNeightbourCell(self, x, y): 77 | return self.getMapRelativeCell(x, y) 78 | 79 | def getLocationCell(self): 80 | return self.getNeightbourCell(0, 0) 81 | 82 | 83 | class Map: 84 | cells: List[List["Cell"]] 85 | width: int 86 | height: int 87 | antCurrentX: int 88 | antCurrentY: int 89 | 90 | def __init__( 91 | self, 92 | cells: List["Cell"], 93 | width: int, 94 | height: int, 95 | currentX: int, 96 | currentY: int, 97 | ): 98 | super().__init__() 99 | self.cells = cells 100 | self.width = width 101 | self.height = height 102 | self.antCurrentX = currentX 103 | self.antCurrentY = currentY 104 | 105 | def getRelativeCell(self, dx: int, dy: int): 106 | x = (self.antCurrentX + dx) % self.width 107 | y = (self.antCurrentY + dy) % self.height 108 | if x < 0: 109 | x += self.width 110 | if y < 0: 111 | y += self.height 112 | return self.cells[x][y] 113 | 114 | 115 | class Cell: 116 | x = 0 117 | y = 0 118 | type = 0 119 | resource_value = 0 120 | resource_type = 0 121 | ants = [] 122 | 123 | def __init__(self, x, y, type, resource_value, resource_type): 124 | self.x = x 125 | self.y = y 126 | self.type = type 127 | self.ants = [] 128 | self.resource_value = resource_value 129 | self.resource_type = resource_type 130 | 131 | 132 | class Resource: 133 | type: int 134 | value: int 135 | 136 | def __init__(self, type: int, value: int): 137 | super().__init__() 138 | self.value = value 139 | self.type = type 140 | 141 | 142 | class Message: 143 | def __init__(self, text: str, turn: int): 144 | self.text = text 145 | self.turn = turn 146 | 147 | 148 | class GameConfig: 149 | map_width: int = 0 150 | map_height: int = 0 151 | ant_type: int = 0 152 | base_x: int = -1 153 | base_y: int = -1 154 | health_kargar: int = 0 155 | health_sarbaaz: int = 0 156 | attack_distance: int = 0 157 | view_distance: int = 0 158 | generate_kargar: int = 0 159 | generate_sarbaz: int = 0 160 | generate_sarbaaz: int = 0 161 | rate_death_resource: int = 0 162 | 163 | def __init__(self, message): 164 | self.__dict__ = message 165 | 166 | 167 | class CurrentState: 168 | around_cells: List["Cell"] = [] 169 | chat_box: List["Message"] = [] 170 | current_x: int = -1 171 | current_y: int = -1 172 | current_resource_type: int = None 173 | current_resource_value: int = 0 174 | health: int = 0 175 | attacks: List["Attack"] = [] 176 | 177 | def __init__(self, message): 178 | self.__dict__ = message 179 | cells = [] 180 | for cel in self.around_cells: 181 | cell_tmp = Cell( 182 | cel["cell_x"], 183 | cel["cell_y"], 184 | cel["cell_type"], 185 | cel["resource_value"], 186 | cel["resource_type"], 187 | ) 188 | cell_tmp.ants = cel["ants"] 189 | cells.append(cell_tmp) 190 | self.around_cells = cells 191 | attacks = [] 192 | for attack in self.attacks: 193 | attacks.append(Attack(attack)) 194 | self.attacks = attacks 195 | 196 | def getVisibleCells(self, height, width): 197 | cells = [[None for i in range(height)] for j in range(width)] 198 | if self.around_cells == None: 199 | return cells 200 | for clientCell in self.around_cells: 201 | cell = Cell( 202 | clientCell.x, 203 | clientCell.y, 204 | clientCell.type, 205 | clientCell.resource_value, 206 | clientCell.resource_type, 207 | ) 208 | for clientAnt in clientCell.ants: 209 | cell.ants.append( 210 | Ant.createAntXY( 211 | clientAnt["ant_type"], 212 | clientAnt["ant_team"], 213 | clientCell.x, 214 | clientCell.y, 215 | ) 216 | ) 217 | cells[cell.x][cell.y] = cell 218 | 219 | return cells 220 | 221 | 222 | class Attack: 223 | attacker_row: int 224 | attacker_col: int 225 | defender_row: int 226 | defender_col: int 227 | is_attacker_enemy: bool 228 | 229 | def __init__(self, message): 230 | self.__dict__ = message 231 | 232 | 233 | class Game: 234 | ant: "Ant" 235 | chatBox: "ChatBox" 236 | antType: int 237 | mapWidth: int 238 | mapHeight: int 239 | baseX: int 240 | baseY: int 241 | healthKargar: int 242 | healthSarbaaz: int 243 | attackDistance: int 244 | viewDistance: int 245 | generateKargar: int 246 | rateDeathResource: int 247 | generateSarbaaz: int 248 | 249 | def __init__(self): 250 | super().__init__() 251 | self.ant = None 252 | self.chatBox = None 253 | self.mapWidth = None 254 | self.mapHeight = None 255 | self.baseX = None 256 | self.baseY = None 257 | self.healthKargar = None 258 | self.healthSarbaaz = None 259 | self.attackDistance = None 260 | self.viewDistance = None 261 | self.generateKargar = None 262 | self.generateSarbaaz = None 263 | self.rateDeathResource = None 264 | 265 | def initGameConfig(self, gameConfig: "GameConfig"): 266 | self.antType = gameConfig.ant_type 267 | self.mapWidth = gameConfig.map_width 268 | self.mapHeight = gameConfig.map_height 269 | self.baseX = gameConfig.base_x 270 | self.baseY = gameConfig.base_y 271 | self.healthKargar = gameConfig.health_kargar 272 | self.healthSarbaaz = gameConfig.health_sarbaaz 273 | self.attackDistance = gameConfig.attack_distance 274 | self.viewDistance = gameConfig.view_distance 275 | self.generateKargar = gameConfig.generate_kargar 276 | self.generateSarbaaz = gameConfig.generate_sarbaaz 277 | self.rateDeathResource = gameConfig.rate_death_resource 278 | 279 | def setCurrentState(self, currentState: "CurrentState"): 280 | self.chatBox = ChatBox(currentState.chat_box) 281 | self.ant = self.initialAntState(currentState) 282 | 283 | def initialAntState(self, currentState: "CurrentState"): 284 | cells = currentState.getVisibleCells(self.mapHeight, self.mapWidth) 285 | my_map = Map( 286 | cells, 287 | self.mapWidth, 288 | self.mapHeight, 289 | currentState.current_x, 290 | currentState.current_y, 291 | ) 292 | return Ant.createCurrentAnt( 293 | self.antType, 294 | AntTeam.ALLIED.value, 295 | currentState, 296 | my_map, 297 | self.attackDistance, 298 | self.viewDistance, 299 | ) 300 | 301 | 302 | class ChatBox: 303 | allChats: List["Chat"] 304 | 305 | def __init__(self, allChats): 306 | super().__init__() 307 | chats = [] 308 | for chat in allChats: 309 | chats.append(Chat(chat["text"], chat["turn"])) 310 | self.allChats = chats 311 | 312 | 313 | class Chat: 314 | text: str 315 | turn: int 316 | 317 | def __init__(self, text, turn): 318 | super().__init__() 319 | self.text = text 320 | self.turn = turn 321 | 322 | 323 | class AntType(Enum): 324 | SARBAAZ = 0 325 | KARGAR = 1 326 | 327 | @staticmethod 328 | def get_value(string: str): 329 | if string == "SARBAAZ": 330 | return AntType.SARBAAZ 331 | if string == "KARGAR": 332 | return AntType.KARGAR 333 | return None 334 | 335 | 336 | class Direction(Enum): 337 | CENTER = 0 338 | RIGHT = 1 339 | UP = 2 340 | LEFT = 3 341 | DOWN = 4 342 | 343 | @staticmethod 344 | def get_value(string: str): 345 | if string == "CENTER": 346 | return Direction.CENTER.value 347 | if string == "RIGHT": 348 | return Direction.RIGHT.value 349 | if string == "UP": 350 | return Direction.UP.value 351 | if string == "LEFT": 352 | return Direction.LEFT.value 353 | if string == "DOWN": 354 | return Direction.DOWN.value 355 | return None 356 | 357 | @staticmethod 358 | def get_random_direction(): 359 | random.seed(datetime.now()) 360 | return random.randint(1, 4) 361 | 362 | @staticmethod 363 | def get_string(dir_val: int): 364 | if dir_val is None: 365 | return "NONE" 366 | return Direction(dir_val).name 367 | 368 | 369 | class CellType(Enum): 370 | BASE = 0 371 | EMPTY = 1 372 | WALL = 2 373 | TRAP = 3 374 | SWAMP = 4 375 | 376 | @staticmethod 377 | def get_value(string: str): 378 | if string == "BASE": 379 | return CellType.BASE.value 380 | if string == "EMPTY": 381 | return CellType.EMPTY.value 382 | if string == "WALL": 383 | return CellType.WALL.value 384 | return None 385 | 386 | 387 | class ResourceType(Enum): 388 | BREAD = 0 389 | GRASS = 1 390 | 391 | @staticmethod 392 | def get_value(string: str): 393 | if string == "BREAD": 394 | return ResourceType.BREAD.value 395 | if string == "GRASS": 396 | return ResourceType.GRASS.value 397 | return None 398 | 399 | 400 | class ServerConstants: 401 | KEY_INFO = "info" 402 | KEY_TURN = "turn" 403 | KEY_TYPE = "type" 404 | 405 | # CONFIG_KEY_IP = "ip" 406 | # CONFIG_KEY_PORT = "port" 407 | CONFIG_KEY_TOKEN = "token" 408 | 409 | MESSAGE_TYPE_INIT = "3" 410 | MESSAGE_TYPE_TURN = "4" 411 | MESSAGE_TYPE_KILL = "7" 412 | MESSAGE_TYPE_DUMMY = "8" 413 | 414 | 415 | class AntTeam(Enum): 416 | ENEMY = 1 417 | ALLIED = 0 418 | 419 | 420 | class ServerMessage: 421 | def __init__(self, type, turn, info): 422 | self.type = type 423 | self.info = info 424 | self.turn = turn 425 | -------------------------------------------------------------------------------- /Network.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import json 3 | import time 4 | 5 | from Model import * 6 | 7 | 8 | class Network(): 9 | def __init__(self, ip, port, token, message_handler): 10 | self.receive_flag = True 11 | self.ip = ip 12 | self.port = port 13 | self.token = token 14 | self.message_handler = message_handler 15 | self.result = b'' 16 | self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 17 | 18 | def connect(self): 19 | connect_attempt = 1 20 | connected = False 21 | error = None 22 | while connected is False and connect_attempt < 11: 23 | try: 24 | print("Trying to connect #{}".format(connect_attempt)) 25 | connect_attempt += 1 26 | self.s.connect((self.ip, self.port)) 27 | connected = True 28 | self.send({"type": ServerConstants.CONFIG_KEY_TOKEN, 29 | "turn": 0, 30 | "info": {ServerConstants.CONFIG_KEY_TOKEN: self.token} 31 | }) 32 | init = self.receive() 33 | if init[ServerConstants.KEY_TYPE] == "wrong token": 34 | raise ConnectionRefusedError("wrong token") 35 | elif not init[ServerConstants.KEY_TYPE] == ServerConstants.MESSAGE_TYPE_INIT: 36 | self.close() 37 | raise IOError("first message was not init") 38 | except Exception as e: 39 | print("error while connecting to server", e) 40 | error = e 41 | time.sleep(2) 42 | continue 43 | print("connected to server!") 44 | self.message_handler(init) 45 | self.start_receiving() 46 | if connected is False: 47 | print('Cant connect to server, ERROR: {}'.format(error)) 48 | 49 | def send(self, message): 50 | j_obj = json.dumps(message, default = str) 51 | self.s.send(j_obj.encode('UTF-8')) 52 | self.s.send(b'\x00') 53 | 54 | def receive(self): 55 | while self.receive_flag: 56 | self.result += self.s.recv(1024) 57 | if b'\x00' in self.result: 58 | ans = json.loads(self.result[:self.result.index(b'\x00')].decode('UTF-8')) 59 | self.result = self.result[self.result.index(b'\x00') + 1:] 60 | return ans 61 | 62 | def start_receiving(self): 63 | import threading 64 | 65 | def run(): 66 | while self.receive_flag: 67 | try: 68 | self.message_handler(self.receive()) 69 | except ConnectionError: 70 | print("disconnected from server!") 71 | self.close() 72 | break 73 | 74 | tr = threading.Thread(target=run, daemon=False) 75 | tr.start() 76 | 77 | def terminate(self): 78 | self.receive_flag = False 79 | 80 | def close(self): 81 | self.terminate() 82 | self.s.close() 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Game Doc : https://github.com/SharifAIChallenge/AIC21-Doc/blob/main/2-Game-Doc.md 2 | 3 | [Certificate](https://drive.google.com/file/d/1edx11xk8h3Rs-0rFybSfkGchs8Ji76hf/view?usp=sharing) 4 | 5 | Our Team : MMA 6 | - [Mohammad Mahdi Ahangaran](https://github.com/mmahdi2414) 7 | - [Mohammad Hosein Zarei](https://github.com/mhezarei) 8 | - [Arman Aminian](https://github.com/arman-aminian) 9 | -------------------------------------------------------------------------------- /Utils.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import Model 4 | 5 | 6 | def print_with_debug(*args, f=None, debug=False): 7 | if debug: 8 | print(*args) 9 | if f is not None: 10 | print(*args, file=f) 11 | 12 | 13 | INIT_ANTS_NUM = 4 14 | MAX_MESSAGES_PER_TURN = 5 15 | MAX_MESSAGES_INIT = 50 16 | 17 | INIT_STRAIGHT_ANTS_MOVES = [[1, 2, 3, 4], 18 | [2, 3, 4, 1], 19 | [3, 4, 1, 2], 20 | [4, 1, 2, 3]] 21 | 22 | INIT_CENTER_ANTS_MOVES1 = [[4, 1, 2, 3], # left-up 23 | [2, 1, 4, 3], # left-down 24 | [4, 3, 2, 1], # right-up 25 | [2, 3, 4, 1]] # right-down 26 | 27 | INIT_CENTER_ANTS_MOVES2 = [[1, 4, 2, 3], # left-up 28 | [1, 2, 4, 3], # left-down 29 | [3, 4, 2, 1], # right-up 30 | [3, 2, 4, 1]] # right-down 31 | 32 | GENERATE_KARGAR = 10 33 | GENERATE_SARBAAZ = 10 34 | WORKER_MAX_CARRYING_RESOURCE_AMOUNT = 10 35 | 36 | GRASS_ONLY_ID = 1 37 | NEW_GRASS_PRIORITY_PER_ROUND = 2 38 | GRASS_PRIORITY_ID1 = 2 39 | GRASS_PRIORITY_ID2 = 3 40 | BREAD_PRIORITY_ID = 4 41 | PRIORITY_GAP = 4 42 | 43 | BASE_DMG = 3 44 | SOLDIER_DMG = 2 45 | HP = [8, 6] # soldier, worker 46 | MAX_TURN_COUNT = 200 47 | BASE_RANGE = 6 48 | SWAMP_TURNS = 3 49 | 50 | VALUE_TO_SUPPORT = 60 51 | MIN_DIST_SUPPORT = 8 52 | EXPLORER_SUPPORT_MAX_ROUND = 80 53 | ATTACKING_SOLDIERS_ROUND = 160 54 | 55 | 56 | VALUES = { 57 | "id": 10000, 58 | "enemy_base": 5000, 59 | "shot": 4000, 60 | "sup": 2000, 61 | "bg": 700, 62 | "disc_gt_3": 600, 63 | "es": 500, 64 | "disc_lt_3": 400, 65 | "bg_add": 300, 66 | "bg_sub": 200, 67 | "none": 100, 68 | } 69 | ALLIES_REQUIRED_TO_ATTACK = 2 70 | 71 | 72 | def reverse_list(lst): 73 | return [ele for ele in reversed(lst)] 74 | 75 | 76 | def get_next_move(cur, path): 77 | if path[0].pos[0] == cur.pos[0]: 78 | if path[0].pos[1] == cur.pos[1] - 1: 79 | return Model.Direction.UP 80 | elif path[0].pos[1] - 1 == cur.pos[1]: 81 | return Model.Direction.DOWN 82 | elif path[0].pos[1] == cur.pos[1]: 83 | if path[0].pos[0] == cur.pos[0] - 1: 84 | return Model.Direction.LEFT 85 | elif path[0].pos[0] - 1 == cur.pos[0]: 86 | return Model.Direction.RIGHT 87 | return Model.Direction.CENTER 88 | 89 | 90 | def fix(pos, w, h): 91 | x = pos[0] % w if pos[0] % w >= 0 else (pos[0] % w) + w 92 | y = pos[1] % h if pos[1] % h >= 0 else (pos[1] % h) + h 93 | return x, y 94 | 95 | 96 | def manhattan_dist(p, q, w, h) -> int: 97 | x_diff = min(abs(p[0] - q[0]), w - abs(p[0] - q[0])) 98 | y_diff = min(abs(p[1] - q[1]), h - abs(p[1] - q[1])) 99 | return x_diff + y_diff 100 | 101 | 102 | def get_view_distance_neighbors(pos, w, h, view: int, exact: bool = False, sort=True): 103 | ret = [] 104 | for i in range(-view, view + 1): 105 | for j in range(-view, view + 1): 106 | p = fix((pos[0] + i, pos[1] + j), w, h) 107 | if exact and manhattan_dist(pos, p, w, h) == view: 108 | ret.append(p) 109 | elif not exact and manhattan_dist(pos, p, w, h) <= view: 110 | ret.append(p) 111 | return sorted(ret) if sort else ret 112 | 113 | 114 | def shortest_path(src, dest, w, h): 115 | if src[0] == dest[0]: # up and down 116 | up = [] 117 | down = [] 118 | i = src[1] 119 | while i != dest[1]: 120 | up.append(fix((src[0], i + 1), w, h)) 121 | i = up[-1][1] 122 | 123 | i = src[1] 124 | while i != dest[1]: 125 | down.append(fix((src[0], i - 1), w, h)) 126 | i = down[-1][1] 127 | 128 | return up if len(up) <= len(down) else down 129 | 130 | if src[1] == dest[1]: # left and right 131 | left = [] 132 | right = [] 133 | i = src[0] 134 | while i != dest[0]: 135 | right.append(fix((i + 1, src[1]), w, h)) 136 | i = right[-1][0] 137 | 138 | i = src[0] 139 | while i != dest[0]: 140 | left.append(fix((i - 1, src[1]), w, h)) 141 | i = left[-1][0] 142 | 143 | return right if len(right) <= len(left) else left 144 | 145 | path1 = shortest_path(src, (dest[0], src[1]), w, h) + shortest_path( 146 | (dest[0], src[1]), dest, w, h) 147 | path2 = shortest_path(src, (src[0], dest[1]), w, h) + shortest_path( 148 | (src[0], dest[1]), dest, w, h) 149 | 150 | 151 | def time_measure(fn): 152 | def wrapper(*args, **kwargs): 153 | now = time.time() 154 | res = fn(*args, **kwargs) 155 | delay = time.time() - now 156 | print_with_debug(f'{fn.__name__} took {delay} seconds!') 157 | 158 | return res 159 | 160 | return wrapper 161 | 162 | 163 | def add_pos_dir(pos, direction, w, h): 164 | temp = [(0, 0), (1, 0), (0, -1), (-1, 0), (0, 1)] 165 | new_pos = tuple(map(sum, zip(pos, temp[direction]))) 166 | new_pos = fix(new_pos, w, h) 167 | return new_pos 168 | 169 | 170 | def handle_exception(fn): 171 | def wrapper(*args, **kwargs): 172 | try: 173 | res = fn(*args, **kwargs) 174 | except Exception as e: 175 | # print_with_debug('ERROR ERROR ERROR') 176 | # print_with_debug(e) 177 | res = '', -50000, Model.Direction.get_random_direction() 178 | # todo: Raise Error 179 | # raise 180 | 181 | return res 182 | 183 | return wrapper 184 | 185 | 186 | def print_map(input_map, pos, f=None): 187 | for j in range(input_map.dim[1]): 188 | for i in range(input_map.dim[0]): 189 | if (i, j) == input_map.base_pos: 190 | if f is not None: 191 | print('B', end='', file=f) 192 | else: 193 | print('B', end='') 194 | elif (i, j) == pos: 195 | if f is not None: 196 | print('P', end='', file=f) 197 | else: 198 | print('P', end='') 199 | elif input_map.nodes[(i, j)].wall: 200 | if f is not None: 201 | print('W', end="", file=f) 202 | else: 203 | print('W', end="") 204 | elif input_map.nodes[(i, j)].swamp and input_map.nodes[(i, j)].trap: 205 | if f is not None: 206 | print('Z', end="", file=f) 207 | else: 208 | print('Z', end="") 209 | elif input_map.nodes[(i, j)].swamp: 210 | if f is not None: 211 | print('S', end="", file=f) 212 | else: 213 | print('S', end="") 214 | elif input_map.nodes[(i, j)].trap: 215 | if f is not None: 216 | print('T', end="", file=f) 217 | else: 218 | print('T', end="") 219 | else: 220 | if f is not None: 221 | print('N' if not input_map.nodes[(i, j)].discovered else 'D', end='', file=f) 222 | else: 223 | print('N' if not input_map.nodes[(i, j)].discovered else 'D', end='') 224 | if f is not None: 225 | print('', file=f) 226 | else: 227 | print() 228 | -------------------------------------------------------------------------------- /graph.py: -------------------------------------------------------------------------------- 1 | import math 2 | import random 3 | from itertools import chain 4 | 5 | import Utils 6 | from Model import Direction 7 | 8 | 9 | class Node: 10 | GRASS_WEIGHT = 1 11 | BREAD_WEIGHT = 1 12 | DISTANCE_WEIGH = 10 13 | GRASS_LIMIT = 10 14 | BREAD_LIMIT = 10 15 | 16 | def __init__(self, pos, discovered, wall=False, swamp=False, trap=False, 17 | bread=0, grass=0, 18 | ally_workers=0, ally_soldiers=0, enemy_workers=0, 19 | enemy_soldiers=0): 20 | # REMEMBER to change the encode/decode function after adding attrs 21 | self.pos = pos 22 | self.discovered = discovered 23 | self.wall = wall 24 | self.swamp = swamp 25 | self.trap = trap 26 | self.bread = bread 27 | self.grass = grass 28 | self.ally_workers = ally_workers 29 | self.ally_soldiers = ally_soldiers 30 | self.enemy_workers = enemy_workers 31 | self.enemy_soldiers = enemy_soldiers 32 | 33 | def __repr__(self): 34 | return f"{self.__dict__}" 35 | 36 | def __eq__(self, other): 37 | if type(self) is type(other): 38 | return self.__dict__ == other.__dict__ 39 | return False 40 | 41 | def get_distance(self, node): 42 | return abs(self.pos[0] - node.pos[0]) + abs(self.pos[1] - node.pos[1]) 43 | 44 | # todo: make get value better(use dest) 45 | # todo: make default dist better 46 | def grass_value(self, src, dest, graph, number=0): 47 | distance = graph.get_shortest_distance(src, self, 'grass', default=math.inf) \ 48 | + graph.get_shortest_distance((dest, 1), self, 'grass', default=math.inf) 49 | return -distance * self.DISTANCE_WEIGH + self.grass + number * self.GRASS_WEIGHT 50 | 51 | def bread_value(self, src, dest, graph, number=0): 52 | distance = graph.get_shortest_distance(src, self, 'bread', default=math.inf) \ 53 | + graph.get_shortest_distance((dest, 1), self, 'bread', default=math.inf) 54 | return -distance * self.DISTANCE_WEIGH + self.bread + number * self.BREAD_WEIGHT 55 | 56 | 57 | class Graph: 58 | TSP_NODE_LIMIT = 5 59 | 60 | def __init__(self, dim, base_pos): 61 | self.base_pos = base_pos 62 | self.dim = dim # width, height 63 | self.enemy_base_pos = None 64 | self.nodes = {} 65 | self.bfs_info = {} 66 | self.edge_nodes = [] 67 | self.shortest_path_info = {'bread': {}, 'grass': {}} 68 | for i in range(dim[0]): 69 | for j in range(dim[1]): 70 | if (i, j) == base_pos: 71 | self.nodes[(i, j)] = Node( 72 | pos=(i, j), 73 | discovered=True, 74 | wall=False 75 | ) 76 | else: 77 | self.nodes[(i, j)] = Node( 78 | pos=(i, j), 79 | discovered=False 80 | ) 81 | 82 | def step(self, src, dest): 83 | if dest[0] - src[0] in [1, -(self.dim[0] - 1)]: 84 | return "RIGHT" 85 | if dest[0] - src[0] in [-1, self.dim[0] - 1]: 86 | return "LEFT" 87 | if dest[1] - src[1] in [-1, self.dim[1] - 1]: 88 | return "UP" 89 | if dest[1] - src[1] in [1, -(self.dim[1] - 1)]: 90 | return "DOWN" 91 | 92 | # @Utils.time_measure 93 | def total_grass_number(self): 94 | res = 0 95 | for pos in self.nodes.keys(): 96 | if not self.nodes[pos].wall: 97 | if self.nodes[pos].grass > 0: 98 | if self.get_path(self.nodes[pos], self.nodes[self.base_pos]) is not None: 99 | # print(pos) 100 | res = res + self.nodes[pos].grass 101 | return res 102 | 103 | # @Utils.time_measure 104 | def total_bread_number(self): 105 | res = 0 106 | # print("###########") 107 | # print("total_bread_number:") 108 | for pos in self.nodes.keys(): 109 | if not self.nodes[pos].wall: 110 | if self.nodes[pos].bread > 0: 111 | if self.get_path(self.nodes[pos], self.nodes[self.base_pos]) is not None: 112 | # print(pos) 113 | res = res + self.nodes[pos].bread 114 | return res 115 | 116 | # @Utils.time_measure 117 | def shortest_path(self, src, dest): 118 | q = [[src]] 119 | visited = [] 120 | 121 | while q: 122 | prev_path = q.pop(0) 123 | node = prev_path[-1] 124 | if node not in visited: 125 | neighbors = self.get_neighbors(node) 126 | for u in neighbors: 127 | path = list(prev_path) 128 | path.append(u) 129 | q.append(path) 130 | if u == dest: 131 | return path 132 | visited.append(node) 133 | 134 | return None 135 | 136 | def set_bread(self, pos, b): 137 | self.nodes[pos].bread = b 138 | 139 | def set_grass(self, pos, g): 140 | self.nodes[pos].grass = g 141 | 142 | def set_ally_workers(self, pos, aw): 143 | self.nodes[pos].ally_workers = aw 144 | 145 | def set_ally_soldiers(self, pos, a_s): 146 | self.nodes[pos].ally_soldiers = a_s 147 | 148 | def set_enemy_workers(self, pos, ew): 149 | self.nodes[pos].enemy_workers = ew 150 | 151 | def set_enemy_soldiers(self, pos, es): 152 | self.nodes[pos].enemy_soldiers = es 153 | 154 | def discover(self, pos, is_wall): 155 | self.nodes[pos].wall = is_wall 156 | self.nodes[pos].discovered = True 157 | 158 | def get_neighbors(self, pos): 159 | if self.nodes[pos].wall or not self.nodes[pos].discovered: 160 | return [] 161 | neighbors = [self.up(pos), self.right(pos), self.down(pos), 162 | self.left(pos)] 163 | neighbors = [n for n in neighbors if not self.nodes[n].wall and 164 | self.nodes[pos].discovered] 165 | return neighbors 166 | 167 | def get_neighbors_with_not_discovered_nodes(self, pos): 168 | if self.nodes[pos].wall and self.nodes[pos].discovered: 169 | return [] 170 | neighbors = [self.up(pos), self.right(pos), self.down(pos), self.left(pos)] 171 | neighbors = [n for n in neighbors if not self.nodes[n].wall or not self.nodes[n].discovered] 172 | return neighbors 173 | 174 | def right(self, pos): 175 | if pos[0] == self.dim[0] - 1: 176 | return 0, pos[1] 177 | return pos[0] + 1, pos[1] 178 | 179 | def left(self, pos): 180 | if pos[0] == 0: 181 | return self.dim[0] - 1, pos[1] 182 | return pos[0] - 1, pos[1] 183 | 184 | def up(self, pos): 185 | if pos[1] == 0: 186 | return pos[0], self.dim[1] - 1 187 | return pos[0], pos[1] - 1 188 | 189 | def down(self, pos): 190 | if pos[1] == self.dim[1] - 1: 191 | return pos[0], 0 192 | return pos[0], pos[1] + 1 193 | 194 | def guess_node(self, node): 195 | # todo: make guessing better 196 | return Node(pos=node.pos, discovered=False, wall=True) 197 | # opposite_node_pos = (self.dim[0] - 1 - node.pos[0], self.dim[1] - 1 - node.pos[1]) 198 | # opposite_node = self.nodes[opposite_node_pos] 199 | # if not opposite_node.discovered or random.randint(1, 5) != 1: 200 | # return Node(pos=node.pos, discovered=False, wall=random.choice([True, False, False, False])) 201 | # return Node( 202 | # pos=node.pos, 203 | # discovered=False, 204 | # wall=opposite_node.wall, 205 | # bread=opposite_node.bread, 206 | # grass=opposite_node.grass 207 | # ) 208 | 209 | def get_node(self, pos): 210 | return self.nodes[pos] if self.nodes[pos].discovered else self.guess_node(self.nodes[pos]) 211 | 212 | def get_worker_weight(self, src, dest): 213 | return int(src.swamp) * Utils.SWAMP_TURNS + 1 214 | 215 | # @Utils.time_measure 216 | def get_shortest_path(self, src, name_of_other_object, number_of_object): 217 | q = [src] 218 | in_queue = {src.pos: True} 219 | dist = {src.pos: 0} 220 | parent = {src.pos: src.pos} 221 | 222 | if (src.discovered and src.wall) or (getattr(src, name_of_other_object) > 0 and number_of_object == 0): 223 | return { 224 | 'dist': dist, 225 | 'parent': parent, 226 | } 227 | 228 | while q: 229 | current_node = q.pop(0) 230 | neighbors = self.get_neighbors_with_not_discovered_nodes(current_node.pos) 231 | in_queue[current_node.pos] = False 232 | 233 | for neighbor in neighbors: 234 | next_node = self.nodes[neighbor] 235 | if getattr(next_node, name_of_other_object) > 0 and number_of_object == 0: 236 | continue 237 | if number_of_object and next_node.trap: 238 | continue 239 | 240 | weight = self.get_worker_weight(current_node, next_node) 241 | if dist.get(next_node.pos) is None or weight + dist[current_node.pos] < dist.get(next_node.pos): 242 | dist[next_node.pos] = weight + dist[current_node.pos] 243 | parent[next_node.pos] = current_node.pos 244 | if not in_queue.get(next_node.pos): 245 | in_queue[next_node.pos] = True 246 | q.append(next_node) 247 | return { 248 | 'dist': dist, 249 | 'parent': parent, 250 | } 251 | 252 | # @Utils.time_measure 253 | def get_path(self, src, dest): 254 | q = [src] 255 | parent = {src.pos: src.pos} 256 | 257 | if src.wall: 258 | return None 259 | 260 | while q: 261 | current_node = q.pop(0) 262 | neighbors = self.get_neighbors(current_node.pos) 263 | 264 | for neighbor in neighbors: 265 | next_node = self.nodes[neighbor] 266 | if parent.get(next_node.pos) is None: 267 | parent[next_node.pos] = current_node.pos 268 | q.append(next_node) 269 | if next_node.pos == dest.pos: 270 | path = [] 271 | last_node_pos = next_node.pos 272 | while last_node_pos != src.pos: 273 | path.append(self.nodes[last_node_pos]) 274 | last_node_pos = parent[last_node_pos] 275 | return list(reversed(path)) 276 | return None 277 | 278 | # @Utils.time_measure 279 | def get_path_with_non_discovered(self, src, dest, unsafe_cells=None, name='soldier'): 280 | if src == dest: 281 | # print('IM IN TARGET') 282 | raise 283 | unsafe_pos = {} 284 | for pos in (unsafe_cells or []): 285 | unsafe_pos.update( 286 | set( 287 | Utils.get_view_distance_neighbors( 288 | pos, self.dim[0], self.dim[1], Utils.BASE_RANGE, exact=False, sort=False 289 | ) 290 | ) 291 | ) 292 | 293 | q = [src] 294 | in_queue = {src.pos: True} 295 | dist = {src.pos: 0} 296 | parent = {src.pos: src.pos} 297 | 298 | if src.wall: 299 | return None 300 | 301 | while q: 302 | current_node = q.pop(0) 303 | in_queue[current_node.pos] = False 304 | neighbors = self.get_neighbors_with_not_discovered_nodes(current_node.pos) 305 | 306 | for neighbor in neighbors: 307 | next_node = self.nodes[neighbor] 308 | if next_node.pos in unsafe_pos: 309 | continue 310 | if parent.get(next_node.pos) is None: 311 | parent[next_node.pos] = current_node.pos 312 | q.append(next_node) 313 | weight = getattr(self, f'get_{name}_weight')(current_node, next_node) 314 | if dist.get(next_node.pos) is None or weight + dist[current_node.pos] < dist.get(next_node.pos): 315 | dist[next_node.pos] = weight + dist[current_node.pos] 316 | parent[next_node.pos] = current_node.pos 317 | if not in_queue.get(next_node.pos): 318 | in_queue[next_node.pos] = True 319 | q.append(next_node) 320 | 321 | return self.get_first_move_from_parent(parent, src.pos, dest.pos) if dist.get(dest.pos) else None 322 | 323 | # @Utils.time_measure 324 | def get_path_with_max_length(self, src, dest, max_len): 325 | q = [src] 326 | parent = {src.pos: src.pos} 327 | 328 | if src.wall: 329 | return None 330 | 331 | while q: 332 | current_node = q.pop(0) 333 | neighbors = self.get_neighbors_with_not_discovered_nodes(current_node.pos) 334 | 335 | for neighbor in neighbors: 336 | next_node = self.nodes[neighbor] 337 | if parent.get(next_node.pos) is None: 338 | parent[next_node.pos] = current_node.pos 339 | 340 | path = [] 341 | last_node_pos = next_node.pos 342 | while last_node_pos != src.pos: 343 | path.append(self.nodes[last_node_pos]) 344 | last_node_pos = parent[last_node_pos] 345 | if len(path) > max_len: 346 | return None 347 | 348 | q.append(next_node) 349 | if next_node.pos == dest.pos: 350 | path = [] 351 | last_node_pos = next_node.pos 352 | 353 | while last_node_pos != src.pos: 354 | path.append(self.nodes[last_node_pos]) 355 | last_node_pos = parent[last_node_pos] 356 | return list(reversed(path)) 357 | return None 358 | 359 | def get_random_nodes(self): 360 | return {pos: self.get_node(pos) for pos in self.nodes.keys()} 361 | 362 | # @Utils.time_measure 363 | def find_all_shortest_path(self, number_of_object, name_of_object, nodes): 364 | for node, is_dest in nodes: 365 | pos = node.pos 366 | key = pos 367 | if is_dest: 368 | key = (pos, 1) 369 | self.shortest_path_info[name_of_object][key] = self.get_shortest_path( 370 | node, 'bread' if name_of_object == 'grass' else 'grass', 371 | number_of_object.get(name_of_object, is_dest) 372 | ) 373 | 374 | # @Utils.time_measure 375 | def get_nearest_grass_nodes(self, src, dest, number_of_object): 376 | number = number_of_object.get('grass', 0) 377 | grass_nodes_temp = [] 378 | for node in self.nodes.values(): 379 | if node.grass > 0 and node.pos != src.pos and node.pos != dest.pos: 380 | grass_nodes_temp.append(node) 381 | 382 | self.find_all_shortest_path( 383 | number_of_object, 'grass', [(src, 0), (dest, 1)] 384 | ) 385 | 386 | # print(grass_nodes_temp) 387 | grass_nodes = [] 388 | for node in grass_nodes_temp: 389 | if self.get_shortest_distance( 390 | (dest, 1), node, 'grass' 391 | ) is not None and self.get_shortest_distance( 392 | src, node, 'grass' 393 | ) is not None: 394 | grass_nodes.append(node) 395 | 396 | return sorted(grass_nodes, key=lambda n: n.grass_value(src, dest, self, number), reverse=True)[ 397 | :self.TSP_NODE_LIMIT] 398 | 399 | # @Utils.time_measure 400 | def get_nearest_bread_nodes(self, src, dest, number_of_object): 401 | number = number_of_object.get('bread', 0) 402 | bread_nodes_temp = [] 403 | for node in self.nodes.values(): 404 | if node.bread > 0 and node.pos != src.pos and node.pos != dest.pos: 405 | bread_nodes_temp.append(node) 406 | 407 | self.find_all_shortest_path( 408 | number_of_object, 'bread', [(src, 0), (dest, 1)] 409 | ) 410 | # print(bread_nodes_temp) 411 | bread_nodes = [] 412 | for node in bread_nodes_temp: 413 | if self.get_shortest_distance( 414 | (dest, 1), node, 'bread' 415 | ) is not None and self.get_shortest_distance( 416 | src, node, 'bread' 417 | ) is not None: 418 | bread_nodes.append(node) 419 | 420 | return sorted(bread_nodes, key=lambda n: n.bread_value(src, dest, self, number), reverse=True)[ 421 | :self.TSP_NODE_LIMIT] 422 | 423 | # @Utils.time_measure 424 | def get_shortest_distance(self, src, dest, name_of_object, default=None): 425 | if isinstance(src, Node): 426 | key = src.pos 427 | else: 428 | key = (src[0].pos, 1) 429 | return self.shortest_path_info[name_of_object].get(key, {}).get('dist', {}).get(dest.pos, default) 430 | 431 | # @Utils.time_measure 432 | def get_shortest_path_from_shortest_path_info(self, src_pos, dest_pos, name_of_object): 433 | parent = self.shortest_path_info[name_of_object][src_pos].get('parent', []) 434 | pos = dest_pos 435 | path = [] 436 | while parent[pos] != pos: 437 | path.append(pos) 438 | pos = parent[pos] 439 | return list(reversed(path)) 440 | 441 | # @Utils.time_measure 442 | def get_first_move_to_enemy_base(self, src_pos): 443 | our_base = self.base_pos 444 | their_base = self.enemy_base_pos or (self.dim[0] - 1 - our_base[0], self.dim[1] - 1 - our_base[1]) 445 | path = self.get_path(self.nodes[src_pos], self.nodes[their_base]) 446 | return self.step(src_pos, path[0].pos) if path else "None" 447 | 448 | # @Utils.time_measure 449 | def get_first_move_to_opposite_node(self, src_pos): 450 | opposite_node_pos = (self.dim[0] - 1 - src_pos[0], self.dim[1] - 1 - src_pos[1]) 451 | path = self.get_path(self.nodes[src_pos], self.nodes[opposite_node_pos]) 452 | return self.step(src_pos, path[0].pos) if path else "None" 453 | 454 | # @Utils.time_measure 455 | def get_edge_nodes(self, src): 456 | q = [src] 457 | parent = {src.pos: src.pos} 458 | dist = {src.pos: 0} 459 | edge_nodes = set() 460 | 461 | if src.wall: 462 | return None 463 | 464 | while q: 465 | current_node = q.pop(0) 466 | neighbors = self.get_neighbors_with_not_discovered_nodes(current_node.pos) 467 | 468 | for neighbor in neighbors: 469 | next_node = self.nodes[neighbor] 470 | if not next_node.discovered: 471 | edge_nodes.add(current_node.pos) 472 | continue 473 | if parent.get(next_node.pos) is None: 474 | parent[next_node.pos] = current_node.pos 475 | dist[next_node.pos] = dist[current_node.pos] + 1 476 | q.append(next_node) 477 | 478 | return { 479 | 'edge_nodes': sorted(list(edge_nodes)), 480 | 'distance': dist, 481 | 'parent': parent, 482 | } 483 | 484 | # @Utils.time_measure 485 | def get_best_list(self, src, each_list_max_size): 486 | if not self.edge_nodes: 487 | self.bfs(src) 488 | edge_nodes = self.edge_nodes 489 | distance = self.bfs_info.get('dist') 490 | parent = self.bfs_info.get('parent') 491 | # print("BEST LIST EDGE NODES", edge_nodes) 492 | 493 | while len(edge_nodes) > each_list_max_size and len( 494 | edge_nodes) % each_list_max_size != 0: 495 | edge_nodes.pop(0) 496 | 497 | all_list = [] 498 | for i in range(len(edge_nodes)): 499 | if all_list and edge_nodes[i] in all_list[0]: 500 | break 501 | all_list.append([]) 502 | for j in range(i, len(edge_nodes), math.ceil(len(edge_nodes) / each_list_max_size)): 503 | all_list[-1].append(edge_nodes[j]) 504 | 505 | # print("ALL LIST", all_list) 506 | mn_value = math.inf 507 | mn_idx = 0 508 | for i in range(len(all_list)): 509 | value = self.get_value_of_list(all_list[i], distance) 510 | if value < mn_value: 511 | mn_idx = i 512 | mn_value = value 513 | 514 | return all_list[mn_idx], parent 515 | 516 | @staticmethod 517 | def get_value_of_list(list_of_candidate, dist): 518 | value = 0 519 | for pos in list_of_candidate: 520 | value += dist.get(pos) 521 | return value / len(list_of_candidate) 522 | 523 | # @Utils.time_measure 524 | def get_first_move_to_discover(self, curr_pos, src_pos, each_list_max_size, my_id, all_ids): 525 | src = self.nodes[src_pos] 526 | # print("FIRST MOVE EDGE NODES", self.edge_nodes) 527 | best_list, parent = self.get_best_list(src, each_list_max_size) 528 | # print(best_list) 529 | idx = random.randint(0, len(best_list) - 1) 530 | ids = all_ids[-each_list_max_size:] 531 | 532 | for i in range(len(ids)): 533 | if ids[-i] == my_id: 534 | idx = i 535 | break 536 | 537 | # print("MY GOAL IS TO REACH", best_list[idx % len(best_list)]) 538 | # print("1", parent, src_pos, best_list[idx % len(best_list)]) 539 | # print("HAHAAHHAHAHAH", self.step(curr_pos.pos, self.get_first_move_from_parent(parent, src_pos, best_list[idx % len(best_list)])), best_list[idx % len(best_list)]) 540 | 541 | return self.step( 542 | curr_pos.pos, self.get_first_move_from_parent( 543 | parent, src_pos, best_list[idx % len(best_list)]) 544 | ), best_list[idx % len(best_list)] 545 | 546 | @staticmethod 547 | def get_first_move_from_parent(parent, src, dest): 548 | last = dest 549 | while parent[last] != src: 550 | last = parent[last] 551 | return last 552 | 553 | # @Utils.time_measure 554 | def bfs(self, src): 555 | self.edge_nodes = [] 556 | q = [src] 557 | in_queue = {src.pos: True} 558 | dist = {src.pos: 0} 559 | parent = {src.pos: src.pos} 560 | 561 | while q: 562 | current_node = q.pop(0) 563 | neighbors = self.get_neighbors_with_not_discovered_nodes(current_node.pos) 564 | in_queue[current_node.pos] = False 565 | 566 | for neighbor in neighbors: 567 | next_node = self.nodes[neighbor] 568 | if not next_node.discovered: 569 | self.edge_nodes.append(current_node.pos) 570 | continue 571 | weight = self.get_soldier_weight(current_node, next_node) 572 | if dist.get(next_node.pos) is None or weight + dist[current_node.pos] < dist.get(next_node.pos): 573 | dist[next_node.pos] = weight + dist[current_node.pos] 574 | parent[next_node.pos] = current_node.pos 575 | if not in_queue.get(next_node.pos): 576 | in_queue[next_node.pos] = True 577 | q.append(next_node) 578 | 579 | # print("EDGE NODES", self.edge_nodes) 580 | self.edge_nodes = list(set(self.edge_nodes)) 581 | self.edge_nodes.sort() 582 | self.bfs_info = { 583 | 'dist': dist, 584 | 'parent': parent, 585 | } 586 | return self.bfs_info 587 | 588 | def get_soldier_weight(self, src, dest): 589 | return int(src.swamp) * Utils.SWAMP_TURNS + 1 590 | 591 | # @Utils.time_measure 592 | def get_best_node_to_support(self, src_pos, grass_weight=1, bread_weight=1, distance_weight=1): 593 | src = self.nodes[src_pos] 594 | best_value = -math.inf 595 | best_pos = None 596 | 597 | dist = self.bfs_info.get('dist') 598 | parent = self.bfs_info.get('parent') 599 | 600 | for node in self.nodes.values(): 601 | poses = Utils.get_view_distance_neighbors(node.pos, self.dim[0], self.dim[1], 3, sort=False) 602 | bread_number = 0 603 | grass_number = 0 604 | distance = dist.get(node.pos, 0) 605 | for pos in poses: 606 | bread_number += self.nodes[pos].bread 607 | grass_number += self.nodes[pos].grass 608 | 609 | value = grass_number * grass_weight + bread_number * bread_weight - distance * distance_weight 610 | if distance and value > best_value: 611 | best_value = value 612 | best_pos = node.pos 613 | 614 | return self.get_first_move_from_parent(parent, src.pos, best_pos) 615 | 616 | # @Utils.time_measure 617 | def get_resource_best_move(self, src_pos, dest_pos, name_of_object, limit, number_of_object): 618 | best_nodes = getattr( 619 | self, f'get_nearest_{name_of_object}_nodes' 620 | )(self.nodes[src_pos], self.nodes[dest_pos], number_of_object) 621 | number_of_bread_need = max(0, limit[name_of_object]['min'] - number_of_object.get(name_of_object, 0)) 622 | if number_of_bread_need == 0: 623 | return Direction.get_value(self.step( 624 | src_pos, self.get_shortest_path_from_shortest_path_info(src_pos, self.base_pos, name_of_object)[0]) 625 | ), name_of_object, self.get_shortest_distance( 626 | self.nodes[src_pos], self.nodes[self.base_pos], name_of_object 627 | ) 628 | if not best_nodes: 629 | return None, None, math.inf 630 | 631 | return Direction.get_value(self.step( 632 | src_pos, self.get_shortest_path_from_shortest_path_info(src_pos, best_nodes[0].pos, name_of_object)[0]) 633 | ), name_of_object, self.get_shortest_distance( 634 | self.nodes[src_pos], self.nodes[best_nodes[0].pos], name_of_object, default=math.inf 635 | ) 636 | 637 | # @Utils.time_measure 638 | def convert_grass_cells_to_wall(self): 639 | converted_map = Graph((self.dim[0], self.dim[1]), self.base_pos) 640 | for pos in self.nodes.keys(): 641 | if self.nodes[pos].grass > 0: 642 | converted_map.nodes[pos] = Node( 643 | pos=pos, 644 | discovered=True, 645 | wall=True 646 | ) 647 | else: 648 | converted_map.nodes[pos] = self.nodes[pos] 649 | return converted_map 650 | 651 | # @Utils.time_measure 652 | def convert_bread_cells_to_wall(self): 653 | converted_map = Graph((self.dim[0], self.dim[1]), self.base_pos) 654 | for pos in self.nodes.keys(): 655 | if self.nodes[pos].bread > 0: 656 | converted_map.nodes[pos] = Node( 657 | pos=pos, 658 | discovered=True, 659 | wall=True 660 | ) 661 | else: 662 | converted_map.nodes[pos] = self.nodes[pos] 663 | return converted_map 664 | 665 | def convert_base_possible_cells_to_wall(self, base_possible_cells): 666 | converted_map = Graph((self.dim[0], self.dim[1]), self.base_pos) 667 | possible_list = [p[1] for p in base_possible_cells] 668 | for pos in self.nodes.keys(): 669 | if pos in possible_list: 670 | converted_map.nodes[pos] = Node( 671 | pos=pos, 672 | discovered=True, 673 | wall=True 674 | ) 675 | else: 676 | converted_map.nodes[pos] = self.nodes[pos] 677 | return converted_map 678 | 679 | def get_first_move_to_base(self, src, number_of_object): 680 | dest = self.nodes[self.base_pos] 681 | resource_number = number_of_object.get('bread', 0) + number_of_object.get('grass', 0) 682 | if src.pos == dest.pos: 683 | # print('IM IN base') 684 | return None 685 | 686 | q = [src] 687 | in_queue = {src.pos: True} 688 | dist = {src.pos: 0} 689 | parent = {src.pos: src.pos} 690 | 691 | if src.wall: 692 | return None 693 | 694 | while q: 695 | current_node = q.pop(0) 696 | in_queue[current_node.pos] = False 697 | neighbors = self.get_neighbors(current_node.pos) 698 | 699 | for neighbor in neighbors: 700 | next_node = self.nodes[neighbor] 701 | if int(next_node.trap) * resource_number: 702 | continue 703 | if parent.get(next_node.pos) is None: 704 | parent[next_node.pos] = current_node.pos 705 | q.append(next_node) 706 | weight = self.get_worker_weight(current_node, next_node) 707 | if dist.get(next_node.pos) is None or weight + dist[current_node.pos] < dist.get(next_node.pos): 708 | dist[next_node.pos] = weight + dist[current_node.pos] 709 | parent[next_node.pos] = current_node.pos 710 | if not in_queue.get(next_node.pos): 711 | in_queue[next_node.pos] = True 712 | q.append(next_node) 713 | 714 | return Direction.get_value( 715 | self.step(src.pos, self.get_first_move_from_parent(parent, src.pos, dest.pos)) 716 | ) if dist.get(dest.pos) else None 717 | 718 | def get_reachable_resource_from_base(self): 719 | reachable_resource = set() 720 | q = [self.nodes[self.base_pos]] 721 | parent = {self.base_pos: self.base_pos} 722 | 723 | while q: 724 | current_node = q.pop(0) 725 | if current_node.bread + current_node.grass > 0: 726 | reachable_resource.add(current_node.pos) 727 | neighbors = self.get_neighbors(current_node.pos) 728 | 729 | for neighbor in neighbors: 730 | next_node = self.nodes[neighbor] 731 | if not next_node.trap and parent.get(next_node.pos) is None: 732 | parent[next_node.pos] = current_node.pos 733 | q.append(next_node) 734 | 735 | return reachable_resource 736 | -------------------------------------------------------------------------------- /message/map_message.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | 3 | from Utils import get_view_distance_neighbors, time_measure 4 | from graph import Node 5 | 6 | MESSAGE_VALUE = { 7 | "id": 1, 8 | "map": 10, 9 | } 10 | DELIM = '!' 11 | CONSTANT = 34 12 | # 00 -> 0 ; 01 -> 1-3 ; 10 -> 4-6 ; 11 -> 7+ 13 | UNIT_COUNT_MAP = [0] + [1] * 3 + [2] * 3 + [3] 14 | 15 | 16 | def direction_enc(direction): 17 | return f'{direction:03b}' 18 | 19 | 20 | def direction_dec(s): 21 | return int(s, 2) 22 | 23 | 24 | def unit_count_enc(cnt: int) -> str: 25 | return f'{UNIT_COUNT_MAP[min(cnt, 7)]:02b}' 26 | 27 | 28 | def unit_count_dec(s: str) -> int: 29 | x = int(s, 2) 30 | t = [i for i in range(len(UNIT_COUNT_MAP)) if UNIT_COUNT_MAP[i] == x] 31 | # could return min, max, and avg 32 | return min(t) 33 | # return sum(t) // len(t) 34 | 35 | 36 | def pos_str(pos, w, h): 37 | # return f"{pos[0] + CONSTANT:06b}{pos[1] + CONSTANT:06b}" 38 | return f"{chr(pos[0] + CONSTANT)}{chr(pos[1] + CONSTANT)}" 39 | 40 | 41 | def str_pos(s, w, h): 42 | # return int(s[:6], 2) - CONSTANT, int(s[6:], 2) - CONSTANT 43 | return ord(s[0]) - CONSTANT, ord(s[1]) - CONSTANT 44 | 45 | 46 | def encode_graph_nodes(pos, nodes: dict, w, h, view, ant_id, direction, 47 | shot: bool, enemy_base_pos=None): 48 | # CURRENT SCHEME: 49 | # id 1c, pos 2c (14b), walls w*1c, swamps s*1c, traps s*1c 50 | # breads b*2c, grasses g*2c, ally soldiers as*1c, 51 | # enemy soldiers es*1c, status 1c 52 | neighbors = get_view_distance_neighbors(pos, w, h, view) 53 | arr = [[], [], []] # for the values (b, g, as, es) 54 | s = chr(ant_id + CONSTANT) + pos_str(pos, w, h) 55 | for p, n in nodes.items(): 56 | if n.wall: 57 | s += chr(neighbors.index(p) + CONSTANT) 58 | s += DELIM 59 | 60 | for p, n in nodes.items(): 61 | if n.swamp: 62 | s += chr(neighbors.index(p) + CONSTANT) 63 | s += DELIM 64 | 65 | for p, n in nodes.items(): 66 | if n.trap: 67 | s += chr(neighbors.index(p) + CONSTANT) 68 | s += DELIM 69 | 70 | for p, n in nodes.items(): 71 | values = [list(n.__dict__.values())[5], list(n.__dict__.values())[6], list(n.__dict__.values())[10]] 72 | for i, v in enumerate(values): 73 | if v > 0: 74 | if i < 2: 75 | arr[i].append(chr(neighbors.index(p) + CONSTANT)) 76 | arr[i].append(chr(min(v, 220) + CONSTANT)) 77 | else: 78 | pos_cnt = f'{neighbors.index(p):06b}' + unit_count_enc(v) 79 | arr[i].append(chr(int(pos_cnt, 2) + CONSTANT)) 80 | 81 | status = direction_enc(direction) + str(int(shot)) + '0000' 82 | 83 | for a in arr: 84 | s = s + ''.join(a) + DELIM 85 | s += chr(int(status, 2) + CONSTANT) + DELIM 86 | if enemy_base_pos is not None: 87 | s += pos_str(enemy_base_pos, w, h) 88 | 89 | return s 90 | 91 | 92 | def decode_nodes(nodes_str: str, w, h, view): 93 | ret = {} 94 | ant_id = ord(nodes_str[0]) - CONSTANT 95 | pos = str_pos(nodes_str[1:3], w, h) 96 | nodes_str = nodes_str[3:] 97 | neighbors = get_view_distance_neighbors(pos, w, h, view) 98 | direction = direction_dec('000') 99 | shot = False 100 | enemy_base_pos = None 101 | non_empties = [] 102 | 103 | part = nodes_str.split(DELIM)[0] # wall 104 | for c in part: 105 | idx = ord(c) - CONSTANT 106 | non_empties.append(neighbors[idx]) 107 | ret[neighbors[idx]] = Node(neighbors[idx], True, True) 108 | 109 | part = nodes_str.split(DELIM)[1] # swamp 110 | for c in part: 111 | idx = ord(c) - CONSTANT 112 | non_empties.append(neighbors[idx]) 113 | ret[neighbors[idx]] = Node(neighbors[idx], True, False, swamp=True) 114 | 115 | part = nodes_str.split(DELIM)[2] # trap 116 | for c in part: 117 | idx = ord(c) - CONSTANT 118 | non_empties.append(neighbors[idx]) 119 | ret[neighbors[idx]] = Node(neighbors[idx], True, False, trap=True) 120 | 121 | for i, part in enumerate(nodes_str.split(DELIM)): 122 | if i == 3: # bread 123 | for j in range(0, len(part), 2): 124 | c, v = part[j], part[j + 1] 125 | idx = ord(c) - CONSTANT 126 | non_empties.append(neighbors[idx]) 127 | ret[neighbors[idx]] = Node(neighbors[idx], True, False, 128 | bread=ord(v) - CONSTANT) 129 | if i == 4: # grass 130 | for j in range(0, len(part), 2): 131 | c, v = part[j], part[j + 1] 132 | idx = ord(c) - CONSTANT 133 | non_empties.append(neighbors[idx]) 134 | ret[neighbors[idx]] = Node(neighbors[idx], True, False, 135 | grass=ord(v) - CONSTANT) 136 | 137 | if i == 5: # enemy soldier 138 | for j in range(len(part)): 139 | p, v = int(f'{ord(part[j]) - CONSTANT:08b}'[:6], 2), \ 140 | unit_count_dec(f'{ord(part[j]) - CONSTANT:08b}'[6:]) 141 | idx = p 142 | non_empties.append(neighbors[idx]) 143 | if neighbors[idx] in ret: 144 | ret[neighbors[idx]].enemy_soldiers = v 145 | else: 146 | ret[neighbors[idx]] = Node(neighbors[idx], True, False, 147 | enemy_soldiers=v) 148 | 149 | if i == 6: # status 150 | status = f'{ord(part[0]) - CONSTANT:08b}' 151 | direction = direction_dec(status[:3]) 152 | shot = bool(int(status[3])) 153 | 154 | if i == 7 and len(part) > 0: # enemy base pos 155 | enemy_base_pos = str_pos(part, w, h) 156 | 157 | for i in get_view_distance_neighbors(pos, w, h, view): 158 | if i not in non_empties: 159 | ret[i] = Node(i, True, False) 160 | 161 | return ant_id, pos, direction, shot, ret, enemy_base_pos 162 | -------------------------------------------------------------------------------- /message/possible_base_message.py: -------------------------------------------------------------------------------- 1 | from message.map_message import CONSTANT 2 | 3 | 4 | def ps(pos, w, h): 5 | return f"{pos[0] + CONSTANT:06b}{pos[1] + CONSTANT:06b}" 6 | 7 | 8 | def sp(s, w, h): 9 | return int(s[:6], 2) - CONSTANT, int(s[6:], 2) - CONSTANT 10 | 11 | 12 | def encode_possible_cells(ant_id, pos, prev_pos, w, h, possible_cells): 13 | s = "sh" + chr(ant_id + CONSTANT) 14 | 15 | binary = ps(pos, w, h) + ps(prev_pos, w, h) 16 | for p in possible_cells: 17 | binary += ps(p, w, h) 18 | 19 | for i in range(0, len(binary) - 8, 8): 20 | part = binary[i:i + 8] 21 | s += chr(int(part, 2) + CONSTANT) 22 | 23 | return s 24 | 25 | 26 | def decode_possible_cells(s, w, h): 27 | ant_id = ord(s[1]) - CONSTANT 28 | temp = ''.join([f"{ord(c) - CONSTANT:08b}" for c in s[3:]]) 29 | # print("decode possible msg error:", temp[:12]) 30 | # print(temp) 31 | pos = sp(temp[:12], w, h) 32 | prev_pos = sp(temp[12:24], w, h) 33 | possible_cells = [] 34 | for i in range(24, len(temp) - 12, 12): 35 | p = temp[i: i + 12] 36 | possible_cells.append(sp(p, w, h)) 37 | 38 | return ant_id, pos, prev_pos, possible_cells 39 | -------------------------------------------------------------------------------- /message/support_cell_message.py: -------------------------------------------------------------------------------- 1 | from message.map_message import CONSTANT 2 | 3 | 4 | def pos_str(pos, w, h): 5 | return f"{chr(pos[0] + CONSTANT)}{chr(pos[1] + CONSTANT)}" 6 | 7 | 8 | def str_pos(s, w, h): 9 | return ord(s[0]) - CONSTANT, ord(s[1]) - CONSTANT 10 | 11 | 12 | def encode_support_cell(ant_id, pos, w, h, resource_count): 13 | s = "sc" + chr(ant_id + CONSTANT) 14 | 15 | s += pos_str(pos, w, h) 16 | 17 | s += chr(min(resource_count, 220) + CONSTANT) 18 | 19 | return s 20 | 21 | 22 | def decode_support_cell(s, w, h): 23 | ant_id = ord(s[2]) - CONSTANT 24 | pos = str_pos(s[3:5], w, h) 25 | resource_count = ord(s[5]) - CONSTANT 26 | return ant_id, pos, resource_count 27 | -------------------------------------------------------------------------------- /state.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class WorkerState(Enum): 5 | Null = -1 6 | Exploring = 0 7 | BreadOnly = 1 8 | GrassOnly = 2 9 | GoingBase = 3 10 | Collecting = 4 11 | InitCollecting = 5 12 | InitExploring = 6 13 | 14 | 15 | class SoldierState(Enum): 16 | Null = -1 17 | HasBeenShot = 6 18 | AttackingBase = 7 19 | WaitingForComrades = 8 20 | BK_GoingNearEnemyBase = 9 21 | BU_GoingNearEnemyBase = 10 22 | BK_StayingNearBase = 11 23 | BU_StayingNearBase = 12 24 | Explorer_Supporter = 13 25 | AllInAttack = 14 26 | -------------------------------------------------------------------------------- /tsp_generator.py: -------------------------------------------------------------------------------- 1 | import math 2 | from itertools import chain, groupby 3 | 4 | from Model import Direction, ResourceType 5 | from Utils import time_measure 6 | 7 | 8 | def get_tsp(src_pos, dest_pos, graph, number_of_object, name_of_object): 9 | tsp = make_tsp( 10 | graph.nodes[src_pos], graph.nodes[dest_pos], name_of_object, graph, number_of_object 11 | ) 12 | # print(tsp) 13 | if not tsp: 14 | return None 15 | return { 16 | f'tsp_{name_of_object}': tsp 17 | } 18 | 19 | 20 | def make_dist_graph(src, dest, name_of_node_object, graph, dist_nodes, number_of_object): 21 | number_of_dist_vertex = len(dist_nodes) + 2 22 | 23 | dist = [number_of_dist_vertex * [-math.inf] for _ in range(number_of_dist_vertex)] 24 | 25 | dist_src = 0 26 | dist_dest = number_of_dist_vertex - 1 27 | 28 | dist[dist_src][dist_dest] = getattr(src, f'{name_of_node_object}_value')(src, dest, graph, number_of_object) 29 | for i in range(number_of_dist_vertex - 2): 30 | node1 = dist_nodes[i] 31 | dist[dist_src][i + 1] = getattr(node1, f'{name_of_node_object}_value')(src, dest, graph, number_of_object) 32 | dist[i + 1][dist_dest] = getattr(dest, f'{name_of_node_object}_value')(node1, dest, graph, number_of_object) 33 | for j in range(number_of_dist_vertex - 2): 34 | node2 = dist_nodes[j] 35 | dist[i + 1][j + 1] = getattr(node2, f'{name_of_node_object}_value')(node1, dest, graph, number_of_object) 36 | 37 | return dist 38 | 39 | 40 | def make_tsp(src, dest, name_of_node_object, graph, number_of_object): 41 | dist_nodes = getattr(graph, f'get_nearest_{name_of_node_object}_nodes')(src, dest, number_of_object) 42 | number_of_object = number_of_object.get(name_of_node_object, 0) 43 | dist = make_dist_graph(src, dest, name_of_node_object, graph, dist_nodes, number_of_object) 44 | 45 | number_of_dist_vertex = len(dist_nodes) + 2 46 | # print(number_of_dist_vertex) 47 | if number_of_dist_vertex == 2: 48 | return None 49 | 50 | dp = [number_of_dist_vertex * [-math.inf] for _ in range(1 << number_of_dist_vertex)] 51 | dp_path = [number_of_dist_vertex * [-1] for _ in range(1 << number_of_dist_vertex)] 52 | 53 | dist_src = 0 54 | 55 | for i in range(1 << number_of_dist_vertex): 56 | vertices = [] 57 | for j in range(number_of_dist_vertex): 58 | if i & (1 << j): 59 | vertices.append(j) 60 | if len(vertices) == 1 and dist_src not in vertices: 61 | continue 62 | elif len(vertices) == 1: 63 | dp[i][dist_src] = 0 64 | dp_path[i][dist_src] = dist_src 65 | for j in vertices: 66 | for k in vertices: 67 | if j == k: 68 | continue 69 | value = dp[i - (1 << j)][k] + dist[k][j] 70 | if dp[i][j] < value: 71 | dp[i][j] = value 72 | dp_path[i][j] = k 73 | 74 | return { 75 | 'dp': dp, 76 | 'dp_path': dp_path, 77 | 'dist_nodes': list(chain([src], dist_nodes, [dest])), 78 | } 79 | 80 | 81 | def get_tsp_path(src_pos, dest_pos, graph, limit, number_of_object, name_of_object): 82 | number_of_objs = number_of_object.get(name_of_object, 0) 83 | 84 | if number_of_objs >= limit.get(name_of_object).get('min'): 85 | res = { 86 | 'path': graph.get_path( 87 | graph.nodes[src_pos], graph.nodes[dest_pos] 88 | ), 89 | 'value': math.inf 90 | } 91 | return { 92 | f'{name_of_object}_path_from_tsp_info': res 93 | } 94 | 95 | tsp_info = get_tsp(src_pos, dest_pos, graph, number_of_object, name_of_object) 96 | path_from_tsp_info = get_path_from_tsp_info(tsp_info, name_of_object, graph, limit, number_of_objs) 97 | 98 | return { 99 | f'{name_of_object}_path_from_tsp_info': path_from_tsp_info, 100 | } 101 | 102 | 103 | def get_path_from_tsp_info(tsp_info, name_of_node_object, graph, limit, number_of_object): 104 | if tsp_info is None: 105 | return None 106 | 107 | dp = tsp_info.get(f'tsp_{name_of_node_object}').get('dp') 108 | dp_path = tsp_info.get(f'tsp_{name_of_node_object}').get('dp_path') 109 | dist_nodes = tsp_info.get(f'tsp_{name_of_node_object}').get('dist_nodes') 110 | number_of_dist_vertex = len(dist_nodes) 111 | if number_of_dist_vertex == 2: 112 | return None 113 | best_mask = None 114 | best_value = 0 115 | for i in range(1 << (number_of_dist_vertex - 2)): 116 | mask = (i << 1) | 1 | (1 << (number_of_dist_vertex - 1)) 117 | number_of_obj = number_of_object 118 | value = dp[mask][number_of_dist_vertex - 1] 119 | if best_mask and value < best_value: 120 | continue 121 | for j in range(number_of_dist_vertex): 122 | if mask & (1 << j): 123 | number_of_obj += getattr(dist_nodes[j], name_of_node_object, 0) 124 | if number_of_obj >= limit.get(name_of_node_object).get('min'): 125 | best_mask = mask 126 | best_value = value 127 | 128 | if not best_mask: 129 | return None 130 | 131 | path = [] 132 | mask = best_mask 133 | last = number_of_dist_vertex - 1 134 | while dp_path[mask][last] != last: 135 | path.append(last) 136 | new_mask = mask - (1 << last) 137 | last = dp_path[mask][last] 138 | mask = new_mask 139 | path.append(last) 140 | path = list(reversed(path)) 141 | actual_path = [] 142 | 143 | for i in range(1, len(path)): 144 | actual_path.extend( 145 | graph.get_shortest_path_from_shortest_path_info( 146 | dist_nodes[path[i - 1]], dist_nodes[path[i]], name_of_node_object 147 | ) 148 | ) 149 | # actual_path.extend( 150 | # graph.get_shortest_path_from_shortest_path_info( 151 | # dist_nodes[path[0]], dist_nodes[path[1]], name_of_node_object 152 | # ) 153 | # ) 154 | 155 | return { 156 | 'path': [el[0] for el in groupby(actual_path)], 157 | 'value': dp[best_mask][number_of_dist_vertex - 1], 158 | } 159 | 160 | 161 | # @time_measure 162 | def get_tsp_first_move(src_pos, dest_pos, graph, name_of_object, limit=None, number_of_object=None): 163 | number_of_object = number_of_object or {} 164 | if not limit: 165 | limit = { 166 | 'bread': { 167 | 'min': 1, 168 | }, 169 | 'grass': { 170 | 'min': 1, 171 | } 172 | } 173 | if not limit.get('bread'): 174 | limit['bread'] = {'min': 1} 175 | if not limit.get('grass'): 176 | limit['grass'] = {'min': 1} 177 | 178 | all_tsp = get_tsp_path(src_pos, dest_pos, graph, limit, number_of_object, name_of_object) 179 | tsp_path = all_tsp.get(f'{name_of_object}_path_from_tsp_info') 180 | 181 | if not tsp_path or not tsp_path.get('path'): 182 | return Direction.get_value('None'), None 183 | # print([p.pos for p in tsp_path.get('path')]) 184 | return Direction.get_value(graph.step(src_pos, tsp_path.get('path')[0].pos)), name_of_object 185 | 186 | 187 | def get_limit(**kwargs): 188 | bread = {} 189 | grass = {} 190 | for k, v in kwargs.items(): 191 | obj, key = k.split('_', 1) 192 | if obj == 'bread': 193 | bread[key] = v 194 | else: 195 | grass[key] = v 196 | 197 | return { 198 | 'grass': grass, 199 | 'bread': bread, 200 | } 201 | 202 | 203 | def get_number_of_object(current_resource): 204 | return { 205 | ResourceType(current_resource.type).name.lower(): current_resource.value} if current_resource.type != 2 else {} 206 | --------------------------------------------------------------------------------