-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbeing.py
127 lines (96 loc) · 3.97 KB
/
being.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import numpy as np
from math import cos, sin, degrees, atan2, hypot
import random
# Hyper-parameters
ENERGY_LOSS_GENERAL = 0.001
ENERGY_LOSS_ACTIONS = 0.005
FOOD_TO_ENERGY = 0.001
WATER_TO_ENERGY = 0.001
# Rotation vectors for movement
rotation_angle = 45
theta = np.deg2rad(rotation_angle)
rot_right = np.array([[cos(theta), -sin(theta)], [sin(theta), cos(theta)]])
rot_left = np.array([[cos(-theta), -sin(-theta)], [sin(-theta), cos(-theta)]])
class Being:
"""
Living being representation. If energy reaches 0 it will be dead
Energy goes down when performing actions, low energy leads to lower happiness.
"""
def __init__(self, sprite_index):
# Subjective states (things the "brain" feels)
self.happiness = 1.
self.hunger = 0.
self.thirst = 0.
# Objective states (hidden from the "brain")
self.food = 1.
self.water = 1.
self.energy = 1.
# State
self.angle = 0.
self.direction = [1, 0] # vector from (0,0) (the being) to the direction its facing
self.speed = 0.
self.action_space = ['NOOP', 'TURN_LEFT', 'TURN_RIGHT', 'MOVE', 'STOP', 'EAT', 'DRINK']
# Vision
self.vision_angle = 45 # degrees of vision, it can see forward, and vision_angle/2 to each side
self.vision_pixels = 5 # resolution of vision, size of the 2-d array it can "see"
self.vision_distance = 10 # distance it can see objects at
# Don't change these
self.vision_chunk_size = self.vision_angle // self.vision_pixels
self.sprite_index = sprite_index
def choose_action(self, vision):
"""
Choose an action based on the current state and the vision
:param vision:
:return:
"""
state = [self.happiness, self.hunger, self.thirst, self.energy, *vision]
# TODO: Use the state to learn somehow
action = random.choice(self.action_space)
if action == 'TURN_LEFT' or action == 'TURN_RIGHT':
rot = rot_left if action == 'TURN_LEFT' else rot_right
self.direction = np.round(np.dot(rot, self.direction), 0).astype(int)
self.angle += theta if action == 'TURN_LEFT' else -theta
return action
def step(self, location, being_locations):
self.energy = max(0, self.energy - ENERGY_LOSS_GENERAL)
if self.energy < 1:
if self.food > 0:
self.food = max(0, self.food - FOOD_TO_ENERGY)
self.energy = min(1, self.energy + FOOD_TO_ENERGY)
if self.water > 0:
self.water = max(0, self.water - WATER_TO_ENERGY)
self.energy = min(1, self.energy + WATER_TO_ENERGY)
if self.speed > 0:
self.energy = max(0, self.energy - ENERGY_LOSS_ACTIONS)
vision = self.vision(location, being_locations)
return self.choose_action(vision)
def vision(self, location, locations):
"""
Calculate the vision array
:param locations:
:return:
"""
direction_angle = degrees(atan2(self.direction[1], self.direction[0]))
min_angle = direction_angle - self.vision_angle / 2
max_angle = direction_angle + self.vision_angle / 2
min_angle %= 360
max_angle %= 360
vision = [0] * self.vision_pixels
# calculate beings in its field of view
for coords in locations:
if coords == location:
# skip itself
continue
y = coords[1] - location[1]
x = coords[0] - location[0]
angle = degrees(atan2(y, x))
angle %= 360
if min_angle <= angle <= max_angle:
dist = hypot(x, y)
if dist <= self.vision_distance:
# visible
vision_chunk = int((angle - min_angle) // self.vision_chunk_size)
vision[vision_chunk] += 1
return vision
def is_alive(self):
return self.energy > 0