Created
July 1, 2010 20:01
-
-
Save Gautier/460475 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import ConfigParser | |
import random | |
import sys | |
import time | |
import numpy | |
import pygame, pygame.locals | |
from terrain.generator import terrain_generator | |
if not pygame.font: print 'Warning, fonts disabled' | |
try: | |
import psyco | |
psyco.full() | |
except ImportError: | |
pass | |
def get_mind(name): | |
full_name = 'minds.' + name | |
__import__(full_name) | |
mind = sys.modules[full_name] | |
mind.name = name | |
return mind | |
STARTING_ENERGY = 20 | |
SCATTERED_ENERGY = 10 | |
#Plant energy output. Remember, this should always be less | |
#than ATTACK_POWER, because otherwise cells sitting on the plant edge | |
#might become invincible. | |
PLANT_MAX_OUTPUT = 20 | |
PLANT_MIN_OUTPUT = 5 | |
#BODY_ENERGY is the amount of energy that a cells body contains | |
#It can not be accessed by the cells, think of it as: they can't | |
#eat their own body. It is released again at death. | |
BODY_ENERGY = 25 | |
ATTACK_POWER = 30 | |
#Amount by which attack power is modified for each 1 height difference. | |
ATTACK_TERR_CHANGE = 2 | |
ENERGY_CAP = 2500 | |
#SPAWN_COST is the energy it takes to seperate two cells from each other. | |
#It is lost forever, not to be confused with the BODY_ENERGY of the new cell. | |
SPAWN_LOST_ENERGY = 20 | |
SUSTAIN_COST = 0 | |
MOVE_COST = 1 | |
#MESSAGE_COST = 0 | |
#BODY_ENERGY + SPAWN_COST is invested to create a new cell. What remains is split evenly. | |
#With this model we only need to make sure a cell can't commit suicide by spawning. | |
SPAWN_TOTAL_ENERGY = BODY_ENERGY + SPAWN_LOST_ENERGY | |
TIMEOUT = None | |
config = ConfigParser.RawConfigParser() | |
def get_next_move(old_x, old_y, x, y): | |
''' Takes the current position, old_x and old_y, and a desired future position, x and y, | |
and returns the position (x,y) resulting from a unit move toward the future position.''' | |
dx = numpy.sign(x - old_x) | |
dy = numpy.sign(y - old_y) | |
return (old_x + dx, old_y + dy) | |
class Game(object): | |
''' Represents a game between different minds. ''' | |
def __init__(self, bounds, mind_list, symmetric, max_time, headless = False): | |
self.size = self.width, self.height = (bounds, bounds) | |
self.mind_list = mind_list | |
self.messages = [MessageQueue() for x in mind_list] | |
self.headless = headless | |
if not self.headless: | |
self.disp = Display(self.size, scale=2) | |
self.time = 0 | |
self.clock = pygame.time.Clock() | |
self.max_time = max_time | |
self.tic = time.time() | |
self.terr = ScalarMapLayer(self.size) | |
self.terr.set_perlin(10, symmetric) | |
self.minds = [m[1].AgentMind for m in mind_list] | |
self.show_energy = True | |
self.show_agents = True | |
self.energy_map = ScalarMapLayer(self.size) | |
self.energy_map.set_streak(SCATTERED_ENERGY, symmetric) | |
self.plant_map = ObjectMapLayer(self.size) | |
self.plant_population = [] | |
self.agent_map = ObjectMapLayer(self.size) | |
self.agent_population = [] | |
self.winner = None | |
if symmetric: | |
self.n_plants = 7 | |
else: | |
self.n_plants = 14 | |
# Add some randomly placed plants to the map. | |
for x in xrange(self.n_plants): | |
mx = random.randrange(1, self.width - 1) | |
my = random.randrange(1, self.height - 1) | |
eff = random.randrange(PLANT_MIN_OUTPUT, PLANT_MAX_OUTPUT) | |
p = Plant(mx, my, eff) | |
self.plant_population.append(p) | |
if symmetric: | |
p = Plant(my, mx, eff) | |
self.plant_population.append(p) | |
self.plant_map.lock() | |
self.plant_map.insert(self.plant_population) | |
self.plant_map.unlock() | |
# Create an agent for each mind and place on map at a different plant. | |
self.agent_map.lock() | |
for idx in xrange(len(self.minds)): | |
# BUG: Number of minds could exceed number of plants? | |
(mx, my) = self.plant_population[idx].get_pos() | |
fuzzed_x = mx | |
fuzzed_y = my | |
while fuzzed_x == mx and fuzzed_y == my: | |
fuzzed_x = mx + random.randrange(-1, 2) | |
fuzzed_y = my + random.randrange(-1, 2) | |
self.agent_population.append(Agent(fuzzed_x, fuzzed_y, STARTING_ENERGY, idx, | |
self.minds[idx], None)) | |
self.agent_map.insert(self.agent_population) | |
self.agent_map.unlock() | |
def run_plants(self): | |
''' Increases energy at and around (adjacent position) for each plant. | |
Increase in energy is equal to the eff(?) value of each the plant.''' | |
for p in self.plant_population: | |
(x, y) = p.get_pos() | |
for dx in (-1, 0, 1): | |
for dy in (-1, 0, 1): | |
adj_x = x + dx | |
adj_y = y + dy | |
if self.energy_map.in_range(adj_x, adj_y): | |
self.energy_map.change(adj_x, adj_y, p.get_eff()) | |
def add_agent(self, a): | |
''' Adds an agent to the game. ''' | |
self.agent_population.append(a) | |
self.agent_map.set(a.x, a.y, a) | |
def del_agent(self, a): | |
''' Kills the agent (if not already dead), removes them from the game and | |
drops any load they were carrying in there previously occupied position. ''' | |
self.agent_population.remove(a) | |
self.agent_map.set(a.x, a.y, None) | |
a.alive = False | |
if a.loaded: | |
a.loaded = False | |
self.terr.change(a.x, a.y, 1) | |
def move_agent(self, a, x, y): | |
''' Moves agent, a, to new position (x,y) unless difference in terrain levels between | |
its current position and new position is greater than 4.''' | |
if abs(self.terr.get(x, y)-self.terr.get(a.x, a.y)) <= 4: | |
self.agent_map.set(a.x, a.y, None) | |
self.agent_map.set(x, y, a) | |
a.x = x | |
a.y = y | |
def run_agents(self): | |
# Create a list containing the view for each agent in the population. | |
views = [] | |
agent_map_get_small_view_fast = self.agent_map.get_small_view_fast | |
plant_map_get_small_view_fast = self.plant_map.get_small_view_fast | |
energy_map = self.energy_map | |
terr_map = self.terr | |
WV = WorldView | |
views_append = views.append | |
for a in self.agent_population: | |
x = a.x | |
y = a.y | |
agent_view = agent_map_get_small_view_fast(x, y) | |
plant_view = plant_map_get_small_view_fast(x, y) | |
world_view = WV(a, agent_view, plant_view, terr_map, energy_map) | |
views_append((a, world_view)) | |
# Create a list containing the action for each agent, where each agent | |
# determines its actions based on its view of the world and messages | |
# from its team. | |
messages = self.messages | |
actions = [(a, a.act(v, messages[a.team])) for (a, v) in views] | |
actions_dict = dict(actions) | |
random.shuffle(actions) | |
self.agent_map.lock() | |
# Apply the action for each agent - in doing so agent uses up 1 energy unit. | |
for (agent, action) in actions: | |
#This is the cost of mere survival | |
agent.energy -= SUSTAIN_COST | |
done = '' | |
if action.type == ACT_MOVE: # Changes position of agent. | |
act_x, act_y = action.get_data() | |
(new_x, new_y) = get_next_move(agent.x, agent.y, | |
act_x, act_y) | |
# Move to the new position if it is in range and it's not | |
#currently occupied by another agent. | |
if (self.agent_map.in_range(new_x, new_y) and | |
not self.agent_map.get(new_x, new_y)): | |
self.move_agent(agent, new_x, new_y) | |
agent.energy -= MOVE_COST | |
done = 'move' | |
elif action.type == ACT_SPAWN: # Creates new agents and uses additional 50 energy units. | |
act_x, act_y = action.get_data()[:2] | |
(new_x, new_y) = get_next_move(agent.x, agent.y, | |
act_x, act_y) | |
if (self.agent_map.in_range(new_x, new_y) and | |
not self.agent_map.get(new_x, new_y) and | |
agent.energy >= SPAWN_TOTAL_ENERGY): | |
agent.energy -= SPAWN_TOTAL_ENERGY | |
agent.energy /= 2 | |
a = Agent(new_x, new_y, agent.energy, agent.get_team(), | |
self.minds[agent.get_team()], | |
action.get_data()[2:]) | |
self.add_agent(a) | |
done = 'spawn' | |
elif action.type == ACT_EAT: | |
#Eat only as much as possible. | |
intake = min(self.energy_map.get(agent.x, agent.y), | |
ENERGY_CAP - agent.energy) | |
agent.energy += intake | |
self.energy_map.change(agent.x, agent.y, -intake) | |
done = 'eat' | |
elif action.type == ACT_RELEASE: | |
#Dump some energy onto an adjacent field | |
#No Seppuku | |
output = action.get_data()[2] | |
output = min(agent.energy - 1, output) | |
act_x, act_y = action.get_data()[:2] | |
#Use get_next_move to simplyfy things if you know | |
#where the energy is supposed to end up. | |
(out_x, out_y) = get_next_move(agent.x, agent.y, | |
act_x, act_y) | |
if (self.agent_map.in_range(out_x, out_y) and | |
agent.energy >= 1): | |
agent.energy -= output | |
self.energy_map.change(out_x, out_y, output) | |
done = 'release' | |
elif action.type == ACT_ATTACK: | |
#Make sure agent is attacking an adjacent field. | |
act_x, act_y = act_data = action.get_data() | |
next_pos = get_next_move(agent.x, agent.y, act_x, act_y) | |
new_x, new_y = next_pos | |
victim = self.agent_map.get(act_x, act_y) | |
terr_delta = (self.terr.get(agent.x, agent.y) | |
- self.terr.get(act_x, act_y)) | |
if (victim is not None and victim.alive and | |
next_pos == act_data): | |
#If both agents attack each other, both loose double energy | |
#Think twice before attacking | |
try: | |
contested = (actions_dict[victim].type == ACT_ATTACK) | |
except: | |
contested = False | |
agent.attack(victim, terr_delta, contested) | |
if contested: | |
victim.attack(agent, -terr_delta, True) | |
done = 'attack' | |
elif action.type == ACT_LIFT: | |
if not agent.loaded and self.terr.get(agent.x, agent.y) > 0: | |
agent.loaded = True | |
self.terr.change(agent.x, agent.y, -1) | |
done = 'lift' | |
elif action.type == ACT_DROP: | |
if agent.loaded: | |
agent.loaded = False | |
self.terr.change(agent.x, agent.y, 1) | |
done = 'drop' | |
if not done: | |
print 'attempted %s. failed' %action.type | |
# Kill all agents with negative energy. | |
team = [0 for n in self.minds] | |
for (agent, action) in actions: | |
if agent.energy < 0 and agent.alive: | |
self.energy_map.change(agent.x, agent.y, BODY_ENERGY) | |
self.del_agent(agent) | |
else : | |
team[agent.team] += 1 | |
# Team wins (and game ends) if opposition team has 0 agents remaining. | |
# Draw if time exceeds time limit. | |
winner = 0 | |
alive = 0 | |
for t in team: | |
if t != 0: | |
alive += 1 | |
else: | |
if alive == 0: | |
winner += 1 | |
if alive == 1: | |
colors = ["red", "white", "purple", "yellow"] | |
print "Winner is %s (%s) in %s" % (self.mind_list[winner][1].name, | |
colors[winner], str(self.time)) | |
self.winner = winner | |
if alive == 0 or (self.max_time > 0 and self.time > self.max_time): | |
print "It's a draw!" | |
self.winner = -1 | |
self.agent_map.unlock() | |
def tick(self): | |
if not self.headless: | |
# Space starts new game | |
# q or close button will quit the game | |
for event in pygame.event.get(): | |
if event.type == pygame.locals.KEYUP: | |
if event.key == pygame.locals.K_SPACE: | |
self.winner = -1 | |
elif event.key == pygame.locals.K_q: | |
sys.exit() | |
elif event.key == pygame.locals.K_e: | |
self.show_energy = not self.show_energy | |
elif event.key == pygame.locals.K_a: | |
self.show_agents = not self.show_agents | |
elif event.type == pygame.locals.MOUSEBUTTONUP: | |
if event.button == 1: | |
print self.agent_map.get(event.pos[0]/2, | |
event.pos[1]/2) | |
elif event.type == pygame.QUIT: | |
sys.exit() | |
self.disp.update(self.terr, self.agent_population, | |
self.plant_population, self.agent_map, | |
self.plant_map, self.energy_map, self.time, | |
len(self.minds), self.show_energy, | |
self.show_agents) | |
# test for spacebar pressed - if yes, restart | |
for event in pygame.event.get(pygame.locals.KEYUP): | |
if event.key == pygame.locals.K_SPACE: | |
self.winner = -1 | |
if pygame.event.get(pygame.locals.QUIT): | |
sys.exit() | |
pygame.event.pump() | |
self.disp.flip() | |
self.run_agents() | |
self.run_plants() | |
for msg in self.messages: | |
msg.update() | |
self.time += 1 | |
self.tic = time.time() | |
self.clock.tick() | |
if self.time % 100 == 0: | |
print 'FPS: %f' % self.clock.get_fps() | |
class MapLayer(object): | |
def __init__(self, size, val=0, valtype=numpy.object_): | |
self.size = self.width, self.height = size | |
self.values = numpy.empty(size, valtype) | |
self.values.fill(val) | |
def get(self, x, y): | |
if y >= 0 and x >= 0: | |
try: | |
return self.values[x, y] | |
except IndexError: | |
return None | |
return None | |
def set(self, x, y, val): | |
self.values[x, y] = val | |
def in_range(self, x, y): | |
return (0 <= x < self.width and 0 <= y < self.height) | |
class ScalarMapLayer(MapLayer): | |
def set_random(self, range, symmetric = True): | |
self.values = terrain_generator().create_random(self.size, range, | |
symmetric) | |
def set_streak(self, range, symmetric = True): | |
self.values = terrain_generator().create_streak(self.size, range, | |
symmetric) | |
def set_simple(self, range, symmetric = True): | |
self.values = terrain_generator().create_simple(self.size, range, | |
symmetric) | |
def set_perlin(self, range, symmetric = True): | |
self.values = terrain_generator().create_perlin(self.size, range, | |
symmetric) | |
def change(self, x, y, val): | |
self.values[x, y] += val | |
class ObjectMapLayer(MapLayer): | |
def __init__(self, size): | |
MapLayer.__init__(self, size, None, numpy.object_) | |
self.surf = pygame.Surface(size) | |
self.surf.set_colorkey((0,0,0)) | |
self.surf.fill((0,0,0)) | |
self.pixels = None | |
# self.pixels = pygame.PixelArray(self.surf) | |
def lock(self): | |
self.pixels = pygame.surfarray.pixels2d(self.surf) | |
def unlock(self): | |
self.pixels = None | |
def get_small_view_fast(self, x, y): | |
ret = [] | |
get = self.get | |
append = ret.append | |
width = self.width | |
height = self.height | |
for dx in (-1, 0, 1): | |
for dy in (-1, 0, 1): | |
if not (dx or dy): | |
continue | |
try: | |
adj_x = x + dx | |
if not 0 <= adj_x < width: | |
continue | |
adj_y = y + dy | |
if not 0 <= adj_y < height: | |
continue | |
a = self.values[adj_x, adj_y] | |
if a is not None: | |
append(a.get_view()) | |
except IndexError: | |
pass | |
return ret | |
def get_view(self, x, y, r): | |
ret = [] | |
for x_off in xrange(-r, r + 1): | |
for y_off in xrange(-r, r + 1): | |
if x_off == 0 and y_off == 0: | |
continue | |
a = self.get(x + x_off, y + y_off) | |
if a is not None: | |
ret.append(a.get_view()) | |
return ret | |
def insert(self, list): | |
for o in list: | |
self.set(o.x, o.y, o) | |
def set(self, x, y, val): | |
MapLayer.set(self, x, y, val) | |
if val is None: | |
self.pixels[x][y] = 0 | |
# self.surf.set_at((x, y), 0) | |
else: | |
self.pixels[x][y] = val.color | |
# self.surf.set_at((x, y), val.color) | |
# Use Cython version of get_small_view_fast if available. | |
# Otherwise, don't bother folks about it. | |
try: | |
import cells_helpers | |
import types | |
ObjectMapLayer.get_small_view_fast = types.MethodType( | |
cells_helpers.get_small_view_fast, None, ObjectMapLayer) | |
except ImportError: | |
pass | |
TEAM_COLORS = [(255, 0, 0), (255, 255, 255), (255, 0, 255), (255, 255, 0)] | |
TEAM_COLORS_FAST = [0xFF0000, 0xFFFFFF, 0xFF00FF, 0xFFFF00] | |
class Agent(object): | |
__slots__ = ['x', 'y', 'mind', 'energy', 'alive', 'team', 'loaded', 'color', | |
'act'] | |
def __init__(self, x, y, energy, team, AgentMind, cargs): | |
self.x = x | |
self.y = y | |
self.mind = AgentMind(cargs) | |
self.energy = energy | |
self.alive = True | |
self.team = team | |
self.loaded = False | |
self.color = TEAM_COLORS_FAST[team % len(TEAM_COLORS_FAST)] | |
self.act = self.mind.act | |
def __str__(self): | |
return "Agent from team %i, energy %i" % (self.team,self.energy) | |
def attack(self, other, offset = 0, contested = False): | |
if not other: | |
return False | |
max_power = ATTACK_POWER + ATTACK_TERR_CHANGE * offset | |
if contested: | |
other.energy -= min(self.energy, max_power) | |
else: | |
other.energy -= max_power | |
return other.energy <= 0 | |
def get_team(self): | |
return self.team | |
def get_pos(self): | |
return (self.x, self.y) | |
def set_pos(self, x, y): | |
self.x = x | |
self.y = y | |
def get_view(self): | |
return AgentView(self) | |
# Actions available to an agent on each turn. | |
ACT_SPAWN, ACT_MOVE, ACT_EAT, ACT_RELEASE, ACT_ATTACK, ACT_LIFT, ACT_DROP = range(7) | |
class Action(object): | |
''' | |
A class for passing an action around. | |
''' | |
def __init__(self, action_type, data=None): | |
self.type = action_type | |
self.data = data | |
def get_data(self): | |
return self.data | |
def get_type(self): | |
return self.type | |
class PlantView(object): | |
def __init__(self, p): | |
self.x = p.x | |
self.y = p.y | |
self.eff = p.get_eff() | |
def get_pos(self): | |
return (self.x, self.y) | |
def get_eff(self): | |
return self.eff | |
class AgentView(object): | |
def __init__(self, agent): | |
(self.x, self.y) = agent.get_pos() | |
self.team = agent.get_team() | |
def get_pos(self): | |
return (self.x, self.y) | |
def get_team(self): | |
return self.team | |
class WorldView(object): | |
def __init__(self, me, agent_views, plant_views, terr_map, energy_map): | |
self.agent_views = agent_views | |
self.plant_views = plant_views | |
self.energy_map = energy_map | |
self.terr_map = terr_map | |
self.me = me | |
def get_me(self): | |
return self.me | |
def get_agents(self): | |
return self.agent_views | |
def get_plants(self): | |
return self.plant_views | |
def get_terr(self): | |
return self.terr_map | |
def get_energy(self): | |
return self.energy_map | |
class Display(object): | |
black = (0, 0, 0) | |
red = (255, 0, 0) | |
green = (0, 255, 0) | |
yellow = (255, 255, 0) | |
def __init__(self, size, scale=2): | |
self.width, self.height = size | |
self.scale = scale | |
self.size = (self.width * scale, self.height * scale) | |
pygame.init() | |
self.screen = pygame.display.set_mode(self.size) | |
self.surface = self.screen | |
pygame.display.set_caption("Cells") | |
self.background = pygame.Surface(self.screen.get_size()) | |
self.background = self.background.convert() | |
self.background.fill((150,150,150)) | |
self.text = [] | |
if pygame.font: | |
def show_text(self, text, color, topleft): | |
font = pygame.font.Font(None, 24) | |
text = font.render(text, 1, color) | |
textpos = text.get_rect() | |
textpos.topleft = topleft | |
self.text.append((text, textpos)) | |
else: | |
def show_text(self, text, color, topleft): | |
pass | |
def update(self, terr, pop, plants, agent_map, plant_map, energy_map, | |
ticks, nteams, show_energy, show_agents): | |
# Slower version: | |
# img = ((numpy.minimum(150, 20 * terr.values) << 16) + | |
# ((numpy.minimum(150, 10 * terr.values + 10.energy_map.values)) << 8)) | |
r = numpy.minimum(150, 20 * terr.values) | |
r <<= 16 | |
# g = numpy.minimum(150, 10 * terr.values + 10 * energy_map.values) | |
if show_energy: | |
g = terr.values + energy_map.values | |
g *= 10 | |
g = numpy.minimum(150, g) | |
g <<= 8 | |
img = r | |
if show_energy: | |
img += g | |
# b = numpy.zeros_like(terr.values) | |
img_surf = pygame.Surface((self.width, self.height)) | |
pygame.surfarray.blit_array(img_surf, img) | |
if show_agents: | |
img_surf.blit(agent_map.surf, (0,0)) | |
img_surf.blit(plant_map.surf, (0,0)) | |
scale = self.scale | |
pygame.transform.scale(img_surf, | |
self.size, self.screen) | |
if not ticks % 60: | |
#todo: find out how many teams are playing | |
team_pop = [0] * nteams | |
for team in xrange(nteams): | |
team_pop[team] = sum(1 for a in pop if a.team == team) | |
self.text = [] | |
drawTop = 0 | |
for t in xrange(nteams): | |
drawTop += 20 | |
self.show_text(str(team_pop[t]), TEAM_COLORS[t], (10, drawTop)) | |
for text, textpos in self.text: | |
self.surface.blit(text, textpos) | |
def flip(self): | |
pygame.display.flip() | |
class Plant(object): | |
color = 0x00FF00 | |
def __init__(self, x, y, eff): | |
self.x = x | |
self.y = y | |
self.eff = eff | |
def get_pos(self): | |
return (self.x, self.y) | |
def get_eff(self): | |
return self.eff | |
def get_view(self): | |
return PlantView(self) | |
class MessageQueue(object): | |
def __init__(self): | |
self.__inlist = [] | |
self.__outlist = [] | |
def update(self): | |
self.__outlist = self.__inlist | |
self.__inlist = [] | |
def send_message(self, m): | |
self.__inlist.append(m) | |
def get_messages(self): | |
return self.__outlist | |
class Message(object): | |
def __init__(self, message): | |
self.message = message | |
def get_message(self): | |
return self.message | |
def main(): | |
global bounds, symmetric, mind_list | |
try: | |
config.read('default.cfg') | |
bounds = config.getint('terrain', 'bounds') | |
symmetric = config.getboolean('terrain', 'symmetric') | |
minds_str = str(config.get('minds', 'minds')) | |
except Exception as e: | |
print 'Got error: %s' % e | |
config.add_section('minds') | |
config.set('minds', 'minds', 'mind1,mind2') | |
config.add_section('terrain') | |
config.set('terrain', 'bounds', '300') | |
config.set('terrain', 'symmetric', 'true') | |
with open('default.cfg', 'wb') as configfile: | |
config.write(configfile) | |
config.read('default.cfg') | |
bounds = config.getint('terrain', 'bounds') | |
symmetric = config.getboolean('terrain', 'symmetric') | |
minds_str = str(config.get('minds', 'minds')) | |
mind_list = [(n, get_mind(n)) for n in minds_str.split(',')] | |
# accept command line arguments for the minds over those in the config | |
try: | |
if len(sys.argv)>2: | |
mind_list = [(n,get_mind(n)) for n in sys.argv[1:] ] | |
except (ImportError, IndexError): | |
pass | |
if __name__ == "__main__": | |
main() | |
while True: | |
game = Game(bounds, mind_list, symmetric, -1) | |
while game.winner is None: | |
game.tick() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# Copyright (c) 2010, Team 2 | |
# | |
# Redistribution and use in source and binary forms, with or without | |
# modification, are permitted provided that the following conditions | |
# are met: | |
# 1. Redistributions of source code must retain the above copyright | |
# notice, this list of conditions and the following disclaimer. | |
# 2. Redistributions in binary form must reproduce the above copyright | |
# notice, this list of conditions and the following disclaimer in the | |
# documentation and/or other materials provided with the distribution. | |
# 3. Neither the name of the Benjamin Meyer nor the names of its contributors | |
# may be used to endorse or promote products derived from this software | |
# without specific prior written permission. | |
# | |
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
# SUCH DAMAGE. | |
# | |
# | |
# Idea: | |
# Keep track of how long we have been alive relative to our plant. | |
# The more time has past, the farther away we will go on a rescue mission and | |
# the more energy we will gather before heading out | |
# | |
# Result: | |
# strong cells have a good chance of making it to another plant where there | |
# are many attacks one after another causing the battle line to shift to a plant | |
# | |
# At the start (weak) cells goto closer attacks and not far away | |
# At the end (strong) cells are sent straight to the (far away) attacking area | |
# | |
import random, cells | |
import cmath, numpy | |
import time | |
class Type: | |
PARENT = 0 | |
SCOUT = 1 | |
class MessageType: | |
ATTACK = 0 | |
FOUNDPLANT = 1 | |
class AgentMind: | |
def __init__(self, args): | |
self.id = 0 | |
self.time = 0 | |
self.type = Type.SCOUT | |
# scout vars | |
self.x = None | |
self.y = None | |
self.search = (random.random() > 0.9) # AKA COW, mostly just go and eat up the world grass so the other team can't | |
self.last_pos = (-1,-1) | |
self.bumps = 0 | |
self.step = 0 | |
self.rescue = None | |
# parent vars | |
self.children = 0 | |
self.plant = None | |
self.plants = [] | |
if args: | |
parent = args[0] | |
self.start = parent.start | |
self.time = parent.time | |
self.plants = parent.plants | |
if len(self.plants) > 7: | |
self.id = random.randrange(0,1) | |
if parent.search: | |
self.search = (random.random() > 0.2) | |
else: | |
#adam | |
self.start = time.time() | |
def choose_new_direction(self, view, msg): | |
me = view.get_me() | |
self.x = random.randrange(-9,9) | |
self.y = random.randrange(-9,9) | |
if self.x == 0 and self.y == 0: | |
self.choose_new_direction(view, msg) | |
self.step = 3 | |
self.bumps = 0 | |
def act_scout(self, view, msg): | |
me = view.get_me() | |
if self.x is None: | |
self.choose_new_direction(view, msg) | |
currentEnergy = view.get_energy().get(me.x, me.y) | |
# Grabbing a plant is the most important thing, we get this we win | |
plants = view.get_plants() | |
if plants : | |
plant = (plants[0]).get_pos() | |
if plant != self.plant: | |
if self.plants.count(plant) == 0: | |
#print "Found a new plant, resetting time: " + str(len(self.plants)) | |
msg.send_message((MessageType.FOUNDPLANT, 0, self.id, me.x, me.y)) | |
self.plants.append(plant) | |
self.time = 0 | |
self.plant = plant | |
self.type = Type.PARENT | |
self.search = None | |
#print str(len(self.plants)) + " " + str(me.get_team()) | |
return self.act_parent(view, msg) | |
else: | |
# Don't let this go to waste | |
if currentEnergy >= 3: | |
return cells.Action(cells.ACT_EAT) | |
if self.search: | |
if me.energy > 100: | |
spawn_x, spawn_y = self.smart_spawn(me, view) | |
return cells.Action(cells.ACT_SPAWN, (me.x + spawn_x, me.y + spawn_y, self)) | |
if (currentEnergy > 3) : | |
return cells.Action(cells.ACT_EAT) | |
# Make sure we wont die | |
if (me.energy < 25 and currentEnergy > 1) : | |
return cells.Action(cells.ACT_EAT) | |
# hit world wall, bounce back | |
map_size = view.energy_map.width | |
if me.x <= 0 or me.x >= map_size-1 or me.y <= 0 or me.y >= map_size-1 : | |
self.choose_new_direction(view, msg) | |
# If I get the message of help go and rescue! | |
if self.step == 0 and (not self.search) and (random.random()>0.2): | |
ax = 0; | |
ay = 0; | |
best = 300 + self.time / 2 | |
message_count = len(msg.get_messages()); | |
for m in msg.get_messages(): | |
(type, count, id, ox, oy) = m | |
if (id == self.id and type == MessageType.ATTACK) : | |
dist = abs(me.x-ax) + abs(me.y-ay) | |
if count >= 2: | |
dist /= count | |
if dist < best and dist > 1: | |
ax = ox | |
ay = oy | |
best = dist | |
if (ax != 0 and ay != 0) : | |
dir = ax-me.x + (ay - me.y) * 1j | |
r, theta = cmath.polar(dir) | |
theta += 0.1 * random.random() - 0.5 | |
dir = cmath.rect(r, theta) | |
self.x = dir.real | |
self.y = dir.imag | |
# if (message_count > 1) : | |
# # Attack the base, not the front | |
# agent_scale = 1 + random.random() | |
# self.x *= agent_scale | |
# self.y *= agent_scale | |
# don't stand still once we get there | |
if (self.x == 0 and self.y == 0) : | |
self.x = random.randrange(-2, 2) | |
self.y = random.randrange(-2, 2) | |
self.step = random.randrange(1, min(30, max(2,int((best+2)/2)))) | |
self.rescue = True | |
if not self.rescue and me.energy > cells.SPAWN_TOTAL_ENERGY and me.energy < 100: | |
spawn_x, spawn_y = self.smart_spawn(me, view) | |
return cells.Action(cells.ACT_SPAWN,(me.x + spawn_x, me.y + spawn_y, self)) | |
# Back to step 0 we can change direction at the next attack. | |
if self.step: | |
self.step -= 1 | |
return self.smart_move(view, msg) | |
def get_available_space_grid(self, me, view): | |
grid = numpy.ones((3,3)) | |
grid[1,1] = 0 | |
for agent in view.get_agents(): | |
grid[agent.x - me.x + 1, agent.y - me.y + 1] = 0 | |
for plant in view.get_plants(): | |
grid[plant.x - me.x + 1, plant.y - me.y + 1] = 0 | |
return grid | |
def smart_move(self, view, msg): | |
me = view.get_me() | |
# make sure we can actually move | |
if me.get_pos() == self.last_pos: | |
self.bumps += 1 | |
else: | |
self.bumps = 0 | |
if self.bumps >= 2: | |
self.choose_new_direction(view, msg) | |
self.last_pos = view.me.get_pos() | |
offsetx = 0 | |
offsety = 0 | |
if self.search: | |
offsetx = random.randrange(-1, 1) | |
offsety = random.randrange(-1, 1) | |
wx = me.x + self.x + offsetx | |
wy = me.y + self.y + offsety | |
grid = self.get_available_space_grid(me, view) | |
bestEnergy = 2 | |
bestEnergyX = -1 | |
bestEnergyY = -1 | |
for x in xrange(3): | |
for y in range(3): | |
if grid[x,y]: | |
e = view.get_energy().get(me.x + x-1, me.y + y-1) | |
if e > bestEnergy: | |
bestEnergy = e; | |
bestEnergyX = x | |
bestEnergyY = y; | |
# Check the desired location first | |
if (wx < me.x) : bx = 0 | |
if (wx == me.x) : bx = 1 | |
if (wx > me.x) : bx = 2 | |
if (wy < me.y) : by = 0 | |
if (wy == me.y) : by = 1 | |
if (wy > me.y) : by = 2 | |
if bx == bestEnergyX and bestEnergy > 1: | |
return cells.Action(cells.ACT_MOVE,(me.x + bestEnergyX-1, me.y + bestEnergyY-1)) | |
if by == bestEnergyY and bestEnergy > 1: | |
return cells.Action(cells.ACT_MOVE,(me.x + bestEnergyX-1, me.y + bestEnergyY-1)) | |
if grid[bx,by]: | |
return cells.Action(cells.ACT_MOVE,(wx, wy)) | |
if bestEnergy > 1: | |
return cells.Action(cells.ACT_MOVE,(me.x + bestEnergyX-1, me.y + bestEnergyY-1)) | |
if grid[2,0] and random.random() > 0.5: | |
return cells.Action(cells.ACT_MOVE,(me.x + 1, me.y - 1)) | |
for x in xrange(3): | |
for y in range(3): | |
if grid[x,y]: | |
return cells.Action(cells.ACT_MOVE,(x-1, y-1)) | |
return cells.Action(cells.ACT_MOVE,(wx, wy)) | |
def smart_spawn(self, me, view): | |
grid = self.get_available_space_grid(me, view) | |
# So we don't always spawn in our top left | |
if grid[2,0] and random.random() > 0.8: | |
return (1, -1) | |
for x in xrange(3): | |
for y in range(3): | |
if grid[x,y]: | |
return (x-1, y-1) | |
return (-1, -1) | |
def should_attack(self, view, msg): | |
me = view.get_me() | |
count = 0 | |
for a in view.get_agents(): | |
if a.get_team() != me.get_team(): | |
count += 1 | |
if count > 0: | |
currentEnergy = view.get_energy().get(me.x, me.y) | |
if currentEnergy > 20: | |
return cells.Action(cells.ACT_EAT) | |
if self.plant: | |
count = 10 | |
msg.send_message((MessageType.ATTACK, count, self.id, me.x, me.y)) | |
return cells.Action(cells.ACT_ATTACK, a.get_pos()) | |
return None | |
def check(self, x, y, view): | |
plant_pos = (px, py) = self.plant | |
me = view.get_me() | |
oldx = x | |
oldy = y | |
x += me.x | |
y += me.y | |
# Make sure the plant is always populated | |
grid = self.get_available_space_grid(me, view) | |
if abs(px - x) <= 1 and abs(py - y) <= 1: | |
grid = self.get_available_space_grid(me, view) | |
if grid[oldx+1, oldy+1] == 1: | |
#print str(x) + " " + str(y) + " " + str(abs(px - x)) + " " + str(abs(py - y)) | |
return True | |
return None | |
def act_parent(self, view, msg): | |
me = view.get_me() | |
plant_pos = (px, py) = self.plant | |
# Make sure the plant is always populated | |
grid = self.get_available_space_grid(me, view) | |
xoffset = -2 | |
yoffset = -2 | |
if self.check( 1, 0, view): xoffset = 1; yoffset = 0; # right | |
if self.check(-1, 0, view): xoffset = -1; yoffset = 0; # left | |
if self.check( 0, 1, view): xoffset = 0; yoffset = 1; # down | |
if self.check( 0, -1, view): xoffset = 0; yoffset = -1; # up | |
if self.check( -1, -1, view): xoffset = -1; yoffset = -1; # diag left | |
if self.check( -1, 1, view): xoffset = -1; yoffset = 1; # diag right | |
if self.check( 1, -1, view): xoffset = 1; yoffset = -1; # diag left | |
if self.check( 1, 1, view): xoffset = 1; yoffset = 1; # diag right | |
if xoffset != -2: | |
if me.energy < cells.SPAWN_TOTAL_ENERGY : return cells.Action(cells.ACT_EAT) | |
# When we are populating plant cells we must spawn some children in case we are being attacked | |
# When we are all alone we don't spawn any cheap children and only do high quality cells | |
self.children += 1 | |
return cells.Action(cells.ACT_SPAWN, (me.x + xoffset, me.y + yoffset, self)) | |
# When there are more then two plants always charge up and then leave | |
# when there are less then two plants only half of the cells should charge up and then leave | |
if self.children <= 0: | |
if me.energy >= cells.ENERGY_CAP or me.energy > cells.SPAWN_TOTAL_ENERGY + self.time + random.randrange(-10,100): | |
self.type = Type.SCOUT | |
return self.act_scout(view, msg) | |
return cells.Action(cells.ACT_EAT) | |
if me.energy < cells.SPAWN_TOTAL_ENERGY : | |
return cells.Action(cells.ACT_EAT) | |
self.children -= 1 | |
spawn_x, spawn_y = self.smart_spawn(me, view) | |
return cells.Action(cells.ACT_SPAWN,(me.x + spawn_x, me.y + spawn_y, self)) | |
def act(self, view, msg): | |
self.time += 1 | |
r = self.should_attack(view, msg) | |
if r: return r | |
me = view.get_me() | |
TRIG_TIME = 4 * 60 + 30 | |
TRIG_TIME = 30 | |
if time.time() > self.start + TRIG_TIME: | |
if me.energy > cells.SPAWN_TOTAL_ENERGY: | |
spawn_x, spawn_y = self.smart_spawn(me, view) | |
return cells.Action(cells.ACT_SPAWN,(me.x + spawn_x, me.y + spawn_y, self)) | |
if self.type == Type.PARENT: | |
return self.act_parent(view, msg) | |
if self.type == Type.SCOUT: | |
return self.act_scout(view, msg) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment