├── sblearn
├── __init__.py
├── substances.py
├── modelling.py
├── states.py
├── brain.py
├── visualization.py
├── field.py
├── entities.py
└── action_library.py
└── example.py
/sblearn/__init__.py:
--------------------------------------------------------------------------------
1 | import action_library
2 |
--------------------------------------------------------------------------------
/sblearn/substances.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | class Substance(object):
5 | def __init__(self):
6 | self.name = "Substance"
7 |
--------------------------------------------------------------------------------
/sblearn/modelling.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import copy
4 |
5 |
6 | def run_simulation(initial_field, check_stop_function, score_function, times=5, verbose=False):
7 | list_results = []
8 | for iteration in range(times):
9 | field = copy.deepcopy(initial_field)
10 | while not check_stop_function(field):
11 | field.make_time()
12 | current_score = score_function(field)
13 | list_results.append(current_score)
14 | if verbose:
15 | print "Iteration: {0} Score: {1})".format(iteration+1, current_score)
16 |
17 | return list_results
18 |
--------------------------------------------------------------------------------
/sblearn/states.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import action_library as actions
4 |
5 |
6 | class State(object):
7 | def __init__(self, subject):
8 | self.subject = subject
9 | self.duration = 0
10 |
11 | def affect(self):
12 | self.duration += 1
13 |
14 |
15 | class Pregnant(State):
16 | def __init__(self, subject):
17 | super(Pregnant, self).__init__(subject)
18 |
19 | self.timing = 15
20 |
21 | def affect(self):
22 | super(Pregnant, self).affect()
23 |
24 | if self.duration == self.timing:
25 | self.subject.action_queue.insert(0, actions.GiveBirth(self.subject, self))
26 |
27 |
28 | class NotTheRightMood(State):
29 | def __init__(self, subject):
30 | super(NotTheRightMood, self).__init__(subject)
31 |
32 | self.timing = 10
33 |
34 | def affect(self):
35 | super(NotTheRightMood, self).affect()
36 |
37 | if self.duration == self.timing:
38 | self.subject.remove_state(self)
39 |
--------------------------------------------------------------------------------
/sblearn/brain.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import unittest
3 |
4 |
5 | class LearningMemory(object):
6 | def __init__(self, host):
7 | self.host = host
8 | self.memories = {}
9 |
10 | def save_state(self, state, action):
11 | self.memories[action] = {"state": state}
12 |
13 | def save_results(self, results, action):
14 | if action in self.memories:
15 | self.memories[action]["results"] = results
16 | else:
17 | pass
18 |
19 | def make_table(self, action_type):
20 | table_list = []
21 | for memory in self.memories:
22 | if isinstance(memory, action_type):
23 | if "state" not in self.memories[memory] or "results" not in self.memories[memory]:
24 | continue
25 | row = self.memories[memory]["state"][:]
26 | row.append(self.memories[memory]["results"])
27 | table_list.append(row)
28 |
29 | return table_list
30 |
31 | def obliviate(self):
32 | self.memories = {}
33 |
34 |
35 | class TestLearningMemory(unittest.TestCase):
36 | def setUp(self):
37 | self.mem = LearningMemory(None)
38 |
39 | def test_init(self):
40 | self.assertTrue(self.mem.host is None)
41 |
42 | def test_save_state(self):
43 | self.mem.save_state({"foo": 1, "bar": 2}, 12)
44 | self.mem.save_state({"foo": 6, "bar": 4}, 65)
45 | self.mem.save_state({"spam": 1, "eggs": 2, "time": 55}, "42")
46 |
47 | self.assertEqual(self.mem.memories, {12: {'state': {'foo': 1, 'bar': 2}},
48 | 65: {'state': {'foo': 6, 'bar': 4}},
49 | "42": {'state': {"spam": 1, "eggs": 2, "time": 55}}})
50 |
51 | def test_save_results(self):
52 | self.mem.save_state({"foo": 1, "bar": 2}, 12)
53 | self.mem.save_state({"foo": 6, "bar": 4}, 65)
54 | self.mem.save_state({"spam": 1, "eggs": 2, "time": 55}, "42")
55 |
56 | results = {"done": True, "accomplished": False}
57 |
58 | self.mem.save_results(results, 65)
59 |
60 | self.assertEqual(self.mem.memories, {12: {'state': {'foo': 1, 'bar': 2}},
61 | 65: {'state': {'foo': 6, 'bar': 4},
62 | 'results': False},
63 | "42": {'state': {"spam": 1, "eggs": 2, "time": 55}}})
64 |
65 | self.assertRaises(ValueError, self.mem.save_results, **{"results": results,
66 | "action": 88})
67 |
68 | def test_make_table(self):
69 | self.mem.save_state([1, 2], 12)
70 | self.mem.save_state([6, 4], 65)
71 | self.mem.save_state([1, 2, 55], "42")
72 | results = {"done": True, "accomplished": False}
73 | self.mem.save_results(results, 65)
74 | results = {"done": True, "accomplished": True}
75 | self.mem.save_results(results, 12)
76 |
77 | print self.mem.make_table(int)
78 |
79 | self.assertEqual(self.mem.make_table(int), [[6, 4, False],
80 | [1, 2, True]])
81 |
82 |
83 | if __name__ == '__main__':
84 | unittest.main()
85 |
--------------------------------------------------------------------------------
/sblearn/visualization.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import pygame
4 | from pygame import *
5 |
6 | import tkinter as tk
7 | import tkFileDialog
8 |
9 | # Объявляем переменные
10 | WIN_WIDTH = 800 # Ширина создаваемого окна
11 | WIN_HEIGHT = 400 # Высота
12 | DISPLAY = (WIN_WIDTH, WIN_HEIGHT) # Группируем ширину и высоту в одну переменную
13 | BACKGROUND_COLOR = "#004400"
14 | PLATFORM_WIDTH = 10
15 | PLATFORM_HEIGHT = 10
16 |
17 |
18 | def visualize(field):
19 | pygame.init() # Инициация PyGame, обязательная строчка
20 | screen = pygame.display.set_mode(DISPLAY) # Создаем окошко
21 | pygame.display.set_caption("Field game") # Пишем в шапку
22 | bg = Surface((WIN_WIDTH, WIN_HEIGHT)) # Создание видимой поверхности
23 | # будем использовать как фон
24 | bg.fill(Color(BACKGROUND_COLOR)) # Заливаем поверхность сплошным цветом
25 |
26 | #
27 | # initialize font; must be called after 'pygame.init()' to avoid 'Font not Initialized' error
28 | myfont = pygame.font.SysFont("monospace", 15)
29 | #
30 |
31 | #
32 | f = field
33 | tick = 10
34 | #
35 |
36 | timer = pygame.time.Clock()
37 | go_on = True
38 |
39 | while go_on: # Основной цикл программы
40 | timer.tick(tick)
41 | for e in pygame.event.get(): # Обрабатываем события
42 | if e.type == QUIT:
43 | raise SystemExit, "QUIT"
44 | if e.type == pygame.KEYDOWN:
45 | if e.key == pygame.K_SPACE:
46 | f.pause = not f.pause
47 | elif e.key == pygame.K_s:
48 | root = tk.Tk()
49 | root.withdraw()
50 | file_path = tkFileDialog.asksaveasfilename()
51 | f.save_pickle(file_path)
52 | elif e.key == pygame.K_l:
53 | root = tk.Tk()
54 | root.withdraw()
55 | file_path = tkFileDialog.askopenfilename()
56 | f = field.load_from_pickle(file_path)
57 | f.pause = True
58 | elif e.key == pygame.K_UP:
59 | tick += 10
60 | elif e.key == pygame.K_DOWN and tick >= 11:
61 | tick -= 10
62 | elif e.key == pygame.K_ESCAPE:
63 | go_on = False
64 |
65 | screen.blit(bg, (0, 0)) # Каждую итерацию необходимо всё перерисовывать
66 |
67 | # TODO Нет первого состояния!
68 | # if f.epoch == 1000:
69 | # f.pause = True
70 | f.integrity_check()
71 | f.make_time()
72 | level = f.list_obj_representation()
73 | #
74 |
75 | #
76 | # render text
77 | label = myfont.render("Epoch: {0}".format(f.epoch), 1, (255, 255, 0))
78 | screen.blit(label, (630, 10))
79 |
80 | stats = f.get_stats()
81 | for i, element in enumerate(stats):
82 | label = myfont.render("{0}: {1}".format(element, stats[element]), 1, (255, 255, 0))
83 | screen.blit(label, (630, 25 + (i * 15)))
84 |
85 | #
86 |
87 | x = y = 0 # координаты
88 |
89 | for row in level: # вся строка
90 | for element in row: # каждый символ
91 | pf = Surface((PLATFORM_WIDTH, PLATFORM_HEIGHT))
92 | pf.fill(Color(element.color))
93 | screen.blit(pf, (x, y))
94 |
95 | x += PLATFORM_WIDTH # блоки платформы ставятся на ширине блоков
96 | y += PLATFORM_HEIGHT # то же самое и с высотой
97 | x = 0 # на каждой новой строчке начинаем с нуля
98 |
99 | pygame.display.update() # обновление и вывод всех изменений на экран
100 |
--------------------------------------------------------------------------------
/example.py:
--------------------------------------------------------------------------------
1 | import random
2 |
3 | import numpy as np
4 | from sklearn.exceptions import NotFittedError
5 | from sklearn.linear_model import SGDClassifier
6 |
7 | from sblearn import action_library as actions
8 | from sblearn import brain
9 | from sblearn import entities
10 | from sblearn import field
11 | from sblearn import states
12 | from sblearn import substances
13 | from sblearn import visualization
14 | from sblearn import modelling
15 |
16 |
17 | # Create deity
18 | class Priapus(field.Demiurge): # Create deity
19 | def __init__(self):
20 | self.public_memory = brain.LearningMemory(self)
21 | self.public_decision_model = SGDClassifier(warm_start=True)
22 |
23 | def handle_creation(self, creation, refuse):
24 | if isinstance(creation, entities.Creature):
25 | creation.public_memory = self.public_memory
26 | creation.public_decision_model = self.public_decision_model
27 | creation.memory_type = "public"
28 | creation.model_type = "public"
29 | creation.memory_batch_size = 20
30 |
31 | if creation.sex:
32 | def difference_in_num_substance(entity):
33 | nearest_partner = actions.SearchMatingPartner(entity).do_results()["partner"]
34 | if nearest_partner is None:
35 | return 9e10
36 | else:
37 | self_has_substance = entity.count_substance_of_type(substances.Substance)
38 | partner_has_substance = nearest_partner.count_substance_of_type(substances.Substance)
39 | return partner_has_substance - self_has_substance
40 |
41 |
42 | def possible_partners_exist(entity):
43 | find_partner = actions.SearchMatingPartner(entity)
44 | search_results = find_partner.do_results()
45 | return float(search_results["accomplished"])
46 |
47 | features = [{"func": lambda creation: float(creation.has_state(states.NotTheRightMood)),
48 | "kwargs": {"creation": creation}},
49 | {"func": difference_in_num_substance,
50 | "kwargs": {"entity": creation}},
51 | {"func": possible_partners_exist,
52 | "kwargs": {"entity": creation}}]
53 |
54 | creation.set_memorize_task(actions.GoMating, features,
55 | {"func": lambda creation: creation.chosen_action.results["accomplished"],
56 | "kwargs": {"creation": creation}})
57 |
58 | def plan(creature):
59 | if creature.sex:
60 | try:
61 | # raise NotFittedError
62 | current_features = creature.get_features(actions.GoMating)
63 | current_features = np.asarray(current_features).reshape(1, -1)
64 | if creature.public_decision_model.predict(current_features):
65 | go_mating = actions.GoMating(creature)
66 | creature.queue_action(go_mating)
67 | return
68 | else:
69 | harvest_substance = actions.HarvestSubstance(creature)
70 | harvest_substance.set_objective(
71 | **{"target_substance_type": type(substances.Substance())})
72 | creature.queue_action(harvest_substance)
73 | return
74 | except NotFittedError:
75 | chosen_action = random.choice(
76 | [actions.GoMating(creature), actions.HarvestSubstance(creature)])
77 | if isinstance(chosen_action, actions.HarvestSubstance):
78 | chosen_action.set_objective(
79 | **{"target_substance_type": type(substances.Substance())})
80 | creature.queue_action(chosen_action)
81 | return
82 | else:
83 | harvest_substance = actions.HarvestSubstance(creature)
84 | harvest_substance.set_objective(**{"target_substance_type": type(substances.Substance())})
85 | creature.queue_action(harvest_substance)
86 |
87 | creation.plan_callable = plan
88 |
89 |
90 | universe = field.Field(60, 40) # Create sample universe (length, height)
91 |
92 | universe.set_demiurge(Priapus()) # Assign deity to universe
93 |
94 | # Fill universe with blanks, blocks, other scenery if necessary
95 | for y in range(10, 30):
96 | universe.insert_object(20, y, field.Block())
97 |
98 | for x in range(21, 40):
99 | universe.insert_object(x, 10, field.Block())
100 |
101 | for y in range(10, 30):
102 | universe.insert_object(40, y, field.Block())
103 |
104 | universe.populate(entities.Creature, 20) # Populate universe with creatures
105 |
106 | visualization.visualize(universe)
107 |
108 |
109 | def check_stop_function(field):
110 | return field.epoch >= 500
111 |
112 |
113 | def score_function(field):
114 | stats = field.get_stats()
115 | if "Creature" not in stats:
116 | return 0
117 | else:
118 | return stats["Creature"]
119 |
120 | # res = modelling.run_simulation(universe, check_stop_function, score_function, verbose=True, times=30)
121 | # print res
122 | # print np.asarray(res).mean()
123 |
124 | # random 1000 10 [193, 37, 97, 224, 349, 165, 251, 130, 184, 335]
125 | # SGDClassifier 1000 10 [9, 106, 127, 11, 187, 38, 193, 114, 236, 27]
126 |
127 | # random 500 20 [63, 24, 38, 14, 30, 65, 29, 60, 28, 25, 93, 44, 51, 26, 104, 56, 53, 38, 23, 42] mean 45.299999999999997
128 | # SGDClassifier 500 20 [116, 52, 50, 82, 109, 49, 109, 37, 25, 115, 130, 180, 52, 52, 113, 46, 34, 135, 26, 33] mean 77.25
129 |
130 | # random 500 20 [71, 24, 57, 56, 34, 14, 75, 66, 41, 56, 29, 69, 30, 72, 40, 57, 49, 24, 41, 48] mean 47.65
131 | # SGDClassifier 500 20 [175, 40, 117, 96, 119, 116, 58, 134, 67, 87, 73, 147, 124, 125, 82, 139, 78, 110, 74, 100] mean 103.05
132 |
133 | # random 500 30 [42, 32, 62, 34, 30, 44, 51, 35, 63, 59, 50, 40, 75, 59, 50, 33, 45, 95, 82, 41, 43, 89, 94, 66, 64, 46, 34, 82, 66, 76]
134 | # 56.0666666667
135 | # SGDClassifier 500 30 [62, 85, 72, 42, 17, 48, 74, 53, 42, 73, 57, 29, 82, 51, 80, 84, 86, 73, 51, 36, 85, 85, 46, 59, 68, 33, 44, 38, 62, 26]
136 | # 58.1
137 |
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/sblearn/field.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from entities import *
4 | import pickle
5 | import threading
6 |
7 | import cProfile
8 |
9 |
10 | def profile(func):
11 | """Decorator for run function profile"""
12 |
13 | def wrapper(*args, **kwargs):
14 | profile_filename = func.__name__ + '.prof'
15 | profiler = cProfile.Profile()
16 | result = profiler.runcall(func, *args, **kwargs)
17 | profiler.dump_stats(profile_filename)
18 | return result
19 |
20 | return wrapper
21 |
22 |
23 | class Field(object):
24 | def __init__(self, length, height):
25 | self.__length = length
26 | self.__height = height
27 | self.__field = []
28 | self.__epoch = 0
29 | self.pause = False
30 |
31 | self.demiurge = None
32 |
33 | for y in range(self.__height):
34 | row = []
35 | self.__field.append(row)
36 | for x in range(self.__length):
37 | if y == 0 or x == 0 or y == (height - 1) or x == (length - 1):
38 | init_object = Block()
39 | else:
40 | init_object = Blank()
41 |
42 | init_object.x = x
43 | init_object.y = y
44 | init_object.z = 0
45 |
46 | row.append([init_object])
47 |
48 | @property
49 | def epoch(self):
50 | return self.__epoch
51 |
52 | @property
53 | def length(self):
54 | return self.__length
55 |
56 | @property
57 | def height(self):
58 | return self.__height
59 |
60 | def _get_field(self):
61 | return self.__field
62 |
63 | def get_cell(self, x, y):
64 | return self.__field[y][x]
65 |
66 | def cell_passable(self, x, y):
67 | return self.__field[y][x][-1].passable
68 |
69 | def print_field(self):
70 | for y in range(self.height):
71 | row_str = ''
72 | for element in self.__field[y]:
73 | row_str += str(element[-1]) + ' '
74 | print row_str
75 |
76 | def list_str_representation(self):
77 | representation = []
78 | for y in range(self.height):
79 | row_str = ''
80 | for element in self.__field[y]:
81 | row_str += str(element[-1])
82 | representation.append(row_str)
83 | return representation
84 |
85 | def list_obj_representation(self):
86 | representation = []
87 | for y in range(self.height):
88 | row_list = []
89 | for cell in self.__field[y]:
90 | row_list.append(cell[-1])
91 | representation.append(row_list)
92 | return representation
93 |
94 | def insert_object(self, x, y, entity_object, epoch_shift=0):
95 | if self.demiurge is not None:
96 | refuse = False
97 | self.demiurge.handle_creation(entity_object, refuse)
98 | if refuse:
99 | return
100 |
101 | assert x < self.length
102 | assert y < self.height
103 |
104 | if self.__field[y][x][-1].scenery:
105 | self.__field[y][x].append(entity_object)
106 | else:
107 | self.__field[y][x][-1] = entity_object
108 |
109 | entity_object.z = self.epoch + epoch_shift
110 |
111 | entity_object.board = self
112 | entity_object.x = x
113 | entity_object.y = y
114 |
115 | def remove_object(self, entity_object, x=None, y=None):
116 | if x is not None and y is not None:
117 | cell = self.get_cell(x, y)
118 | cell.remove(entity_object)
119 | else:
120 | for row in self.__field:
121 | for cell in row:
122 | if entity_object in cell:
123 | cell.remove(entity_object)
124 |
125 | def make_time(self):
126 | if self.pause:
127 | return
128 |
129 | for y in range(self.height):
130 | for x in range(self.length):
131 | for element in self.__field[y][x]:
132 | if element.z == self.epoch:
133 | element.live()
134 |
135 | self.__epoch += 1
136 |
137 | def _make_time(self):
138 | if self.pause:
139 | return
140 |
141 | threads_list = []
142 |
143 | for y in range(self.height):
144 | for x in range(self.length):
145 | for element in self.__field[y][x]:
146 | if element.z == self.epoch:
147 | threads_list.append(threading.Thread(target=element.live))
148 |
149 | for t in threads_list:
150 | t.start()
151 |
152 | wait = True
153 |
154 | while wait:
155 | wait = False
156 | for t in threads_list:
157 | if t.isAlive():
158 | wait = True
159 | continue
160 |
161 | self.__epoch += 1
162 |
163 | def integrity_check(self):
164 | error_list = []
165 | # First we check the __field structure
166 | # and make full list of objects
167 | objects_full_list = []
168 | if len(self.__field) != self.height:
169 | error_str = "Field height ({0}) is not equal to the number of rows({1})".format(self.height,
170 | len(self.__field))
171 | error_list.append(error_str)
172 | for y, row in enumerate(self.__field):
173 | if len(row) != self.length:
174 | error_str = "Field length ({0}) is not equal to the number of cells ({1}) in row {2}".format(
175 | self.height, len(self.__field), y)
176 | error_list.append(error_str)
177 | for x, cell in enumerate(row):
178 | if len(cell) == 0:
179 | error_str = "Absolute vacuum (empty list) at coordinates x:{0} y:{1}".format(x, y)
180 | error_list.append(error_str)
181 | for element in cell:
182 | objects_full_list.append(element)
183 | if element.x != x or element.y != y:
184 | error_str = "Object at coordinates x:{0} y:{1} thinks it's at x:{2} y:{3}".format(x, y,
185 | element.x,
186 | element.y)
187 | error_list.append(error_str)
188 | if element.z != self.epoch:
189 | error_str = "Object {0} at spacial coordinates x:{1} y:{2} travels in time. Global " \
190 | "epoch: {3}, its local time: {4}".format(str(element), x, y, self.epoch, element.z)
191 | error_list.append(error_str)
192 |
193 | # Then we check for object doubles
194 |
195 | for line in error_list:
196 | print line
197 | return error_list
198 |
199 | def get_stats(self):
200 | stats = {}
201 |
202 | for row in self.__field:
203 | for cell in row:
204 | for element in cell:
205 | class_name = element.class_name()
206 |
207 | if class_name not in stats:
208 | stats[class_name] = 1
209 | else:
210 | stats[class_name] += 1
211 |
212 | return stats
213 |
214 | def save_pickle(self, filename):
215 | with open(filename, 'wb') as f:
216 | pickle.dump(self, f)
217 |
218 | def find_all_coordinates_by_type(self, type_to_find):
219 | field = self.__field
220 |
221 | list_found = []
222 |
223 | for y, row in enumerate(field):
224 | for x, cell in enumerate(row):
225 | for element in cell:
226 | if isinstance(element, type_to_find):
227 | if (x, y) not in list_found:
228 | list_found.append((x, y))
229 | if element.contains(type_to_find):
230 | if (x, y) not in list_found:
231 | list_found.append((x, y))
232 |
233 | return list_found
234 |
235 | def find_all_entities_by_type(self, type_to_find):
236 | field = self.__field
237 |
238 | list_found = []
239 |
240 | for row in field:
241 | for cell in row:
242 | for element in cell:
243 | if isinstance(element, type_to_find):
244 | if element not in list_found:
245 | list_found.append(element)
246 |
247 | return list_found
248 |
249 | def make_path(self, x1, y1, x2, y2):
250 |
251 | if not self.cell_passable(x2, y2):
252 | return []
253 |
254 | field_map = self.__make_map()
255 | self.__wave(field_map, x1, y1, x2, y2)
256 |
257 | return self.__find_backwards(field_map, x2, y2)
258 |
259 | def __make_map(self):
260 | field_map = []
261 |
262 | for input_row in self.__field:
263 | row = []
264 | for cell in input_row:
265 | if cell[-1].passable:
266 | row.append(None)
267 | else:
268 | row.append(-1)
269 | field_map.append(row)
270 |
271 | return field_map
272 |
273 | @staticmethod
274 | def __wave(field_map, x1, y1, x2, y2):
275 | current_wave_list = [(x1, y1)]
276 | field_map[y1][x1] = 0
277 |
278 | while len(current_wave_list) > 0 and field_map[y2][x2] is None:
279 | next_wave_list = []
280 | for coordinates in current_wave_list:
281 | x, y = coordinates
282 | wave_num = field_map[y][x] + 1
283 |
284 | if (len(field_map) - 1 >= y + 1) and field_map[y + 1][x] is None:
285 | field_map[y + 1][x] = wave_num
286 | next_wave_list.append((x, y + 1))
287 |
288 | if (y > 0) and field_map[y - 1][x] is None:
289 | field_map[y - 1][x] = wave_num
290 | next_wave_list.append((x, y - 1))
291 |
292 | if (len(field_map[y]) - 1 >= x + 1) and field_map[y][x + 1] is None:
293 | field_map[y][x + 1] = wave_num
294 | next_wave_list.append((x + 1, y))
295 |
296 | if (x > 0) and field_map[y][x - 1] is None:
297 | field_map[y][x - 1] = wave_num
298 | next_wave_list.append((x - 1, y))
299 |
300 | current_wave_list = next_wave_list[:]
301 |
302 | @staticmethod
303 | def __find_backwards(field_map, x2, y2):
304 | num_steps = field_map[y2][x2]
305 |
306 | if num_steps is None or num_steps == -1:
307 | return None
308 |
309 | path = [(x2, y2)]
310 | num_steps -= 1
311 |
312 | while num_steps > 0:
313 |
314 | x, y = path[-1]
315 |
316 | possible_steps = []
317 |
318 | if (len(field_map) - 1 >= y + 1) and (field_map[y + 1][x] == num_steps):
319 | possible_steps.append((x, y + 1))
320 | elif (y > 0) and (field_map[y - 1][x] == num_steps):
321 | possible_steps.append((x, y - 1))
322 | elif (len(field_map[y]) - 1 >= x + 1) and (field_map[y][x + 1] == num_steps):
323 | possible_steps.append((x + 1, y))
324 | elif (x > 0) and (field_map[y][x - 1] == num_steps):
325 | possible_steps.append((x - 1, y))
326 |
327 | path.append(random.choice(possible_steps))
328 |
329 | num_steps -= 1
330 |
331 | path.reverse()
332 |
333 | return path
334 |
335 | def coordinates_valid(self, x, y):
336 | if x < 0 or y < 0:
337 | return False
338 |
339 | if x >= self.__length or y >= self.__height:
340 | return False
341 |
342 | return True
343 |
344 | def populate(self, entity_type, number):
345 | for i in range(number):
346 | x = random.randint(0, self.length - 1)
347 | y = random.randint(0, self.height - 1)
348 |
349 | if self.cell_passable(x, y):
350 | self.insert_object(x, y, entity_type())
351 | else:
352 | i -= 1
353 |
354 | def set_demiurge(self, demiurge):
355 | self.demiurge = demiurge
356 |
357 |
358 | class Demiurge(object):
359 | def handle_creation(self, creation, refuse):
360 | pass
361 |
362 |
363 | def load_from_pickle(filename):
364 | with open(filename, 'rb') as f:
365 | field = pickle.load(f)
366 | return field
367 |
--------------------------------------------------------------------------------
/sblearn/entities.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import random
4 | import numpy as np
5 |
6 | import action_library as actions
7 | import brain
8 | import states
9 | import substances
10 |
11 |
12 | class Entity(object):
13 | def __init__(self):
14 | # home universe
15 | self.board = None
16 |
17 | # time-space coordinates
18 | self.x = None
19 | self.y = None
20 | self.z = None
21 |
22 | # lifecycle properties
23 | self.age = 0
24 | self.alive = False
25 | self.time_of_death = None
26 |
27 | # action queues
28 | self.action_queue = []
29 | self.action_log = []
30 |
31 | # common properties
32 | self.passable = False
33 | self.scenery = True
34 | self._container = []
35 | self._states_list = []
36 |
37 | # visualization properties
38 | self.color = "#004400"
39 |
40 | def __str__(self):
41 | raise Exception
42 |
43 | @classmethod
44 | def class_name(cls):
45 | return "Entity"
46 |
47 | def live(self):
48 | self.get_affected()
49 | self.z += 1
50 | self.age += 1
51 |
52 | def get_affected(self):
53 | for state in self._states_list:
54 | state.affect()
55 |
56 | def has_state(self, state_type):
57 | for state in self._states_list:
58 | if isinstance(state, state_type):
59 | return True
60 | return False
61 |
62 | def add_state(self, state):
63 | self._states_list.append(state)
64 |
65 | def remove_state(self, state):
66 | self._states_list.remove(state)
67 |
68 | def contains(self, substance_type):
69 | for element in self._container:
70 | if type(element) == substance_type:
71 | return True
72 | return False
73 |
74 | def extract(self, substance_type):
75 | substance_index = None
76 | for i, element in enumerate(self._container):
77 | if type(element) == substance_type:
78 | substance_index = i
79 | break
80 | if substance_index is None:
81 | return None
82 | return self._container.pop(substance_index)
83 |
84 | def pocket(self, substance_object):
85 | if substance_object is not None:
86 | self._container.append(substance_object)
87 |
88 | def dissolve(self):
89 | self.board.remove_object(self)
90 |
91 | def count_substance_of_type(self, type_of_substance):
92 | num = 0
93 | for element in self._container:
94 | if isinstance(element, type_of_substance):
95 | num += 1
96 |
97 | return num
98 |
99 |
100 | class Blank(Entity):
101 | def __init__(self):
102 | super(Blank, self).__init__()
103 | self.passable = True
104 | self.color = "#004400"
105 |
106 | def __str__(self):
107 | return '.'
108 |
109 | @classmethod
110 | def class_name(cls):
111 | return "Blank"
112 |
113 | def live(self):
114 | super(Blank, self).live()
115 |
116 | if random.random() <= 0.0004:
117 | self._container.append(substances.Substance())
118 |
119 | if len(self._container) > 0:
120 | self.color = "#224444"
121 | else:
122 | self.color = "#004400"
123 |
124 |
125 | class Block(Entity):
126 | def __init__(self):
127 | super(Block, self).__init__()
128 | self.passable = False
129 | self.color = "#000000"
130 |
131 | def __str__(self):
132 | return '#'
133 |
134 | @classmethod
135 | def class_name(cls):
136 | return "Block"
137 |
138 |
139 | class Agent(Entity):
140 | def __init__(self):
141 | super(Agent, self).__init__()
142 | self.passable = False
143 | self.scenery = False
144 | self.name = ''
145 | self.sex = random.choice([True, False])
146 |
147 | self.private_learning_memory = brain.LearningMemory(self)
148 | self.public_memory = None
149 |
150 | self.private_decision_model = None
151 | self.public_decision_model = None
152 |
153 | self.plan_callable = None
154 |
155 | self.memory_type = ""
156 | self.model_type = ""
157 |
158 | self.memory_batch_size = 1
159 |
160 | self.memorize_tasks = {}
161 | self.chosen_action = None
162 |
163 | @classmethod
164 | def class_name(cls):
165 | return "Agent"
166 |
167 | def pre_actions(self, refuse):
168 | return True
169 |
170 | def live(self):
171 | super(Agent, self).live()
172 |
173 | if not self.pre_actions():
174 | return
175 |
176 | if self.need_to_update_plan():
177 | self.plan()
178 |
179 | if len(self.action_queue) > 0:
180 |
181 | current_action = self.action_queue[0]
182 |
183 | self.perform_action_save_memory(current_action)
184 |
185 | while len(self.action_queue) > 0 and self.action_queue[0].instant:
186 | current_action = self.action_queue[0]
187 |
188 | self.perform_action_save_memory(current_action)
189 |
190 | self.update_decision_model()
191 |
192 | def need_to_update_plan(self):
193 | return len(self.action_queue) == 0
194 |
195 | def plan(self):
196 |
197 | if self.plan_callable is not None:
198 | self.plan_callable(self)
199 | return
200 |
201 | def queue_action(self, action):
202 |
203 | if type(action) in self.memorize_tasks:
204 | self.private_learning_memory.save_state(self.get_features(type(action)), action)
205 | self.public_memory.save_state(self.get_features(type(action)), action)
206 |
207 | self.action_queue.append(action)
208 |
209 | def perform_action_save_memory(self, action):
210 | self.chosen_action = action
211 |
212 | if type(action) in self.memorize_tasks:
213 | results = self.perform_action(action)
214 | if results["done"]:
215 | self.private_learning_memory.save_results(self.get_target(type(action)), action)
216 | self.public_memory.save_results(self.get_target(type(action)), action)
217 | else:
218 | results = self.perform_action(action)
219 |
220 | self.chosen_action = None
221 | return results
222 |
223 | def perform_action(self, action):
224 | results = action.do_results()
225 |
226 | if results["done"] or not action.action_possible():
227 | self.action_log.append(self.action_queue.pop(0))
228 |
229 | return results
230 |
231 | def update_decision_model(self):
232 | model_to_use = None
233 | memory_to_use = None
234 |
235 | if self.memory_type == "public":
236 | memory_to_use = self.public_memory
237 | elif self.memory_type == "private":
238 | memory_to_use = self.private_learning_memory
239 |
240 | if self.model_type == "public":
241 | model_to_use = self.public_decision_model
242 | elif self.model_type == "private":
243 | model_to_use = self.private_decision_model
244 |
245 | if memory_to_use is None or model_to_use is None:
246 | raise Exception("You should set memory and model types ('public' or 'private')")
247 |
248 | table_list = memory_to_use.make_table(actions.GoMating)
249 | if len(table_list) >= self.memory_batch_size:
250 | df_train = np.asarray(table_list)
251 | # print df_train
252 | target_column = len(table_list[0])-1
253 | unique_targets = np.unique(df_train[:, target_column]) # TODO maybe discard
254 | if len(unique_targets) > 1:
255 | y_train = df_train[:, [target_column]].ravel()
256 | X_train = np.delete(df_train, target_column, 1)
257 | model_to_use.fit(X_train, y_train)
258 | memory_to_use.obliviate()
259 | print "Update successful"
260 | else:
261 | memory_to_use.obliviate()
262 | print "Memory discarded"
263 |
264 | def set_memorize_task(self, action_types, features_list, target):
265 | if isinstance(action_types, list):
266 | for action_type in action_types:
267 | self.memorize_tasks[action_type] = {"features": features_list,
268 | "target": target}
269 | else:
270 | self.memorize_tasks[action_types] = {"features": features_list,
271 | "target": target}
272 |
273 | def get_features(self, action_type):
274 | if action_type not in self.memorize_tasks:
275 | return None
276 |
277 | features_list_raw = self.memorize_tasks[action_type]["features"]
278 | features_list = []
279 |
280 | for feature_raw in features_list_raw:
281 | if isinstance(feature_raw, dict):
282 | if "kwargs" in feature_raw:
283 | features_list.append(feature_raw["func"](**feature_raw["kwargs"]))
284 | else:
285 | features_list.append(feature_raw["func"]())
286 | elif callable(feature_raw):
287 | features_list.append(feature_raw())
288 | else:
289 | features_list.append(feature_raw)
290 |
291 | return features_list
292 |
293 | def get_target(self, action_type):
294 | if action_type not in self.memorize_tasks:
295 | return None
296 |
297 | target_raw = self.memorize_tasks[action_type]["target"]
298 |
299 | if callable(target_raw):
300 | return target_raw()
301 | elif isinstance(target_raw, dict):
302 | if "kwargs" in target_raw:
303 | return target_raw["func"](**target_raw["kwargs"])
304 | else:
305 | return target_raw["func"]()
306 | else:
307 | return target_raw
308 |
309 |
310 | class Creature(Agent):
311 | def __init__(self):
312 | super(Creature, self).__init__()
313 | self.passable = False
314 | self.scenery = False
315 | self.alive = True
316 | self.name = ''
317 | self.sex = random.choice([True, False])
318 | if self.sex:
319 | self.color = "#550000"
320 | else:
321 | self.color = "#990000"
322 | self.mortal = True
323 | self.private_learning_memory = brain.LearningMemory(self)
324 | self.public_memory = None
325 |
326 | self.private_decision_model = None
327 | self.public_decision_model = None
328 |
329 | self.plan_callable = None
330 |
331 | self.memory_type = ""
332 | self.model_type = ""
333 |
334 | self.memory_batch_size = 1
335 |
336 | self.memorize_tasks = {}
337 | self.chosen_action = None
338 |
339 | def __str__(self):
340 | return '@'
341 |
342 | @classmethod
343 | def class_name(cls):
344 | return "Creature"
345 |
346 | def pre_actions(self):
347 | if (self.time_of_death is not None) and self.z - self.time_of_death > 10:
348 | self.dissolve()
349 | return False
350 |
351 | if not self.alive:
352 | return False
353 |
354 | if random.random() <= 0.001 and self.age > 10:
355 | self.die()
356 | return False
357 |
358 | return True
359 |
360 | def die(self):
361 | if not self.mortal:
362 | return
363 | self.alive = False
364 | self.time_of_death = self.z
365 |
366 | def set_sex(self, sex):
367 | self.sex = sex
368 | if self.sex:
369 | self.color = "#550000"
370 | else:
371 | self.color = "#990000"
372 |
373 | def can_mate(self, with_who):
374 | if isinstance(with_who, Creature):
375 | if with_who.sex != self.sex:
376 |
377 | if not self.alive or not with_who.alive:
378 | return False
379 |
380 | if self.sex:
381 | return not with_who.has_state(states.Pregnant)
382 | else:
383 | return not self.has_state(states.Pregnant)
384 |
385 | return False
386 |
387 | def will_mate(self, with_who):
388 | if not self.can_mate(with_who):
389 | return False
390 |
391 | if self.sex:
392 | if self.has_state(states.NotTheRightMood):
393 | return False
394 | return True
395 | else:
396 | self_has_substance = self.count_substance_of_type(substances.Substance)
397 | partner_has_substance = with_who.count_substance_of_type(substances.Substance)
398 | if self_has_substance + partner_has_substance == 0:
399 | return False
400 | if self_has_substance <= partner_has_substance:
401 | return True
402 | else:
403 | return random.random() < 1. * partner_has_substance / (self_has_substance*3 + partner_has_substance)
404 |
405 |
406 | class BreedingGround(Entity):
407 | def __init__(self):
408 | super(BreedingGround, self).__init__()
409 | self.passable = True
410 | self.color = "#000055"
411 |
412 | def __str__(self):
413 | return "*"
414 |
415 | def live(self):
416 | super(BreedingGround, self).live()
417 |
418 | if not self.board.cell_passable(self.x, self.y):
419 | return
420 |
421 | if random.random() < 0.2:
422 | new_creature = Creature()
423 | self.board.insert_object(self.x, self.y, new_creature)
424 |
425 | @classmethod
426 | def class_name(cls):
427 | return "Breeding ground"
428 |
--------------------------------------------------------------------------------
/sblearn/action_library.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import cProfile
4 | import math
5 | import random
6 |
7 | import entities
8 | import states
9 |
10 |
11 | def profile(func):
12 | """Decorator for run function profile"""
13 |
14 | def wrapper(*args, **kwargs):
15 | profile_filename = func.__name__ + '.prof'
16 | profiler = cProfile.Profile()
17 | result = profiler.runcall(func, *args, **kwargs)
18 | profiler.dump_stats(profile_filename)
19 | return result
20 |
21 | return wrapper
22 |
23 |
24 | class Action(object):
25 | def __init__(self, subject):
26 | self.subject = subject
27 | self.accomplished = False
28 | self._done = False
29 | self.instant = False
30 |
31 | def get_objective(self):
32 | return {}
33 |
34 | def set_objective(self, control=False, **kwargs):
35 | valid_objectives = self.get_objective().keys()
36 |
37 | for key in kwargs.keys():
38 | if key not in valid_objectives:
39 | if control:
40 | raise ValueError("{0} is not a valid objective".format(key))
41 | else:
42 | pass # maybe need to print
43 | else:
44 | setattr(self, "_{0}".format(key), kwargs[key])
45 |
46 | def action_possible(self):
47 | return True
48 |
49 | def do(self):
50 | self.check_set_results()
51 | self._done = True
52 |
53 | def check_set_results(self):
54 | self.accomplished = True
55 |
56 | @property
57 | def results(self):
58 | out = {"done": self._done, "accomplished": self.accomplished}
59 | return out
60 |
61 | def do_results(self):
62 | self.do()
63 | return self.results
64 |
65 |
66 | class MovementXY(Action):
67 | def __init__(self, subject):
68 | super(MovementXY, self).__init__(subject)
69 |
70 | self._target_x = None
71 | self._target_y = None
72 |
73 | self.path = []
74 |
75 | def get_objective(self):
76 | out = {"target_x": self._target_x, "target_y": self._target_y}
77 |
78 | return out
79 |
80 | def action_possible(self):
81 |
82 | if self._target_x is None or self._target_y is None:
83 | return False
84 |
85 | if self.path is None or len(self.path) == 0:
86 | self.initialize_path()
87 |
88 | if self.path is None or len(self.path) == 0:
89 | return False
90 |
91 | return True
92 |
93 | def do(self):
94 |
95 | if self.results["done"]:
96 | return
97 |
98 | if not self.action_possible():
99 | return
100 |
101 | if not self.path or not self.check_path_passable():
102 | self.initialize_path()
103 |
104 | if not self.path:
105 | self.check_set_results()
106 | self._done = True
107 | return
108 |
109 | current_step_x, current_step_y = self.path.pop(0)
110 |
111 | if self.subject.board.cell_passable(current_step_x, current_step_y):
112 | self.subject.board.remove_object(self.subject, self.subject.x, self.subject.y)
113 | self.subject.board.insert_object(current_step_x, current_step_y, self.subject, epoch_shift=1)
114 |
115 | self.check_set_results()
116 |
117 | self._done = self.results["accomplished"]
118 |
119 | def check_set_results(self):
120 | self.accomplished = (self.subject.x == self._target_x and self.subject.y == self._target_y)
121 |
122 | def initialize_path(self):
123 |
124 | self.path = self.subject.board.make_path(self.subject.x, self.subject.y, self._target_x, self._target_y)
125 |
126 | def check_path_passable(self):
127 |
128 | next_step = self.path[0]
129 |
130 | return self.subject.board.cell_passable(next_step[0], next_step[1])
131 |
132 |
133 | class MovementToEntity(MovementXY):
134 | def __init__(self, subject):
135 | super(MovementToEntity, self).__init__(subject)
136 |
137 | self._target_entity = None
138 |
139 | def get_objective(self):
140 | out = {"target_entity": self._target_entity}
141 |
142 | return out
143 |
144 | def action_possible(self):
145 |
146 | if self._target_entity is None:
147 | return False
148 |
149 | self.set_target_coordinates()
150 |
151 | if self._target_x is None or self._target_y is None:
152 | return False
153 |
154 | if self.path is None or len(self.path) == 0:
155 | self.initialize_path()
156 |
157 | if self.path is None or len(self.path) == 0:
158 | return False
159 |
160 | return True
161 |
162 | def do(self):
163 |
164 | if self.results["done"]:
165 | return
166 |
167 | if not self.action_possible():
168 | return
169 |
170 | self.set_target_coordinates()
171 |
172 | self.initialize_path()
173 |
174 | if not self.action_possible():
175 | return
176 |
177 | super(MovementToEntity, self).do()
178 |
179 | self.check_set_results()
180 |
181 | self._done = self.results["accomplished"]
182 |
183 | def check_set_results(self):
184 | if self._target_entity.passable:
185 | self.accomplished = (self.subject.x == self._target_x and self.subject.y == self._target_y)
186 | else:
187 | distance = abs(self.subject.x - self._target_entity.x) + abs(self.subject.y - self._target_entity.y)
188 | self.accomplished = distance < 2
189 |
190 | def set_target_coordinates(self):
191 | if self._target_entity.passable:
192 | self._target_x = self._target_entity.x
193 | self._target_y = self._target_entity.y
194 | else:
195 | cells_near = []
196 | if self.subject.board.cell_passable(self._target_entity.x, self._target_entity.y + 1):
197 | cells_near.append((self._target_entity.x, self._target_entity.y + 1))
198 | if self.subject.board.cell_passable(self._target_entity.x, self._target_entity.y - 1):
199 | cells_near.append((self._target_entity.x, self._target_entity.y - 1))
200 | if self.subject.board.cell_passable(self._target_entity.x + 1, self._target_entity.y):
201 | cells_near.append((self._target_entity.x + 1, self._target_entity.y))
202 | if self.subject.board.cell_passable(self._target_entity.x - 1, self._target_entity.y):
203 | cells_near.append((self._target_entity.x - 1, self._target_entity.y))
204 | if len(cells_near) == 0:
205 | return
206 |
207 | best_coordinates = random.choice(cells_near)
208 | smallest_distance = 9e10
209 |
210 | for coordinates in cells_near:
211 | distance = math.sqrt((self.subject.x - coordinates[0]) ** 2 + (self.subject.y - coordinates[1]) ** 2)
212 |
213 | if distance < smallest_distance:
214 | smallest_distance = distance
215 | best_coordinates = coordinates
216 |
217 | self._target_x, self._target_y = best_coordinates
218 |
219 |
220 | class SearchSubstance(Action):
221 | def __init__(self, subject):
222 | super(SearchSubstance, self).__init__(subject)
223 |
224 | self.instant = True
225 |
226 | self._target_substance_type = None
227 |
228 | self._substance_x = None
229 | self._substance_y = None
230 |
231 | def get_objective(self):
232 | out = {"target_substance_type": self._target_substance_type}
233 |
234 | return out
235 |
236 | def action_possible(self):
237 | if self._target_substance_type is None:
238 | return False
239 |
240 | return True
241 |
242 | def do(self):
243 | if self.results["done"]:
244 | return
245 |
246 | if not self.action_possible():
247 | return
248 |
249 | self.search()
250 |
251 | self.check_set_results()
252 | self._done = True
253 |
254 | def check_set_results(self):
255 | self.accomplished = (self._substance_x is not None and self._substance_y is not None)
256 |
257 | @property
258 | def results(self):
259 | out = super(SearchSubstance, self).results
260 |
261 | out["substance_x"] = self._substance_x
262 | out["substance_y"] = self._substance_y
263 |
264 | return out
265 |
266 | def search(self):
267 | current_wave = [(self.subject.x, self.subject.y)]
268 | checked = {self.subject.x, self.subject.y}
269 |
270 | while current_wave:
271 | next_wave = []
272 |
273 | for wave_coordinates in current_wave:
274 | x, y = wave_coordinates
275 | coordinates_to_check = [(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)]
276 |
277 | for coordinates in coordinates_to_check:
278 | if self.subject.board.coordinates_valid(coordinates[0], coordinates[1]) \
279 | and self.subject.board.cell_passable(coordinates[0], coordinates[1]) \
280 | and (coordinates[0], coordinates[1]) not in checked:
281 |
282 | cell = self.subject.board.get_cell(coordinates[0], coordinates[1])
283 |
284 | for element in cell:
285 | if element.contains(self._target_substance_type):
286 | self._substance_x, self._substance_y = coordinates
287 | return
288 |
289 | next_wave.append(coordinates)
290 | checked.add(coordinates)
291 |
292 | current_wave = next_wave[:]
293 |
294 |
295 | class SearchMatingPartner(Action):
296 | def __init__(self, subject):
297 | super(SearchMatingPartner, self).__init__(subject)
298 |
299 | self.instant = True
300 |
301 | self._partner = None
302 |
303 | def do(self):
304 | if self.results["done"]:
305 | return
306 |
307 | if not self.action_possible():
308 | return
309 |
310 | self.search()
311 |
312 | self.check_set_results()
313 | self._done = True
314 |
315 | def check_set_results(self):
316 | self.accomplished = self._partner is not None
317 |
318 | @property
319 | def results(self):
320 | out = super(SearchMatingPartner, self).results
321 |
322 | out["partner"] = self._partner
323 |
324 | return out
325 |
326 | def search(self):
327 | current_wave = [(self.subject.x, self.subject.y)]
328 | checked = {self.subject.x, self.subject.y}
329 |
330 | height = self.subject.board.height
331 | length = self.subject.board.length
332 |
333 | while current_wave:
334 | next_wave = []
335 |
336 | for wave_coordinates in current_wave:
337 | x, y = wave_coordinates
338 | coordinates_to_check = [(x + 1, y),
339 | (x - 1, y),
340 | (x, y + 1),
341 | (x, y - 1)]
342 |
343 | for coordinates in coordinates_to_check:
344 | if 0 <= coordinates[0] <= length - 1 \
345 | and 0 <= coordinates[1] <= height - 1 \
346 | and (coordinates[0], coordinates[1]) not in checked:
347 |
348 | cell = self.subject.board.get_cell(coordinates[0], coordinates[1])
349 |
350 | for element in cell:
351 | if self.subject.can_mate(element):
352 | self._partner = element
353 | return
354 |
355 | checked.add(coordinates)
356 | if self.subject.board.cell_passable(coordinates[0], coordinates[1]):
357 | next_wave.append(coordinates)
358 |
359 | current_wave = next_wave[:]
360 |
361 |
362 | class ExtractSubstanceXY(Action):
363 | def __init__(self, subject):
364 | super(ExtractSubstanceXY, self).__init__(subject)
365 |
366 | self.instant = True
367 |
368 | self._substance_x = None
369 | self._substance_y = None
370 |
371 | self._substance_type = None
372 |
373 | def get_objective(self):
374 | out = {"substance_type": self._substance_type, "substance_x": self._substance_x,
375 | "substance_y": self._substance_y}
376 |
377 | return out
378 |
379 | def action_possible(self):
380 | if self._substance_x is None or self._substance_y is None or self._substance_type is None:
381 | return False
382 |
383 | cell_contains_substance = False
384 | cell = self.subject.board.get_cell(self._substance_x, self._substance_y)
385 | for element in cell:
386 | if element.contains(self._substance_type):
387 | cell_contains_substance = True
388 | break
389 |
390 | if not cell_contains_substance:
391 | return False
392 |
393 | x_distance = abs(self._substance_x - self.subject.x)
394 | y_distance = abs(self._substance_y - self.subject.y)
395 |
396 | if x_distance + y_distance > 1:
397 | return False
398 |
399 | return True
400 |
401 | def do(self):
402 | if self.results["done"]:
403 | return
404 |
405 | if not self.action_possible():
406 | return
407 |
408 | cell = self.subject.board.get_cell(self._substance_x, self._substance_y)
409 |
410 | extracted = False
411 |
412 | for element in cell:
413 | if element.contains(self._substance_type):
414 | self.subject.pocket(element.extract(self._substance_type))
415 | extracted = True
416 | break
417 |
418 | if extracted:
419 | self.check_set_results()
420 |
421 | self._done = True
422 |
423 | def check_set_results(self):
424 | self.accomplished = True
425 |
426 |
427 | class Mate(Action):
428 | def __init__(self, subject):
429 | super(Mate, self).__init__(subject)
430 |
431 | self.instant = True
432 |
433 | self._target_entity = None
434 |
435 | def get_objective(self):
436 | out = {"target_entity": self._target_entity}
437 |
438 | return out
439 |
440 | def action_possible(self):
441 | if self._target_entity is None:
442 | return False
443 |
444 | if not self.subject.will_mate(self._target_entity) or not self._target_entity.will_mate(self.subject):
445 | return False
446 |
447 | distance = abs(self.subject.x - self._target_entity.x) + abs(self.subject.y - self._target_entity.y)
448 | if distance > 1:
449 | return False
450 |
451 | return True
452 |
453 | def do(self):
454 | if self.results["done"]:
455 | return
456 |
457 | if not self.action_possible():
458 | return
459 |
460 | self._target_entity.add_state(states.Pregnant(self._target_entity))
461 | self.subject.add_state(states.NotTheRightMood(self.subject))
462 |
463 | self._done = True
464 |
465 | self.check_set_results()
466 |
467 | self._done = self.results["accomplished"]
468 |
469 | def check_set_results(self):
470 | self.accomplished = self._done
471 |
472 |
473 | class GiveBirth(Action):
474 | def __init__(self, subject, pregnant_state):
475 | super(GiveBirth, self).__init__(subject)
476 |
477 | self.pregnant_state = pregnant_state
478 |
479 | def action_possible(self):
480 | cells_around = self.get_empty_cells_around()
481 |
482 | if not cells_around:
483 | return False
484 |
485 | return True
486 |
487 | def do(self):
488 | if self.results["done"]:
489 | return
490 |
491 | if not self.action_possible():
492 | return
493 |
494 | cells_around = self.get_empty_cells_around()
495 |
496 | place = random.choice(cells_around)
497 |
498 | offspring = entities.Creature()
499 |
500 | self.subject.board.insert_object(place[0], place[1], offspring, epoch_shift=1)
501 |
502 | self.subject.remove_state(self.pregnant_state)
503 |
504 | self._done = True
505 |
506 | self.check_set_results()
507 |
508 | def get_empty_cells_around(self):
509 | cells_near = []
510 |
511 | if self.subject.board.cell_passable(self.subject.x, self.subject.y + 1):
512 | cells_near.append((self.subject.x, self.subject.y + 1))
513 | if self.subject.board.cell_passable(self.subject.x, self.subject.y - 1):
514 | cells_near.append((self.subject.x, self.subject.y - 1))
515 | if self.subject.board.cell_passable(self.subject.x + 1, self.subject.y):
516 | cells_near.append((self.subject.x + 1, self.subject.y))
517 | if self.subject.board.cell_passable(self.subject.x - 1, self.subject.y):
518 | cells_near.append((self.subject.x - 1, self.subject.y))
519 |
520 | return cells_near
521 |
522 |
523 | class HarvestSubstance(Action):
524 | def __init__(self, subject):
525 | super(HarvestSubstance, self).__init__(subject)
526 |
527 | self._target_substance_type = None
528 |
529 | self.search_action = SearchSubstance(subject)
530 | self.move_action = MovementXY(subject)
531 | self.extract_action = ExtractSubstanceXY(subject)
532 |
533 | self.current_action = None
534 |
535 | def get_objective(self):
536 | out = {"target_substance_type": self._target_substance_type}
537 |
538 | return out
539 |
540 | def set_objective(self, control=False, **kwargs):
541 | super(HarvestSubstance, self).set_objective(control, **kwargs)
542 |
543 | self.search_action.set_objective(**{"target_substance_type": self._target_substance_type})
544 |
545 | self.current_action = self.search_action
546 |
547 | def action_possible(self):
548 |
549 | if not self.current_action:
550 | return False
551 |
552 | return self.current_action.action_possible()
553 |
554 | def do(self):
555 | if self.results["done"]:
556 | return
557 |
558 | if not self.action_possible():
559 | return
560 |
561 | first = True
562 |
563 | while first or (self.current_action and self.current_action.instant):
564 | first = False
565 |
566 | current_results = self.current_action.do_results()
567 |
568 | if current_results["done"]:
569 | if current_results["accomplished"]:
570 | if isinstance(self.current_action, SearchSubstance):
571 | self.current_action = self.move_action
572 | self.current_action.set_objective(**{"target_x": current_results["substance_x"],
573 | "target_y": current_results["substance_y"]})
574 | elif isinstance(self.current_action, MovementXY):
575 | self.current_action = self.extract_action
576 | self.current_action.set_objective(**{"substance_x": self.subject.x,
577 | "substance_y": self.subject.y,
578 | "substance_type": self._target_substance_type})
579 | elif isinstance(self.current_action, ExtractSubstanceXY):
580 | self.current_action = None
581 | self._done = True
582 | else:
583 | self.current_action = None
584 | self._done = True
585 | else:
586 | break
587 |
588 | def check_set_results(self):
589 | self.accomplished = self._done
590 |
591 |
592 | class GoMating(Action):
593 | def __init__(self, subject):
594 | super(GoMating, self).__init__(subject)
595 |
596 | self.search_action = SearchMatingPartner(subject)
597 | self.move_action = MovementToEntity(subject)
598 | self.mate_action = Mate(subject)
599 |
600 | self.current_action = self.search_action
601 |
602 | def action_possible(self):
603 |
604 | if not self.current_action:
605 | return False
606 |
607 | return self.current_action.action_possible()
608 |
609 | def do(self):
610 | if self.subject.has_state(states.NotTheRightMood):
611 | self._done = True
612 | return
613 |
614 | if self.results["done"]:
615 | return
616 |
617 | if not self.action_possible():
618 | self._done = True
619 | return
620 |
621 | first = True
622 |
623 | while first or (self.current_action and self.current_action.instant) and not self.results["done"]:
624 |
625 | first = False
626 |
627 | current_results = self.current_action.do_results()
628 |
629 | if current_results["done"]:
630 | if current_results["accomplished"]:
631 | if isinstance(self.current_action, SearchMatingPartner):
632 | if current_results["accomplished"]:
633 | self.current_action = self.move_action
634 | self.current_action.set_objective(**{"target_entity": current_results["partner"]})
635 | elif isinstance(self.current_action, MovementXY):
636 | self.current_action = self.mate_action
637 | self.current_action.set_objective(**{"target_entity": self.search_action.results["partner"]})
638 | elif isinstance(self.current_action, Mate):
639 | self.current_action = None
640 | self.accomplished = True
641 | self._done = True
642 | else:
643 | self.current_action = None
644 | self._done = True
645 | else:
646 | break
647 |
648 | def check_set_results(self):
649 | self.accomplished = self._done
650 |
--------------------------------------------------------------------------------