world.py 18.7 KB
Newer Older
Ahmad Reza's avatar
Ahmad Reza committed
1
"""The world module provides the interface of the simulation world. In the simulation world
2
all the data of the particles, tiles, and locations are stored.
3
It also have the the coordination system and stated the maximum of the x and y coordinate.
Ahmad Reza's avatar
Ahmad Reza committed
4

5
 .. todo:: What happens if the maximum y or x axis is passed? Either the start from the other side or turns back.
Ahmad Reza's avatar
Ahmad Reza committed
6
"""
7
8
import importlib
import logging
Karol Actun's avatar
Karol Actun committed
9
10
11
import random
import threading

12
from lib import csv_generator, particle, tile, location, vis3d
13
14


Ahmad Reza's avatar
Ahmad Reza committed
15
class World:
16
    def __init__(self, config_data):
Ahmad Reza's avatar
Ahmad Reza committed
17
        """
Ahmad Reza's avatar
Ahmad Reza committed
18
        Initializing the world constructor
Karol Actun's avatar
Karol Actun committed
19
        :param config_data: configuration data from config.ini file
Ahmad Reza's avatar
Ahmad Reza committed
20
21
22
        """
        self.__round_counter = 1
        self.__end = False
Ahmad Reza's avatar
Ahmad Reza committed
23

Karol Actun's avatar
Karol Actun committed
24
        self.init_particles = []
25
        self.particle_id_counter = 0
26
        self.particles = []
27
        self.particle_map_coordinates = {}
28
        self.particle_map_id = {}
Karol Actun's avatar
Karol Actun committed
29
30
        self.particles_created = []
        self.particle_rm = []
Karol Actun's avatar
Karol Actun committed
31
32
        self.__particle_deleted = False
        self.new_particle = None
Ahmad Reza's avatar
Ahmad Reza committed
33

34
        self.tiles = []
35
        self.tile_map_coordinates = {}
36
        self.tile_map_id = {}
Karol Actun's avatar
Karol Actun committed
37
38
39
        self.tiles_created = []
        self.tiles_rm = []

Karol Actun's avatar
Karol Actun committed
40
        self.__tile_deleted = False
Ahmad Reza's avatar
Ahmad Reza committed
41
        self.new_tile = None
Karol Actun's avatar
Karol Actun committed
42
        self.__tile_deleted = False
Ahmad Reza's avatar
Ahmad Reza committed
43

44
45
46
47
48
49
50
        self.locations = []
        self.location_map_coordinates = {}
        self.location_map_id = {}
        self.locations_created = []
        self.locations_rm = []
        self.__location_deleted = False
        self.new_location = None
Ahmad Reza's avatar
Ahmad Reza committed
51

52
        self.config_data = config_data
Karol Actun's avatar
Karol Actun committed
53
        self.grid = config_data.grid
54

Ahmad Reza's avatar
Ahmad Reza committed
55
56
57
        self.csv_round = csv_generator.CsvRoundData(scenario=config_data.scenario,
                                                    solution=config_data.solution,
                                                    seed=config_data.seed_value,
58
                                                    directory=config_data.direction_name)
Ahmad Reza's avatar
Ahmad Reza committed
59

Karol Actun's avatar
Karol Actun committed
60
61
62
        if config_data.visualization:
            self.vis = vis3d.Visualization(self)

Ahmad Reza's avatar
Ahmad Reza committed
63
        mod = importlib.import_module('scenario.' + self.config_data.scenario)
Karol Actun's avatar
Karol Actun committed
64
65
66
67
68
69
70

        if config_data.visualization:
            import threading
            x = threading.Thread(target=mod.scenario, args=(self,))
            self.vis.wait_for_thread(x, "loading scenario... please wait.", "Loading Scenario")
        else:
            mod.scenario(self)
Ahmad Reza's avatar
Ahmad Reza committed
71

Ahmad Reza's avatar
Ahmad Reza committed
72
        if self.config_data.particle_random_order:
73
74
            random.shuffle(self.particles)

Karol Actun's avatar
Karol Actun committed
75
76
    def reset(self):
        """
77
        resets everything (particles, tiles, locations) except for the logging in system.log and in the csv file...
Karol Actun's avatar
Karol Actun committed
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
        reloads the scenario.
        :return:
        """
        self.__round_counter = 1
        self.__end = False

        self.init_particles = []
        self.particle_id_counter = 0
        self.particles = []
        self.particles_created = []
        self.particle_rm = []
        self.particle_map_coordinates = {}
        self.particle_map_id = {}
        self.__particle_deleted = False
        self.new_particle = None

        self.tiles = []
        self.tiles_created = []
        self.tiles_rm = []
        self.tile_map_coordinates = {}
        self.tile_map_id = {}
        self.__tile_deleted = False
        self.new_tile = None

102
103
104
105
106
107
108
        self.locations = []
        self.locations_created = []
        self.location_map_coordinates = {}
        self.location_map_id = {}
        self.locations_rm = []
        self.__location_deleted = False
        self.new_location = None
Karol Actun's avatar
Karol Actun committed
109
110
111
112
113
114
115
116
117
118

        if self.config_data.visualization:
            self.vis.reset()

        mod = importlib.import_module('scenario.' + self.config_data.scenario)

        if self.config_data.visualization:
            # if visualization is on, run the scenario in a separate thread and show that the program runs..
            x = threading.Thread(target=mod.scenario, args=(self,))
            self.vis.wait_for_thread(x, "loading scenario... please wait.", "Loading Scenario")
Karol Actun's avatar
Karol Actun committed
119
120
            self.vis.update_visualization_data()

Karol Actun's avatar
Karol Actun committed
121
122
123
124
125
126
        else:
            # if no vis, just run the scenario on the main thread
            mod.scenario(self)

        if self.config_data.particle_random_order:
            random.shuffle(self.particles)
127

Ahmad Reza's avatar
Ahmad Reza committed
128
129
    def csv_aggregator(self):
        self.csv_round.aggregate_metrics()
130
        particle_csv = csv_generator.CsvParticleFile(self.config_data.direction_name)
Karol Actun's avatar
Karol Actun committed
131
132
        for p in self.particles:
            particle_csv.write_particle(p)
133
        particle_csv.csv_file.close()
Ahmad Reza's avatar
Ahmad Reza committed
134

135
    def set_successful_end(self):
Ahmad Reza's avatar
Ahmad Reza committed
136
        self.csv_round.success()
Karol Actun's avatar
Karol Actun committed
137
        # self.set_end()
Ahmad Reza Cheraghi's avatar
Ahmad Reza Cheraghi committed
138
139
140
141
142
143
144
145
        
    def get_max_round(self):
        """
        The max round number
    
        :return: maximum round number
        """
        return self.config_data.max_round
Ahmad Reza's avatar
Ahmad Reza committed
146
147
148
149
150
151
152
153
154

    def get_actual_round(self):
        """
        The actual round number

        :return: actual round number
        """
        return self.__round_counter

155
    def set_unsuccessful_end(self):
Ahmad Reza's avatar
Ahmad Reza committed
156
157
158
        """
        Allows to terminate before the max round is reached
        """
Karol Actun's avatar
Karol Actun committed
159
        self.__end = True
Ahmad Reza's avatar
Ahmad Reza committed
160
161
162
163
164
165
166

    def get_end(self):
        """
            Returns the end parameter values either True or False
        """
        return self.__end

167
    def inc_round_counter_by(self, number=1):
Ahmad Reza's avatar
Ahmad Reza committed
168
169
170
171
172
        """
        Increases the the round counter by

        :return:
        """
Karol Actun's avatar
Karol Actun committed
173
        self.__round_counter += number
Ahmad Reza's avatar
Ahmad Reza committed
174
175
176
177
178
179
180

    def get_solution(self):
        """
        actual solution name

        :return: actual solution name
        """
Karol Actun's avatar
Karol Actun committed
181
        return self.config_data.solution
Ahmad Reza's avatar
Ahmad Reza committed
182

183
    def get_amount_of_particles(self):
184
        """
Ahmad Reza's avatar
Ahmad Reza committed
185
        Returns the actual number of particles in the world
186
187
188

        :return: The actual number of Particles
        """
189
        return len(self.particles)
190
191
192

    def get_particle_list(self):
        """
Ahmad Reza's avatar
Ahmad Reza committed
193
        Returns the actual number of particles in the world
194
195
196
197
198

        :return: The actual number of Particles
        """
        return self.particles

199
    def get_particle_map_coordinates(self):
200
201
202
203
204
        """
        Get a dictionary with all particles mapped with their actual coordinates

        :return: a dictionary with particles and their coordinates
        """
205
        return self.particle_map_coordinates
206
207
208
209
210
211
212
213
214

    def get_particle_map_id(self):
        """
        Get a dictionary with all particles mapped with their own ids

        :return: a dictionary with particles and their own ids
        """
        return self.particle_map_id

215
    def get_amount_of_tiles(self):
216
        """
Ahmad Reza's avatar
Ahmad Reza committed
217
        Returns the actual number of particles in the world
218
219
220

        :return: The actual number of Particles
        """
221
        return len(self.tiles)
222
223
224

    def get_tiles_list(self):
        """
Ahmad Reza's avatar
Ahmad Reza committed
225
        Returns the actual number of tiles in the world
226

Ahmad Reza's avatar
Ahmad Reza committed
227
        :return: a list of all the tiles in the world
228
229
230
        """
        return self.tiles

231
    def get_tile_map_coordinates(self):
232
233
234
235
236
        """
        Get a dictionary with all tiles mapped with their actual coordinates

        :return: a dictionary with particles and their coordinates
        """
237
        return self.tile_map_coordinates
238
239
240
241
242
243
244
245
246

    def get_tile_map_id(self):
        """
        Get a dictionary with all particles mapped with their own ids

        :return: a dictionary with particles and their own ids
        """
        return self.tile_map_id

247
    def get_amount_of_locations(self):
248
        """
249
        Returns the actual number of locations in the world
250

251
        :return: The actual number of locations
252
        """
253
        return len(self.locations)
254

255
    def get_location_list(self):
256
        """
257
        Returns the actual number of locations in the world
258

259
        :return: The actual number of locations
260
        """
261
        return self.locations
262

263
    def get_location_map_coordinates(self):
264
        """
265
        Get a dictionary with all locations mapped with their actual coordinates
266

267
        :return: a dictionary with locations and their coordinates
268
        """
269
        return self.location_map_coordinates
270

271
    def get_location_map_id(self):
272
        """
273
        Get a dictionary with all locations mapped with their own ids
274

275
        :return: a dictionary with locations and their own ids
276
        """
277
        return self.location_map_id
278

279
    def get_world_x_size(self):
280
281
        """

Ahmad Reza's avatar
Ahmad Reza committed
282
        :return: Returns the maximal x size of the world
283
        """
Ahmad Reza's avatar
Ahmad Reza committed
284
        return self.config_data.size_x
285

286
    def get_world_y_size(self):
287
        """
Ahmad Reza's avatar
Ahmad Reza committed
288
        :return: Returns the maximal y size of the world
289
        """
Ahmad Reza's avatar
Ahmad Reza committed
290
        return self.config_data.size_y
291

292
293
294
295
    def get_world_size(self):
        """
        :return: Returns the maximal (x,y) size of the world as a tupel
        """
Karol Actun's avatar
Karol Actun committed
296
        return self.config_data.size_x, self.config_data.size_y
297

298
299
300
301
302
303
    def get_tile_deleted(self):
        return self.__tile_deleted

    def get_particle_deleted(self):
        return self.__particle_deleted

304
305
    def get_location_deleted(self):
        return self.__location_deleted
306
307
308
309
310

    def set_tile_deleted(self):
        self.__tile_deleted = False

    def set_particle_deleted(self):
Karol Actun's avatar
Karol Actun committed
311
        self.__particle_deleted = False
312

313
314
    def set_location_deleted(self):
        self.__location_deleted = False
315

Karol Actun's avatar
Karol Actun committed
316
    def add_particle(self, coordinates, color=None):
317
        """
Ahmad Reza's avatar
Ahmad Reza committed
318
        Add a particle to the world database
319

Karol Actun's avatar
Karol Actun committed
320
321
322
        :param coordinates: The x coordinate of the particle
        :param color: The color of the particle
        :return: Added Matter; False: Unsuccessful
323
        """
Karol Actun's avatar
Karol Actun committed
324
325
326
327

        if len(coordinates) == 2:
            coordinates = (coordinates[0], coordinates[1], 0.0)

Ahmad Reza's avatar
Ahmad Reza committed
328
        if len(self.particles) < self.config_data.max_particles:
329
            if self.grid.are_valid_coordinates(coordinates):
Karol Actun's avatar
Karol Actun committed
330
                if coordinates not in self.get_particle_map_coordinates():
Karol Actun's avatar
Karol Actun committed
331
332
                    if color is None:
                        color = self.config_data.particle_color
333
                    self.particle_id_counter += 1
Karol Actun's avatar
Karol Actun committed
334
335
336
337
338
339
340
                    self.new_particle = particle.Particle(self, coordinates, color, self.particle_id_counter)
                    if self.vis is not None:
                        self.vis.particle_changed(self.new_particle)
                    self.particles_created.append(self.new_particle)
                    self.particle_map_coordinates[self.new_particle.coordinates] = self.new_particle
                    self.particle_map_id[self.new_particle.get_id()] = self.new_particle
                    self.particles.append(self.new_particle)
Ahmad Reza's avatar
Ahmad Reza committed
341
                    self.csv_round.update_particle_num(len(self.particles))
Karol Actun's avatar
Karol Actun committed
342
343
344
345
                    self.init_particles.append(self.new_particle)
                    self.new_particle.created = True
                    logging.info("Created particle at %s", self.new_particle.coordinates)
                    return self.new_particle
Karol Actun's avatar
Karol Actun committed
346

347
                else:
Karol Actun's avatar
Karol Actun committed
348
                    logging.info("there is already a particle on %s" % str(coordinates))
349
350
                    return False
            else:
Karol Actun's avatar
Karol Actun committed
351
                logging.info("%s is not a valid location!" % str(coordinates))
Karol Actun's avatar
Karol Actun committed
352
                return False
353
354
355
356
        else:
            logging.info("Max of particles reached and no more particles can be created")
            return False

Karol Actun's avatar
Karol Actun committed
357
    def remove_particle(self, particle_id):
Ahmad Reza's avatar
Ahmad Reza committed
358
        """ Removes a particle with a given particle id from the world database
359
360
361
362
363


        :param particle_id: particle id
        :return: True: Successful removed; False: Unsuccessful
        """
Karol Actun's avatar
Karol Actun committed
364
        rm_particle = self.particle_map_id[particle_id]
365
366
        if rm_particle:
            self.particles.remove(rm_particle)
Karol Actun's avatar
Karol Actun committed
367
368
            del self.particle_map_coordinates[rm_particle.coordinates]
            del self.particle_map_id[particle_id]
369
            self.particle_rm.append(rm_particle)
Karol Actun's avatar
Karol Actun committed
370
371
            if self.vis is not None:
                self.vis.remove_particle(rm_particle)
Ahmad Reza's avatar
Ahmad Reza committed
372
373
            self.csv_round.update_particle_num(len(self.particles))
            self.csv_round.update_metrics(particle_deleted=1)
374
375
376
377
378
            self.__particle_deleted = True
            return True
        else:
            return False

379
    def remove_particle_on(self, coordinates):
380
        """
Karol Actun's avatar
Karol Actun committed
381
        Removes a particle on a give coordinate from to the world database
382

Karol Actun's avatar
Karol Actun committed
383
        :param coordinates: A tuple that includes the x and y coordinates
384
385
        :return: True: Successful removed; False: Unsuccessful
        """
386
        if coordinates in self.particle_map_coordinates:
387
            return self.remove_particle(self.particle_map_coordinates[coordinates].get_id())
388
389
390
        else:
            return False

Karol Actun's avatar
Karol Actun committed
391
    def add_tile(self, coordinates, color=None):
392
        """
Ahmad Reza's avatar
Ahmad Reza committed
393
        Adds a tile to the world database
Karol Actun's avatar
Karol Actun committed
394
395
396
        :param color: color of the tile (None for config default)
        :param coordinates: the coordinates on which the tile should be added
        :return: Successful added matter; False: Unsuccessful
397
        """
Karol Actun's avatar
Karol Actun committed
398
399
400
401

        if len(coordinates) == 2:
            coordinates = (coordinates[0], coordinates[1], 0.0)

402
        if self.grid.are_valid_coordinates(coordinates):
Karol Actun's avatar
Karol Actun committed
403
            if coordinates not in self.tile_map_coordinates:
Karol Actun's avatar
Karol Actun committed
404
405
406
                if color is None:
                    color = self.config_data.tile_color
                self.new_tile = tile.Tile(self, coordinates, color)
407
                self.tiles.append(self.new_tile)
Karol Actun's avatar
Karol Actun committed
408
409
                if self.vis is not None:
                    self.vis.tile_changed(self.new_tile)
Ahmad Reza's avatar
Ahmad Reza committed
410
                self.csv_round.update_tiles_num(len(self.tiles))
411
                self.tile_map_coordinates[self.new_tile.coordinates] = self.new_tile
412
                self.tile_map_id[self.new_tile.get_id()] = self.new_tile
Karol Actun's avatar
Karol Actun committed
413
414
                logging.info("Created tile with tile id %s on coordinates %s",
                             str(self.new_tile.get_id()), str(coordinates))
415
                return self.new_tile
416
417

            else:
Karol Actun's avatar
Karol Actun committed
418
                logging.info("there is already a tile on %s " % str(coordinates))
419
420
                return False
        else:
Karol Actun's avatar
Karol Actun committed
421
422
            logging.info("%s is not a valid location!" % str(coordinates))
            return False
423

Karol Actun's avatar
Karol Actun committed
424
    def remove_tile(self, tile_id):
425
        """
Ahmad Reza's avatar
Ahmad Reza committed
426
        Removes a tile with a given tile_id from to the world database
427

Karol Actun's avatar
Karol Actun committed
428
        :param tile_id: The tiles id that should be removed
429
430
        :return:  True: Successful removed; False: Unsuccessful
        """
Karol Actun's avatar
Karol Actun committed
431
432
        if tile_id in self.tile_map_id:
            rm_tile = self.tile_map_id[tile_id]
433
434
            self.tiles.remove(rm_tile)
            self.tiles_rm.append(rm_tile)
Karol Actun's avatar
Karol Actun committed
435
436
437
            if self.vis is not None:
                self.vis.remove_tile(rm_tile)
            logging.info("Deleted tile with tile id %s on %s", str(rm_tile.get_id()), str(rm_tile.coordinates))
438
439
440
441
442
            try:  # cher: added so the program does not crashed if it does not find any entries in the map
                del self.tile_map_id[rm_tile.get_id()]
            except KeyError:
                pass
            try:  # cher: added so the program does not crashed if it does not find any entries in the map
443
                del self.tile_map_coordinates[rm_tile.coordinates]
444
445
            except KeyError:
                pass
Ahmad Reza's avatar
Ahmad Reza committed
446
447
            self.csv_round.update_tiles_num(len(self.tiles))
            self.csv_round.update_metrics(tile_deleted=1)
448
449
450
451
452
            self.__tile_deleted = True
            return True
        else:
            return False

453
    def remove_tile_on(self, coordinates):
454
        """
Karol Actun's avatar
Karol Actun committed
455
        Removes a tile on a give coordinates from to the world database
456

Karol Actun's avatar
Karol Actun committed
457
        :param coordinates: A tuple that includes the x and y coordinates
458
459
        :return: True: Successful removed; False: Unsuccessful
        """
460
        if coordinates in self.tile_map_coordinates:
Karol Actun's avatar
Karol Actun committed
461
462
            return self.remove_tile(self.tile_map_coordinates[coordinates].get_id())

463
464
465
        else:
            return False

466
    def add_location(self, coordinates, color=None):
467
        """
Ahmad Reza's avatar
Ahmad Reza committed
468
        Add a tile to the world database
469
470

        :param color:
Karol Actun's avatar
Karol Actun committed
471
472
        :param coordinates: the coordinates on which the tile should be added
        :return: True: Successful added; False: Unsuccessful
473
        """
Karol Actun's avatar
Karol Actun committed
474
475
476
477

        if len(coordinates) == 2:
            coordinates = (coordinates[0], coordinates[1], 0.0)

478
479
        if self.grid.are_valid_coordinates(coordinates):
            if coordinates not in self.location_map_coordinates:
Karol Actun's avatar
Karol Actun committed
480
                if color is None:
481
482
483
                    color = self.config_data.location_color
                self.new_location = location.Location(self, coordinates, color)
                self.locations.append(self.new_location)
Karol Actun's avatar
Karol Actun committed
484
                if self.vis is not None:
485
486
487
488
489
490
491
492
                    self.vis.location_changed(self.new_location)
                self.location_map_coordinates[self.new_location.coordinates] = self.new_location
                self.location_map_id[self.new_location.get_id()] = self.new_location
                self.csv_round.update_locations_num(len(self.locations))
                logging.info("Created location with id %s on coordinates %s",
                             str(self.new_location.get_id()), str(self.new_location.coordinates))
                self.new_location.created = True
                return self.new_location
493
            else:
494
                logging.info("there is already a location on %s" % str(coordinates))
495
496
                return False
        else:
Karol Actun's avatar
Karol Actun committed
497
            logging.info("%s is not a valid location!" % str(coordinates))
498
499
            return False

500
    def remove_location(self, location_id):
501
        """
Ahmad Reza's avatar
Ahmad Reza committed
502
        Removes a tile with a given tile_id from to the world database
503

504
        :param location_id: The locations id that should be removed
505
506
        :return:  True: Successful removed; False: Unsuccessful
        """
507
508
509
510
        if location_id in self.location_map_id:
            rm_location = self.location_map_id[location_id]
            if rm_location in self.locations:
                self.locations.remove(rm_location)
Karol Actun's avatar
Karol Actun committed
511
            if self.vis is not None:
512
513
514
                self.vis.remove_location(rm_location)
            self.locations_rm.append(rm_location)
            logging.info("Deleted location with location id %s on %s", str(location_id), str(rm_location.coordinates))
515
            try:
516
                del self.location_map_coordinates[rm_location.coordinates]
517
518
519
            except KeyError:
                pass
            try:
520
                del self.location_map_id[location_id]
521
522
            except KeyError:
                pass
523
524
525
            self.csv_round.update_locations_num(len(self.locations))
            self.csv_round.update_metrics(location_deleted=1)
            self.__location_deleted = True
526
527
528
529
            return True
        else:
            return False

530
    def remove_location_on(self, coordinates):
531
        """
532
        Removes a location on a give coordinates from to the world database
533

Karol Actun's avatar
Karol Actun committed
534
        :param coordinates: A tuple that includes the x and y coordinates
535
536
        :return: True: Successful removed; False: Unsuccessful
        """
537
538
        if coordinates in self.location_map_coordinates:
            return self.remove_location(self.location_map_coordinates[coordinates].get_id())
539
        else:
Karol Actun's avatar
Karol Actun committed
540
            return False