Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,40 @@
# pacman-capture-flag-contest
# Pacman-capture-flag-contest
Attack and Defense agent for the Pacman intrauniversities contest
(Best ranking: 11th place out of 32)

## Team

| **Name | **GitHub** |
| :---: | :---: |
| `Roberta` | [![name](https://github.com/b-rbmp/NexxGate/blob/main/docs/logos/github.png)](https://github.com/RobCTs) |
| `Nazanin` | [![name](https://github.com/b-rbmp/NexxGate/blob/main/docs/logos/github.png)](https://github.com/Naominickels) |

### Introduction

The Eutopia Pacman contest is an activity consisting of a multiplayer capture-the-flag variant of Pacman, where agents control both Pacman and ghosts in coordinated team-based strategies. Students from different EUTOPIA universities compete with each other through their programmed agents.
The project is based on the material from the CS188 course Introduction to Arti cial Intelligence at Berkeley2, which was extended for the AI course in 2017 by lecturer Prof. Sebastian Sardina at the Royal Melbourne Institute of Technology (RMIT University) and Dr. Nir Lipovetzky at University of Melbourne
(UoM)3. UPF has refactored the RMIT and UoM code. All the source code is written in Python.

### Rules of Pacman Capture the Flag

**2.1 Layout**:
The Pacman map is now divided into two halves: blue (right) and red (left). Red agents (which all have even indices) must defend the red food while trying to eat the blue food. When on the red side, a red agent is a ghost. When crossing into enemy territory, the agent becomes a Pacman.

**2.2 Scoring:**
As a Pacman eats food dots, those food dots are stored up inside of that Pacman and removed from the board. When a Pacman returns to his side of the board, he \deposits" the food dots he is carrying, earning one point per food pellet delivered. Red team scores are positive, while Blue team scores are negative.
If Pacman is eaten by a ghost before reaching his own side of the board, he will explode into a cloud of food dots that will be deposited back onto the board.

**2.3 Eating Pacman:**
When a Pacman is eaten by an opposing ghost, the Pacman returns to its starting position (as a ghost). No points are awarded for eating an opponent.

**2.4 Power Capsules:**
If Pacman eats a power capsule, agents on the opposing team become \scared" for the next 40 moves, or until they are eaten and respawn, whichever comes sooner. Agents that are \scared" are susceptible while in the form of ghosts (i.e. while on their own team's side) to being eaten by Pacman. Speci cally, if Pacman collides with a \scared" ghost, Pacman is una ected and the ghost respawns at its starting position (no longer in the \scared" state).

**2.5 Observations:**
Agents can only observe an opponent's con guration (position and direction) if they or their teammate is within 5 squares (Manhattan distance). In addition, an agent always gets a noisy distance reading for each agent on the board, which can be used to approximately locate unobserved opponents.

**2.6 Winning:**
A game ends when one team returns all but two of the opponents' dots. Games are also limited to 1200 agent moves (300 moves per each of the four agents). If this move limit is reached, whichever team has returned the most food wins. If the score is zero (i.e., tied) this is recorded as a tie game.

**2.7 Computation Time:**
We will run your submissions on the UPF cluster, SNOW. Tournaments will generate many processes that have to be executed without overloading the system. Therefore, each agent has 1 second to return each action. Each move which does not return within one second will incur a warning. After three warnings, or any single move taking more than 3 seconds, the game is forfeit. There will be an initial start-up allowance of 15 seconds (use the registerInitialState function). If your agent times out or otherwise throws an exception, an error message will be present in the log les, which you can download from the results page.
149 changes: 146 additions & 3 deletions myTeam2.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@

import random
import contest.util as util
import os
os.system('pip install scikit-learn')
from sklearn.cluster import DBSCAN
import numpy as np

from contest.captureAgents import CaptureAgent
from contest.game import Directions
Expand Down Expand Up @@ -189,7 +193,7 @@ def choose_action(self, game_state):
v = self.MCTS(game_state, a, 5, 0, numCarrying)
values.append(v)
maxValue = max(values)
print(maxValue)
#print(maxValue)
bestActions = [a for a, v in zip(actions, values) if v == maxValue]

return bestActions[0]
Expand Down Expand Up @@ -319,18 +323,23 @@ def get_weights(self, game_state, action, numCarrying):

class DefensiveGoodAgent(GoodCaptureAgent):
"""
An agent that keeps its side Pacman-free. Again,
A reflex agent that keeps its side Pacman-free. Again,
this is to give you an idea of what a defensive agent
could be like. It is not the best or only way to make
such an agent.
"""

# inharit features from parents
def __init__(self, *args, **kwargs):
super(GoodCaptureAgent, self).__init__(*args, **kwargs)

def get_features(self, game_state, action):
features = util.Counter()
successor = self.get_successor(game_state, action)

my_state = successor.get_agent_state(self.index)
my_pos = my_state.get_position()
#print("Current position:", my_pos)

# Computes whether we're on defense (1) or offense (0)
features['on_defense'] = 1
Expand All @@ -343,12 +352,146 @@ def get_features(self, game_state, action):
if len(invaders) > 0:
dists = [self.get_maze_distance(my_pos, a.get_position()) for a in invaders]
features['invader_distance'] = min(dists)
# Implementation
else:
# Patrolling strategy when no invaders are visible
features['patrol_distance'] = self.get_patrol_distance(successor) #changed it from game_state

# Encoding the actions if we need to use it for rewards
if action == Directions.STOP: features['stop'] = 1
rev = Directions.REVERSE[game_state.get_agent_state(self.index).configuration.direction]
if action == rev: features['reverse'] = 1

return features


# Defining patrol points
def get_patrol_points(self, game_state):
"""
Identify dynamic patrol points focusing on areas near remaining food
and the nearest power capsule position.
"""
patrol_points = []

food_list = self.get_food(game_state).as_list()
nearest_food_in_cluster = self.cluster_food(game_state, food_list)
patrol_points.append(nearest_food_in_cluster)


# Include additional strategic points like the nearest power capsule position
power_capsule_position = self.get_power_capsule_position(game_state)
if power_capsule_position:
patrol_points.append(power_capsule_position)

return patrol_points

#patrolling strategies
def get_patrol_distance(self, game_state):
"""
Calculate the average distance to key patrol points.
"""
my_state = game_state.get_agent_state(self.index)
my_pos = my_state.get_position()
#print("Current positionPatrol:", my_pos)

# Define key patrol points (static or dynamically determined)
patrol_points = self.get_patrol_points(game_state)

# Calculate distances to each patrol point
#distances = [self.get_maze_distance(tuple(my_pos), tuple(point)) for point in patrol_points] # point is a np.array, but it needs to be a tuple
distances = [self.get_maze_distance(tuple(map(int, my_pos)), tuple(map(int, point))) for point in patrol_points]

# Return the average distance
if distances:
return sum(distances) / len(distances)
else:
return 0


def cluster_food(self, game_state, food_list, eps=3, min_samples=2):
"""
Cluster food pellets using DBSCAN.

:param food_list: List of food pellet coordinates.
:param eps: The maximum distance between two samples for one to be considered as in the neighborhood of the other.
:param min_samples: The number of samples in a neighborhood for a point to be considered as a core point.
:return: List of clusters with their food pellet coordinates.
"""
# Convert food_list to a numpy array for DBSCAN
food_array = np.array(food_list)

# Apply DBSCAN clustering
dbscan = DBSCAN(eps=eps, min_samples=min_samples)
dbscan.fit(food_array)

# Extract clustered food pellets
clusters = [food_array[dbscan.labels_ == label] for label in set(dbscan.labels_) if label != -1]

if not clusters:
return None

# Find the largest cluster
largest_cluster = max(clusters, key=len)

# Get current position of the agent
my_pos = game_state.get_agent_state(self.index).get_position()

# Find the nearest food in the largest cluster
nearest_food = min(largest_cluster, key=lambda food: self.get_maze_distance(my_pos, tuple(food)))

return tuple(nearest_food)


def get_power_capsule_position(self, game_state):
"""
Find and return the position of the nearest power capsule.
"""
my_state = game_state.get_agent_state(self.index)
my_pos = my_state.get_position()
capsules = game_state.get_capsules()

if capsules:
return min(capsules, key=lambda pos: self.get_maze_distance(my_pos, pos))
else:
return None


def get_weights(self, game_state, action):
return {'num_invaders': -1000, 'on_defense': 100, 'invader_distance': -10, 'stop': -100, 'reverse': -2}
"""
Dynamically adjust weights based on the current game state.
"""

# Default weights
weights = {
'num_invaders': -1000,
'on_defense': 100,
'invader_distance': -10,
'stop': -100,
'reverse': -2,
'patrol_distance': -5 # Weight for patrol distance
}

# Adjust weights based on specific game state conditions
my_state = game_state.get_agent_state(self.index)
my_pos = my_state.get_position()
enemies = [game_state.get_agent_state(i) for i in self.get_opponents(game_state)]
invaders = [a for a in enemies if a.is_pacman and a.get_position() is not None]

# Example: Increase the penalty for stopping if there are invaders close by
if invaders:
closest_invader_distance = min([self.get_maze_distance(my_pos, a.get_position()) for a in invaders])
if closest_invader_distance < 5: # If an invader is very close
weights['stop'] -= 50 # Increase the penalty for stopping

# Example: Adjust weights based on the remaining food and moves
remaining_food = len(self.get_food_you_are_defending(game_state).as_list())
remaining_moves = game_state.data.timeleft
total_moves = 1200 # Total moves before the game ends

if remaining_food <= 4 or remaining_moves < total_moves / 4:
weights['num_invaders'] *=3
weights['on_defense'] *= 2
weights['patrol_distance'] *= 1

return weights