Skip to content

Instantly share code, notes, and snippets.

@Gautier
Created July 1, 2010 20:01
Show Gist options
  • Save Gautier/460475 to your computer and use it in GitHub Desktop.
Save Gautier/460475 to your computer and use it in GitHub Desktop.
#!/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()
#
# 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