Skip to content

Commit 3e6f330

Browse files
author
adrian
committed
Added agent categories. Added simple routing. Adjusted canvas size.
1 parent 0244ede commit 3e6f330

File tree

2 files changed

+140
-54
lines changed

2 files changed

+140
-54
lines changed

src/backend/universe.py

Lines changed: 125 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77

88
logger = logging.getLogger(__name__)
99

10+
11+
def distance(a, b):
12+
d = ((b.x-a.x)**2 + (b.y-a.y)**2)**(1/2)
13+
return round(d,2)
14+
1015
class Cycle:
1116
def __init__(self, cycle, agents, foods):
1217
self.cycle = cycle
@@ -22,15 +27,16 @@ def toJSON(obj):
2227
return result
2328

2429
def buildAgentState(self, agent):
25-
return AgentState(agent.id, agent.alive(), agent.x, agent.y, agent.health)
30+
return AgentState(agent.id, agent.alive(), agent.x, agent.y, agent.health, agent.category)
2631

2732
class AgentState:
28-
def __init__(self, uuid, alive, x, y, health):
33+
def __init__(self, uuid, alive, x, y, health, category):
2934
self.id = uuid
3035
self.alive = alive
3136
self.x = x
3237
self.y = y
3338
self.health = health
39+
self.category = category
3440
self.type = "AgentState"
3541

3642
class Food:
@@ -39,6 +45,7 @@ def __init__(self, x, y):
3945
self.y = y
4046
self.id = uuid.uuid4().hex
4147
self.type = "Food"
48+
4249

4350
class Agent(object):
4451
def __init__(self, environment):
@@ -50,21 +57,26 @@ def __init__(self, environment):
5057
self.x = random.randint(0, environment.size)
5158
self.y = random.randint(0, environment.size)
5259
self.health = 10
53-
self.state = AgentState(self.id, self.alive(), self.x, self.y, self.health)
5460
self.foods = []
5561
self.logger = logging.getLogger(__name__)
62+
self.category = random.choice([1,2,3])
63+
self.state = AgentState(self.id, self.alive(), self.x, self.y, self.health, self.category)
5664
self.type = "Agent"
65+
self.route = []
66+
self.seen = []
67+
5768

5869
# each step should perform various combinations of each behaviour
59-
def step(self):
70+
def step(self, foods):
6071
if not self.alive():
61-
return self.state
72+
return self.foods
6273

63-
foods, agents = self.see()
64-
self.move(foods)
74+
self.environment.foods = foods
75+
self.move()
76+
self.eat()
6577
self.health = self.health - self.environment.decayRate
66-
self.state = AgentState(self.id, self.alive(), self.x, self.y, self.health)
67-
return self.state
78+
self.state = AgentState(self.id, self.alive(), self.x, self.y, self.health, self.category)
79+
return self.foods
6880

6981
def size(self):
7082
return len(self.foods)
@@ -74,64 +86,112 @@ def alive(self):
7486

7587
# Actions, should take a vector, actor accelerates in that direction
7688
# degrees are made up from combinations of [up right, down right, up left, down left]
77-
def move(self, visibleFoods):
89+
def move(self):
7890
# need to avoid agents that are larger
7991
# need to eat the closest food
8092
# question, build a policy engine and router
8193
# use reinforcement learning to build policies?
82-
for i in range(self.environment.velocity):
83-
visibleFoods = self.eat(visibleFoods)
94+
def travel(x, y):
95+
if x < 0:
96+
x = self.environment.width
97+
if x > self.environment.width:
98+
x = 0
99+
if y < 0:
100+
y = self.environment.height
101+
if y > self.environment.height:
102+
y = 0
103+
return x, y
104+
105+
def findFoodAndDistance():
106+
foods, agents = self.see()
107+
foods = list((sorted(foods, key=lambda food: abs(distance(self, food)))))
108+
food = foods[1]
109+
d = abs(int(distance(self, food)))
110+
return food, d
111+
112+
def route():
113+
self.eat()
114+
food, d = findFoodAndDistance()
115+
if d == 0:
116+
self.logger.warning("NOPE ROUTE HERE %d %d" % (d, self.health))
117+
self.eat();
118+
self.logger.warning("HEALTH %d " % self.health)
119+
food, d = findFoodAndDistance()
120+
121+
x = self.x
122+
y = self.y
123+
sign = lambda n, n1 : 0 if n == n1 else (1 if n > n1 else -1)
124+
for i in range(d):
125+
xs = sign(food.x, x)
126+
x = x + xs
127+
ys = sign(food.y, y)
128+
y = y + ys
129+
#tx, ty = travel(x, y)
130+
self.logger.warning("%d %d" % (x, y))
131+
#if (tx == x & ty == y):
132+
self.route.append((xs, ys))
133+
134+
self.logger.warning("ROUTE LENGTH %d" % len(self.route))
135+
136+
def routeTraverse():
137+
if len(self.route) > 0:
138+
point = self.route.pop()
139+
self.x = self.x + point[0]
140+
self.y = self.y + point[1]
141+
self.eat()
142+
143+
if (self.category == 1):
84144
# move one space in a random direction
85145
rl = random.choice([(1,0), (0,1), (-1,0),(0,-1),(2, -1), (-1, 2), (-2, 1), (1, -2)])
86146
self.x = self.x + rl[0]
87147
self.y = self.y + rl[0]
88-
if self.x < 0:
89-
self.x = self.environment.width
90-
if self.x > self.environment.width:
91-
self.x = 0
92-
if self.y < 0:
93-
self.y = self.environment.height
94-
if self.y > self.environment.height:
95-
self.y = 0
148+
x, y = travel(self.x, self.y)
149+
self.x = x
150+
self.y = y
96151

152+
elif self.category == 2 | self.category == 3:
153+
food, d = findFoodAndDistance()
154+
if (len(self.route) == 0):
155+
route()
156+
routeTraverse()
157+
158+
self.eat()
97159

98160
# senses
99161
# get all objects that surround the agent
100162
# with each move the agent should update the in view list
101163
def see(self):
102-
def distance(a, b):
103-
return ((b.x - a.x)**2 + (b.y - a.y)**2)**1/2
104-
105164
currentEnvironment = self.environment
106-
foods = list(filter(lambda food: distance(self, food) <= currentEnvironment.visibility, currentEnvironment.foods))
165+
foods = list(filter(lambda food: int(distance(self, food)) <= currentEnvironment.visibility, currentEnvironment.foods))
107166
agents = list(filter(lambda agent: currentEnvironment.agents, currentEnvironment.agents))
167+
# see food diet, if I can see it, I can eat it
168+
self.seen = foods
108169
return foods, agents
109170

110171
# Rules/behaviours
111172
# each move should check whether rules
112173
# rules need to accept the environment
113-
def eat(self, visibleFoods):
114-
onFood = list(filter(lambda food: self.x == food.x & self.y == food.y, visibleFoods))
115-
self.health = self.health + len(onFood)
116-
self.foods.append(onFood)
117-
if len(onFood) > 0:
118-
self.logger.debug("ATE FOOD")
119-
# would be better to be a dictionary and delete by id?
120-
self.environment.foods = list(filter(lambda food: food not in onFood, self.environment.foods))
121-
visibleFoods = list(filter(lambda food: food not in onFood, visibleFoods))
122-
return visibleFoods
174+
def eat(self):
175+
currentEnvironment = self.environment
176+
onFoods = [food for food in self.seen if (self.x == food.x) & (self.y == food.y)]
177+
if len(onFoods) == 0:
178+
#self.logger.warning("NAH", self.x, self.y)
179+
return self.foods
180+
181+
for onFood in onFoods:
182+
self.health = self.health + 1
183+
self.foods.append(onFood)
184+
return self.foods
123185

124186
# each AgarAgent needs access to the environment
125187
@ray.remote
126188
class Environment(object):
127-
def __init__(self, size, agentVelocity, agentVisibility, foodSpawnRate, numberOfAgents, decayRate):
189+
def __init__(self, size, agentVisibility, foodSpawnRate, numberOfAgents, decayRate):
128190
self.size = size
129191
self.width = size
130192
self.height = size
131193

132194
# constants
133-
# max number of spaces an agent can move each turn
134-
self.velocity = agentVelocity
135195
# minimum size difference an agent needs to be to eat another agent
136196
self.edibleThreshold = 5
137197
# radius that agents can see other objects
@@ -142,6 +202,7 @@ def __init__(self, size, agentVelocity, agentVisibility, foodSpawnRate, numberOf
142202
self.numberOfAgents = numberOfAgents
143203

144204
self.decayRate = decayRate
205+
self.lastFoods = []
145206
self.foods = []
146207
self.agents = []
147208
self.agents = [Agent(self) for i in range(self.numberOfAgents)]
@@ -151,18 +212,22 @@ def __init__(self, size, agentVelocity, agentVisibility, foodSpawnRate, numberOf
151212
def printAlive(self):
152213
point = lambda x: 1 if x.alive() else 0
153214
aliveCount = sum(map(point, self.agents))
154-
avgHealth = mean(map(lambda x: x.health, self.agents)) if len(self.agents) > 0 else 0
215+
avgHealth = max(map(lambda x: x.health, self.agents)) if len(self.agents) > 0 else 0
155216
self.logger.warning("=== ALIVE c: %d h: %d ====" % (aliveCount, avgHealth))
156217

157218
def run(self, itr):
158219
self.logger.warning("=== CYCLE %d ===" % itr)
159220
self.generateFood()
160-
[agent.step() for agent in self.agents]
221+
self.lastFoods = self.foods
161222
self.logger.warning("=== FOODS %d ===" % len(self.foods))
223+
for agent in self.agents:
224+
foods = agent.step(self.foods)
225+
self.foods = list(filter(lambda f: f.id not in list(map(lambda f1: f1.id, foods)), self.foods))
226+
162227
self.printAlive()
163228
self.logger.warning("=== FOODS %d ===" % len(self.foods))
164229
self.logger.warning("\n")
165-
return self.agents, self.foods
230+
return self.agents, self.lastFoods
166231

167232
def occupiedSpot(self, x, y):
168233
for food in self.foods:
@@ -179,22 +244,38 @@ def generateFood(self):
179244
originalFoodCount = len(self.foods)
180245
foodCount = len(self.foods)
181246
tryCount = 0
182-
while foodCount < originalFoodCount + self.foodSpawnRate and tryCount < 5:
247+
while (foodCount < originalFoodCount + self.foodSpawnRate) and tryCount < 5:
183248
foodCount = len(self.foods)
184249
x = random.randint(0, self.width)
185250
y = random.randint(0, self.height)
186251
if not self.occupiedSpot(x, y):
187252
food = Food(x, y)
188253
self.foods.append(food)
254+
tryCount = 0
189255
else:
190256
tryCount += 1
257+
191258

192259
# information of each cycle should be sent over socket
193260
async def run(ws):
261+
logger = logging.getLogger(__name__)
194262
if not ray.is_initialized():
195263
ray.init()
196-
environment = Environment.remote(5, 5, 5, 20, 10, 1)
197-
for i in range(50):
264+
environment = Environment.remote(10, 20, 10, 5, 1)
265+
i = 0
266+
for i in range(1000):
198267
agents, foods = ray.get(environment.run.remote(i))
199-
await ws.send(json.dumps(Cycle(i, agents, foods), default=Cycle.toJSON))
268+
if len(list(filter(lambda x : x.alive(), agents))) > 0 :
269+
await ws.send(json.dumps(Cycle(i, agents, foods), default=Cycle.toJSON))
270+
else:
271+
logger.warning("=== CYCLES COMPLETED %d ===" % i)
272+
273+
if __name__ == "__main__":
274+
if not ray.is_initialized():
275+
ray.init()
276+
environment = Environment(10, 20, 20, 1, 1)
277+
for i in range(30):
278+
agents, foods = environment.run(i)
279+
if len(list(filter(lambda x : x.alive(), agents))) == 0:
280+
break
200281
#ray.shutdown()

src/frontend/src/components/Environment.vue

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ const { uniqueNamesGenerator, names, adjectives, colors } = require('unique-name
7171
class Food {
7272
constructor(food, sketch) {
7373
this.sketch = sketch;
74-
this.x = food.x * 100 + 25;
75-
this.y = food.y * 100 + 25;
74+
this.x = (food.x * 50) + 25;
75+
this.y = (food.y * 50) + 25;
7676
this.id = food.id;
7777
}
7878
@@ -84,7 +84,7 @@ class Food {
8484
display(sketch) {
8585
sketch.fill("yellow")
8686
sketch.strokeWeight(2);
87-
sketch.ellipse(this.x, this.y, 10, 10);
87+
sketch.ellipse(this.x, this.y, 5, 5);
8888
}
8989
}
9090
@@ -93,10 +93,11 @@ class Agent {
9393
this.sketch = sketch;
9494
this.id = agent.id;
9595
this.alive = agent.alive;
96-
this.x = (agent.x * 100) + 25;
97-
this.y = (agent.y * 100) + 25;
96+
this.x = (agent.x * 50) + 25;
97+
this.y = (agent.y * 50) + 25;
9898
this.health = agent.health;
9999
this.namesMap = namesMap;
100+
this.category = agent.category;
100101
this.generateDisplayName();
101102
}
102103
@@ -119,10 +120,14 @@ class Agent {
119120
if (this.health == 0) {
120121
sketch.text(this.health, this.x, this.y)
121122
return;
122-
}
123-
sketch.fill("blue");
123+
}
124+
if (this.category == 1) {
125+
sketch.fill("blue");
126+
} else {
127+
sketch.fill("black");
128+
}
124129
sketch.strokeWeight(2);
125-
let radius = Math.max(5, this.health);
130+
let radius = Math.max(10, this.health * 5);
126131
sketch.ellipse(this.x, this.y, radius, radius);
127132
sketch.fill("black");
128133
sketch.text(this.namesMap[this.id] + "::" + this.health, this.x, this.y)
@@ -150,7 +155,7 @@ class Cycle {
150155
151156
run(sketch) {
152157
if (this.foods && this.foods.length > 0) {
153-
for (let i = this.foods.length-1; i >= 0; i--) {
158+
for (let i = 0; i < this.foods.length; i++) {
154159
let food = new Food(this.foods[i], sketch);
155160
food.run(sketch);
156161
}
@@ -198,7 +203,7 @@ export default {
198203
},
199204
setup(sketch) {
200205
sketch.noLoop();
201-
sketch.createCanvas(650, 550);
206+
sketch.createCanvas(575, 525);
202207
this.cycleSocket = new Cycles(this.cycles);
203208
this.sketch = sketch;
204209
},

0 commit comments

Comments
 (0)