From 9893145177f9d3abd9d693e007ef1fd6ec978276 Mon Sep 17 00:00:00 2001 From: Sophia Wang Date: Thu, 29 Jan 2026 11:54:22 -0800 Subject: [PATCH 1/3] allow 3-5 ants per team, update docs --- README.md | 21 ++++++++++++--------- main.py | 51 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index d2e31d2..bddbfa9 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,21 @@ Players code the behavior of ants that compete in teams to bring food back to their anthill. ## Introduction -Each team has 1-4 ants, which are placed on a grid. At the end of 200 rounds, whichever team has dropped the most food at their anthill wins. Ants have the ability to move, get food, drop food, and pass messages to their teammates. +Each team has 3-5 ants, which are placed on a grid. At the end of 200 rounds, whichever team has dropped the most food at their anthill wins. Ants have the ability to move, get food, drop food, and pass messages to their teammates. ### The Map The map is randomly generated each round with 20-24 rows and columns. It's printed to the console using the following symbols: -| Symbol | Meaning | -| :------------- | :------------- | -| # | Wall | -| 1-9 | Food pile | -| . | Empty | -| A-D, @ | North team ants & anthill | -| E-H, X | South team ants & anthill | +| Symbol | Meaning | +| :------------- | :------------- | +| # | Wall | +| 1-9 | Food pile | +| . | Empty | +| @ | North anthill | +| X | South anthill | +| A-C, [A-D, A-E] | North team of 3-5 ants | +| D-F, [E-H, F-J] | South team of 3-5 ants | ### Ant Commands Ants have four moves they can make on their turn: @@ -52,7 +54,8 @@ This class is fully documented, so see `AntStrategy.py` for more details about t ## Running the Simulation First, add an import to the list of `AntStrategy` subclass imports in `main.py` to import your class. Then, find the two tuples called `team1` and `team2`. -Change the contents of these so that they're the names of the 1-5 AntStrategy classes you want on each team. +Change the contents of these so that they're the names of the 3-5 AntStrategy classes you want on each team. +Each team must have the same number of ants. If using CodeHS, click Run! If using something else, execute `main.py` (on the commandline: `$ python3 main.py`). ### Saving and Loading a Map from a File diff --git a/main.py b/main.py index fc39ed0..3dbbedb 100755 --- a/main.py +++ b/main.py @@ -24,11 +24,20 @@ from ScoutStrat import ScoutStrat from StarterStrat import StarterStrat -# B. Register strategy class names in team1/team2 tuples below, 1-5 ants per team -team1 = (RandomStrat, SmarterRandomStrat, StraightHomeStrat, ScoutStrat, RandomStrat) -team2 = (GridBuilderStrat, SmarterRandomStrat, HorizontalStrat, VerticalStrat, RandomStrat) +# B. Register strategy class names in team1/team2 tuples below, 3-5 ants per team +team1 = (RandomStrat, SmarterRandomStrat, StraightHomeStrat, VerticalStrat, HorizontalStrat) +team2 = (GridBuilderStrat, SmarterRandomStrat, HorizontalStrat, ScoutStrat, RandomStrat) DEBUG = False # Change this to True to get more detailed errors from ant strategies +# C. Create relevant error-handling measures +class UnequalTeamsError (Exception): + """Raised when teams do not have the same number of ants.""" + pass + +class AntCountError (Exception): + """ Raised when there are too few or too many ants in a team.""" + pass + # --- Begin Game --- # Configuration variables for randomly generated maps @@ -40,8 +49,22 @@ WALL = '#' NORTH_HILL = '@' SOUTH_HILL = 'X' -NORTH_SYMS = ["A", "B", "C", "D", "E"] -SOUTH_SYMS = ["F", "G", "H", "I", "J"] +ANTS_COUNT = len(team1) +if (len(team1) != len(team2)): + raise UnequalTeamsError ("Teams do not have an equal number of ants.") +NORTH_SYMS = [] +SOUTH_SYMS = [] +if (ANTS_COUNT == 3): + NORTH_SYMS = ["A", "B", "C"] + SOUTH_SYMS = ["D", "E", "F"] +elif (ANTS_COUNT == 4): + NORTH_SYMS = ["A", "B", "C", "D"] + SOUTH_SYMS = ["E", "F", "G", "H"] +elif (ANTS_COUNT == 5): + NORTH_SYMS = ["A", "B", "C", "D", "E"] + SOUTH_SYMS = ["F", "G", "H", "I", "J"] +else: + raise AntCountError ("Teams do not have between 3 to 5 ants.") class Cell: '''Cell in the game matrix. @@ -177,7 +200,7 @@ def generate_game_config(): def load_save_file(filename): """Load saved game data from a file. - Trusts that map is valid format, with walls, 10 ants, and 2 anthills. + Trusts that map is valid format, with walls, 6-10 ants, and 2 anthills. Returns: Dict[str, int or str] of game data with following keys: @@ -323,8 +346,20 @@ def construct_map(config): bottom_hill = cols-(int((cols)/2))-1 else: bottom_hill = cols-(int((cols)/2)) - team1_starting = {'A': (3,1), 'B': (6,1), 'C': (top_hill,1), 'D': (cols-7,1), 'E': (cols-4,1)} - team2_starting = {'F': (3,rows-2), 'G': (6,rows-2), 'H': (bottom_hill, rows-2), 'I': (cols-7,rows-2), 'J': (cols-4,rows-2)} + + match ANTS_COUNT: + case 3: + team1_starting = {'A': (4,1), 'B': (top_hill,1), 'C': (cols-5,1)} + team2_starting = {'D': (4,rows-2), 'E': (bottom_hill,rows-2), 'F': (cols-5,rows-2)} + case 4: + team1_starting = {'A': (3,1), 'B': (7,1), 'C': (cols-8,1), 'D': (cols-4,1)} + team2_starting = {'E': (3,rows-2), 'F': (7,rows-2), 'G': (cols-8,rows-2), 'H': (cols-4,rows-2)} + case 5: + team1_starting = {'A': (3,1), 'B': (6,1), 'C': (top_hill,1), 'D': (cols-7,1), 'E': (cols-4,1)} + team2_starting = {'F': (3,rows-2), 'G': (6,rows-2), 'H': (bottom_hill,rows-2), 'I': (cols-7,rows-2), 'J': (cols-4,rows-2)} + case _: + team1_starting = {'A': (3,1), 'B': (6,1), 'C': (top_hill,1), 'D': (cols-7,1), 'E': (cols-4,1)} + team2_starting = {'F': (3,rows-2), 'G': (6,rows-2), 'H': (bottom_hill,rows-2), 'I': (cols-7,rows-2), 'J': (cols-4,rows-2)} initialize_ants(team1, team1_starting, team2, team2_starting, len(matrix), len(matrix[0])) place_ants(matrix, ants) From d7f92c698e120cf58f08856e21c8bc2ad3fe9a97 Mon Sep 17 00:00:00 2001 From: Sophia Wang Date: Mon, 9 Feb 2026 22:22:52 -0800 Subject: [PATCH 2/3] refactor ant team symbols, fix collision issue for 3+ ants simultaneously colliding --- main.py | 124 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 65 insertions(+), 59 deletions(-) diff --git a/main.py b/main.py index 3dbbedb..54be855 100755 --- a/main.py +++ b/main.py @@ -25,19 +25,10 @@ from StarterStrat import StarterStrat # B. Register strategy class names in team1/team2 tuples below, 3-5 ants per team -team1 = (RandomStrat, SmarterRandomStrat, StraightHomeStrat, VerticalStrat, HorizontalStrat) -team2 = (GridBuilderStrat, SmarterRandomStrat, HorizontalStrat, ScoutStrat, RandomStrat) +team1 = (GridBuilderStrat, RandomStrat, SmarterRandomStrat, StraightHomeStrat, HorizontalStrat) +team2 = (RandomStrat, VerticalStrat, SmarterRandomStrat, HorizontalStrat, ScoutStrat) DEBUG = False # Change this to True to get more detailed errors from ant strategies -# C. Create relevant error-handling measures -class UnequalTeamsError (Exception): - """Raised when teams do not have the same number of ants.""" - pass - -class AntCountError (Exception): - """ Raised when there are too few or too many ants in a team.""" - pass - # --- Begin Game --- # Configuration variables for randomly generated maps @@ -49,22 +40,8 @@ class AntCountError (Exception): WALL = '#' NORTH_HILL = '@' SOUTH_HILL = 'X' -ANTS_COUNT = len(team1) -if (len(team1) != len(team2)): - raise UnequalTeamsError ("Teams do not have an equal number of ants.") -NORTH_SYMS = [] -SOUTH_SYMS = [] -if (ANTS_COUNT == 3): - NORTH_SYMS = ["A", "B", "C"] - SOUTH_SYMS = ["D", "E", "F"] -elif (ANTS_COUNT == 4): - NORTH_SYMS = ["A", "B", "C", "D"] - SOUTH_SYMS = ["E", "F", "G", "H"] -elif (ANTS_COUNT == 5): - NORTH_SYMS = ["A", "B", "C", "D", "E"] - SOUTH_SYMS = ["F", "G", "H", "I", "J"] -else: - raise AntCountError ("Teams do not have between 3 to 5 ants.") +NORTH_SYMS = ["A", "B", "C", "D", "E"] +SOUTH_SYMS = ["F", "G", "H", "I", "J"] class Cell: '''Cell in the game matrix. @@ -128,6 +105,7 @@ def get_symbol(self): def __repr__(self): return self.symbol +# utility functions def is_open_cell(matrix, x, y, ant=''): """Check if a cell in matrix is in bounds and not a wall.""" if (x > 0 and x < len(matrix) and y > 0 and y < len(matrix[0]) and not matrix[x][y].wall): @@ -137,14 +115,15 @@ def is_open_cell(matrix, x, y, ant=''): return False -def initialize_ants(team1_strats, team1_locs, team2_strats, team2_locs, rows, cols): +def initialize_ants(team1_strats, team1_locs, team2_strats, team2_locs, rows, cols, config): """Instantiate ant classes for each team. Populate ants list and dictionary of Ant->matrixSymbol mappings. Takes two lists for each team: AntStrategy class names and inital (x, y) positions """ + # Team 1 - for Strat, sym in zip(team1_strats, NORTH_SYMS): + for Strat, sym in zip(team1_strats, NORTH_SYMS[:config['ants_count']]): try: ant_strat = Strat(rows, cols, NORTH_HILL) except Exception as e: @@ -155,7 +134,7 @@ def initialize_ants(team1_strats, team1_locs, team2_strats, team2_locs, rows, co ants.append(Ant(ant_strat, team1_locs[sym][0], team1_locs[sym][1], 1, sym)) # Team 2 - for Strat, sym in zip(team2_strats, SOUTH_SYMS): + for Strat, sym in zip(team2_strats, SOUTH_SYMS[:config['ants_count']]): try: ant_strat = Strat(rows, cols, SOUTH_HILL) except Exception as e: @@ -165,18 +144,20 @@ def initialize_ants(team1_strats, team1_locs, team2_strats, team2_locs, rows, co continue ants.append(Ant(ant_strat, team2_locs[sym][0], team2_locs[sym][1], 2, sym)) -def generate_game_config(): +def generate_game_config(team1_strats, team2_strats): """Prompt user for game configuration options, including saved map file Returns: dict[str, boolean or str], with the following keys: 'fast_forward': boolean, continue to end without stopping 'load_map': boolean, use saved map 'save_file': str, filename if load_map is True + 'ants_count': int, number of ants in each team """ config = { - 'fast_forward': False, - 'load_map': False, - 'save_file': None, + 'fast_forward': False, + 'load_map': False, + 'save_file': None, + 'ants_count': 0, } fast_forward = input("Run to the end without pausing? (yes/) ") @@ -195,6 +176,21 @@ def generate_game_config(): print("File not found, using new map: " + filepath) config['load_map'] = False + if (len(team1_strats) == len(team2_strats)): + if (len(team1_strats) <= 5 and len(team1_strats) >= 3): + config['ants_count'] = len(team1_strats) + else: + raise ValueError("Teams must have between 3-5 ants (inclusive).") + else: + raise ValueError("Teams must have the same number of ants.") + + # uncomment these lines if varying number of active players per team per game + # ants_count = input("Number of ants on each team? (3-5) ") + # while(not ants_count.isnumeric() or int(ants_count) > 5 or int(ants_count) < 3): + # print("Teams must have between 3 to 5 ants.") + # ants_count = input("Number of ants on each team? (3-5) ") + # config['ants_count'] = int(ants_count) + return config def load_save_file(filename): @@ -347,13 +343,13 @@ def construct_map(config): else: bottom_hill = cols-(int((cols)/2)) - match ANTS_COUNT: + match config['ants_count']: case 3: team1_starting = {'A': (4,1), 'B': (top_hill,1), 'C': (cols-5,1)} - team2_starting = {'D': (4,rows-2), 'E': (bottom_hill,rows-2), 'F': (cols-5,rows-2)} + team2_starting = {'F': (4,rows-2), 'G': (bottom_hill,rows-2), 'H': (cols-5,rows-2)} case 4: team1_starting = {'A': (3,1), 'B': (7,1), 'C': (cols-8,1), 'D': (cols-4,1)} - team2_starting = {'E': (3,rows-2), 'F': (7,rows-2), 'G': (cols-8,rows-2), 'H': (cols-4,rows-2)} + team2_starting = {'F': (3,rows-2), 'G': (7,rows-2), 'H': (cols-8,rows-2), 'I': (cols-4,rows-2)} case 5: team1_starting = {'A': (3,1), 'B': (6,1), 'C': (top_hill,1), 'D': (cols-7,1), 'E': (cols-4,1)} team2_starting = {'F': (3,rows-2), 'G': (6,rows-2), 'H': (bottom_hill,rows-2), 'I': (cols-7,rows-2), 'J': (cols-4,rows-2)} @@ -361,7 +357,7 @@ def construct_map(config): team1_starting = {'A': (3,1), 'B': (6,1), 'C': (top_hill,1), 'D': (cols-7,1), 'E': (cols-4,1)} team2_starting = {'F': (3,rows-2), 'G': (6,rows-2), 'H': (bottom_hill,rows-2), 'I': (cols-7,rows-2), 'J': (cols-4,rows-2)} - initialize_ants(team1, team1_starting, team2, team2_starting, len(matrix), len(matrix[0])) + initialize_ants(team1, team1_starting, team2, team2_starting, len(matrix), len(matrix[0]), config) place_ants(matrix, ants) return matrix @@ -530,27 +526,37 @@ def game_loop(matrix, ants, config): proposed_moves[loc] = a else: conflict_ant = proposed_moves[loc] - print("Collision between " + a.symbol + " and " + conflict_ant.symbol) - # Return this ant to original position, resolving any chains of conflicts - proposed_moves[loc] = None # No one gets to be here - current_ant = a - while current_ant and (current_ant.x, current_ant.y) in proposed_moves and proposed_moves[(current_ant.x, current_ant.y)]: - next_current_ant = proposed_moves[(current_ant.x, current_ant.y)] - proposed_moves[(current_ant.x, current_ant.y)] = current_ant - current_ant = next_current_ant - else: - if current_ant: + if (type(proposed_moves[loc]) == list): # existing collision; previous ants already moved back + for conflict_ant_i in proposed_moves[loc]: + print("Collision between " + a.symbol + " and " + conflict_ant_i.symbol) + # return this ant to original position + proposed_moves[loc].append(a) + proposed_moves[(a.x, a.y)] = a + else: # new collision + print("Collision between " + a.symbol + " and " + conflict_ant.symbol) + # Return this ant to original position, resolving any chains of conflicts + proposed_moves[loc] = None # No one gets to be here + current_ant = a + while current_ant and (current_ant.x, current_ant.y) in proposed_moves and proposed_moves[(current_ant.x, current_ant.y)]: + next_current_ant = proposed_moves[(current_ant.x, current_ant.y)] proposed_moves[(current_ant.x, current_ant.y)] = current_ant - - # Return conflicting ant to original position, resolving conflicts - while conflict_ant and (conflict_ant.x, conflict_ant.y) in proposed_moves and proposed_moves[(conflict_ant.x, conflict_ant.y)]: - next_conflict_ant = proposed_moves[(conflict_ant.x, conflict_ant.y)] - proposed_moves[(conflict_ant.x, conflict_ant.y)] = conflict_ant - conflict_ant = next_conflict_ant - else: - if conflict_ant: + current_ant = next_current_ant + else: + if current_ant: + proposed_moves[(current_ant.x, current_ant.y)] = current_ant + + # Return conflicting ant to original position, resolving conflicts + while conflict_ant and (conflict_ant.x, conflict_ant.y) in proposed_moves and proposed_moves[(conflict_ant.x, conflict_ant.y)]: + next_conflict_ant = proposed_moves[(conflict_ant.x, conflict_ant.y)] proposed_moves[(conflict_ant.x, conflict_ant.y)] = conflict_ant - + conflict_ant = next_conflict_ant + else: + if conflict_ant: + proposed_moves[(conflict_ant.x, conflict_ant.y)] = conflict_ant + + # record all ants in conflict at this location for scenario of multi-way collisions + proposed_moves[loc] = [a, conflict_ant] + # Resolve proposed gets for (target_x, target_y), aList in proposed_gets.items(): if matrix[target_x][target_y].food > 0 and matrix[target_x][target_y].food >= len(aList): #here @@ -562,7 +568,7 @@ def game_loop(matrix, ants, config): # Update arena & redraw screen for loc, a in proposed_moves.items(): - if a: # May be none if it was the site of a movement conflict + if (type(a) != list): # May be a list if it was the site of a movement conflict matrix[a.x][a.y].ant = None a.x = loc[0] a.y = loc[1] @@ -646,7 +652,7 @@ def prompt_save_map(initial_matrix): if __name__ == '__main__': random.seed() ants = [] - config = generate_game_config() + config = generate_game_config(team1, team2) matrix = construct_map(config) initial_matrix = matrix_to_str_list(matrix) game_loop(matrix, ants, config) From aab3c8ffdb5af804e2623bfc77406ff48e814b49 Mon Sep 17 00:00:00 2001 From: Sophia Wang Date: Thu, 12 Feb 2026 11:41:13 -0800 Subject: [PATCH 3/3] fix conflicting ant behavior error, condense conflict messages, add clarifying notes about ant behavior, vision priority, and message passing, fix example code typo --- HorizontalStrat.py | 2 +- README.md | 39 ++++++++++++-------- main.py | 89 +++++++++++++++++++++++++++++----------------- 3 files changed, 82 insertions(+), 48 deletions(-) diff --git a/HorizontalStrat.py b/HorizontalStrat.py index 84c1577..b10ad0d 100644 --- a/HorizontalStrat.py +++ b/HorizontalStrat.py @@ -24,7 +24,7 @@ def one_step(self, x, y, vision, food): self.direction = "WEST" return self.direction elif self.direction == "WEST": - if x > 0: + if x > 1: return self.direction else: self.direction = "EAST" diff --git a/README.md b/README.md index bddbfa9..3433f34 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ It's printed to the console using the following symbols: | . | Empty | | @ | North anthill | | X | South anthill | -| A-C, [A-D, A-E] | North team of 3-5 ants | -| D-F, [E-H, F-J] | South team of 3-5 ants | +| A-C, [D, E] | North team of 3-5 ants | +| F-H, [I, J] | South team of 3-5 ants | ### Ant Commands Ants have four moves they can make on their turn: @@ -25,7 +25,7 @@ Ants have four moves they can make on their turn: 3. DROP [direction]: If carrying food, it will be dropped at the coordinates in the direction given. 4. GET [direction]: If there is food in the direction given, pick up the food. Ants can carry one food at a time. -All ants move simutaneously. If the actions of two or more ants conflict (possible with moving or GET), these ants' actions won't be executed. Raising an exception, taking too long, or returning an invalid command will result in your ant being eliminated. The game will print a hint of what went wrong. +All ants move simutaneously. If the actions of two or more ants conflict (possible with moving or GET), these ants' actions won't be executed. If two or more ants attempt to drop food on a cell, resulting in the cell having more than 9 food items at a time, these ants' actions also won't be executed. Raising an exception, taking too long, or returning an invalid command will result in your ant being eliminated. The game will print a hint of what went wrong. The supported cardinal directions are as follows: | | | | @@ -49,6 +49,11 @@ Each `AntStrategy` has three methods for you to complete, which will be called i 2. `one_step()`: Using the state information about your ant passed in as arguments, decide on and return the next move for your ant. 3. `send_info()`: Return any messages you want to send this round. +All ants also have some information about the state of the game: +- Grid size: The dimensions of the grid, including the outer walls, will be passed to the ant upon initialization. +- Anthill symbol: The symbol for the ant's anthill, either `'X'` or `'@'`, will be passed to the ant upon initialization. +- Current coordinates: The ant's current position on the matrix will be passed to the ant every round in the `one_step()` method. + This class is fully documented, so see `AntStrategy.py` for more details about these methods and how the game interacts with the class. ## Running the Simulation @@ -72,6 +77,7 @@ Always enter *relative* paths to the files you are saving from or loading ### Ant Vision One of the pieces of state information passed to the `one_step()` method is the ant's `vision`. This is a 3x3 list representing the locations in the map immediately under and around the ant with the ant at the center (`vision[1][1]`). +Ant vision prioritizes showing items and objects in the vicinity in the priority Ant > Food or Anthill or Wall > Empty space, meaning vision will not display the anthill or food in a cell if another ant is occupying that space. Each index will have one of symbols in the key above, indicating what is around the ant in the map. (0,0) is northwest of the ant. Ants can "see" one unit in all directions. @@ -79,33 +85,38 @@ Ants can "see" one unit in all directions. ### Message Passing Each round, your ant will have the ability to receive messages sent by your team in the prior round and send messages. Your team will need to decide what information you want to communicate and how to format your messages. -Suppose your team wants to share whether they're carrying food at the end of each round. + +At the beginning of each round, the game will call `receive_info()` on your ant. It will pass a `list` of messages from your teammates that were sent in the previous round. +Parse each message according to how your team decides to send information, and update your ant's state if needed. +For instance, suppose your team wants to share whether they're carrying food at the end of each round. You might choose the message format `"ANT1 FOOD " + str(food)`. +Then, if all members of your team follow this format, +at the start of each round, your ant may receive a list of messages such as, `["ANT1 FOOD False", "ANT2 FOOD True", "ANT3 FOOD False"]`. + `GridBuilderStrat` and `ScoutStrat` send messages about what they've discovered on the map in the format `str(x) + " " + str(y) + " " + item`. Each of these examples is a string, but the messages don't have to be strings. `SmarterRandomStrat` isn't very creative and just sends the word `"message"` to its teammates. -If you try to run GridBuilderStrat and SmarterRandomStrat together, you will see an error because they try to parse each others' messages incorrectly! +If you try to run `GridBuilderStrat` and `SmarterRandomStrat` together, you will see an error because they try to parse each others' messages incorrectly! -At the beginning of each round, the game will call `receive_info` on your ant. It will pass a `list` of messages from your teammates. -Parse each message according to the your team's format and update your ant's state if needed. -For example, `GridBuilderStrat` uses the messages to fill in its internal map of the playing field and even has some basic checks to avoid causing an exception if a message is the wrong format. +`GridBuilderStrat` uses the messages to fill in its internal map of the playing field and even has some basic checks to avoid causing an exception if a message is the wrong format. ```python3 def receive_info(self, messages): for m in messages: words = m.split() if len(words) != 3: - print("Message incorrectly formatted: " + m); + print("Message incorrectly formatted: " + m) continue x, y, agent = words self.grid[int(x)][int(y)] = agent ``` -Then, the game will call the `one_step` method. -Do not call `send_info` yourself from `one_step`; it's called by the game after `one_step`. -Since you may want to share information learned during `one_step` with your teammates, one solution is to create an instance variable for outgoing messages, e.g. `self.outbox = []`. +Then, the game will call the `one_step()` method. +Do not call `send_info()` yourself from `one_step()`; it's called by the game after `one_step()`. +Since you may want to share information learned during `one_step` with your teammates, +one solution is to create an instance variable for outgoing messages, e.g. `self.outbox = []`. -In `send_info`, your ant can return a `list` of messages. -Here's an example where an ant shares if it's carrying food. Note that this method still returns a list even though there's only one message. +Finally, in `send_info()`, your ant must return a `list` of messages. +Here's an example where an ant shares if it's carrying food. Note that this method still returns a list even though there's only one message. It may return an empty list if there are no messages to send. ```python3 def send_info(self): '''Send whether or not I'm carrying food''' diff --git a/main.py b/main.py index 54be855..043f0df 100755 --- a/main.py +++ b/main.py @@ -23,10 +23,11 @@ from GridBuilderStrat import GridBuilderStrat from ScoutStrat import ScoutStrat from StarterStrat import StarterStrat +from FoodHoardingStrat import FoodHoardingStrat # B. Register strategy class names in team1/team2 tuples below, 3-5 ants per team team1 = (GridBuilderStrat, RandomStrat, SmarterRandomStrat, StraightHomeStrat, HorizontalStrat) -team2 = (RandomStrat, VerticalStrat, SmarterRandomStrat, HorizontalStrat, ScoutStrat) +team2 = (RandomStrat, HorizontalStrat, StarterStrat, HorizontalStrat, ScoutStrat) DEBUG = False # Change this to True to get more detailed errors from ant strategies # --- Begin Game --- @@ -474,7 +475,9 @@ def game_loop(matrix, ants, config): # Parse moves proposed_moves = {} + conflict_sites = {} proposed_gets = {} + proposed_drops = {} for a, move in moves.items(): loc = (a.x, a.y) @@ -512,9 +515,11 @@ def game_loop(matrix, ants, config): team1_points += 1 elif cell.anthill == SOUTH_HILL: team2_points += 1 - else: - cell.food += 1 + if (target_x, target_y) in proposed_drops: + proposed_drops[(target_x, target_y)].append(a) + else: + proposed_drops[(target_x, target_y)] = [a] elif move[0] != "PASS": print("Invalid move from " + a.symbol + ": " + str(move)) @@ -522,41 +527,41 @@ def game_loop(matrix, ants, config): continue # Attempt to place this ant in next phase of simulation. Ants in conflict must go back + print("current ant:", a) + if loc not in proposed_moves: proposed_moves[loc] = a else: conflict_ant = proposed_moves[loc] - if (type(proposed_moves[loc]) == list): # existing collision; previous ants already moved back - for conflict_ant_i in proposed_moves[loc]: - print("Collision between " + a.symbol + " and " + conflict_ant_i.symbol) - # return this ant to original position - proposed_moves[loc].append(a) - proposed_moves[(a.x, a.y)] = a - else: # new collision - print("Collision between " + a.symbol + " and " + conflict_ant.symbol) - # Return this ant to original position, resolving any chains of conflicts - proposed_moves[loc] = None # No one gets to be here - current_ant = a - while current_ant and (current_ant.x, current_ant.y) in proposed_moves and proposed_moves[(current_ant.x, current_ant.y)]: - next_current_ant = proposed_moves[(current_ant.x, current_ant.y)] + + # Record conflict site + print("loc", loc) + print(conflict_sites) + if (loc in conflict_sites): + conflict_sites[loc].append(a.symbol) + else: + conflict_sites[loc] = [a.symbol, conflict_ant.symbol] + + # Return this ant to original position, resolving any chains of conflicts + proposed_moves[loc] = None # No one gets to be here + current_ant = a + while current_ant and (current_ant.x, current_ant.y) in proposed_moves and proposed_moves[(current_ant.x, current_ant.y)]: + next_current_ant = proposed_moves[(current_ant.x, current_ant.y)] + proposed_moves[(current_ant.x, current_ant.y)] = current_ant + current_ant = next_current_ant + else: + if current_ant: proposed_moves[(current_ant.x, current_ant.y)] = current_ant - current_ant = next_current_ant - else: - if current_ant: - proposed_moves[(current_ant.x, current_ant.y)] = current_ant - - # Return conflicting ant to original position, resolving conflicts - while conflict_ant and (conflict_ant.x, conflict_ant.y) in proposed_moves and proposed_moves[(conflict_ant.x, conflict_ant.y)]: - next_conflict_ant = proposed_moves[(conflict_ant.x, conflict_ant.y)] + + # Return conflicting ant to original position, resolving conflicts + while conflict_ant and (conflict_ant.x, conflict_ant.y) in proposed_moves and proposed_moves[(conflict_ant.x, conflict_ant.y)]: + next_conflict_ant = proposed_moves[(conflict_ant.x, conflict_ant.y)] + proposed_moves[(conflict_ant.x, conflict_ant.y)] = conflict_ant + conflict_ant = next_conflict_ant + else: + if conflict_ant: proposed_moves[(conflict_ant.x, conflict_ant.y)] = conflict_ant - conflict_ant = next_conflict_ant - else: - if conflict_ant: - proposed_moves[(conflict_ant.x, conflict_ant.y)] = conflict_ant - - # record all ants in conflict at this location for scenario of multi-way collisions - proposed_moves[loc] = [a, conflict_ant] - + # Resolve proposed gets for (target_x, target_y), aList in proposed_gets.items(): if matrix[target_x][target_y].food > 0 and matrix[target_x][target_y].food >= len(aList): #here @@ -566,13 +571,31 @@ def game_loop(matrix, ants, config): else: ## insufficient food print("Invalid GET in " + a.symbol + ": " + str(move)) + # Resolve proposed drops + for (target_x, target_y), aList in proposed_drops.items(): + if (matrix[target_x][target_y].food < 9 - len(aList)): + matrix[target_x][target_y].food += len(aList) + else: ## too much food on a tile + for a in aList: + a.food = True ## don't allow ant to drop here + print("Invalid DROP in " + a.symbol + ": " + str(move)) + # Update arena & redraw screen for loc, a in proposed_moves.items(): - if (type(a) != list): # May be a list if it was the site of a movement conflict + if (a): # May be none if it was the site of a movement conflict matrix[a.x][a.y].ant = None a.x = loc[0] a.y = loc[1] + # Notify user of collisions + for loc, aList in conflict_sites.items(): + if (len(aList) == 2): + print("Ants " + aList[0] + " and " + aList[1] + " collided.") + elif (len(aList) > 2): + print("Ants " + ", ".join(aList[:-1]) + ", and " + aList[-1] + " collided.") + else: + print("Something went wrong.") + place_ants(matrix, ants) print_map(matrix) print("Round:", lap, "Team 1:", str(team1_points), "Team 2:", str(team2_points))