├── 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 | --------------------------------------------------------------------------------