def test_elem_wise(self): v1 = Vector(5, 2) v2 = Vector(1, 2) self.assertEqual(v1 + v2, v2 + v1) self.assertEqual(v1 + v2, (6, 4)) self.assertEqual(v1.scale(*v2), (5, 4)) v1 += v2 self.assertEqual(v1, (6, 4))
def test_cmp(self): v1 = Vector(4.3, 5.2) v2 = Vector(6.1, 2.8) self.assertFalse(v1 == v2) self.assertTrue(v1 != v2) self.assertTrue(Vector(2.0, 1.0) == Vector(2, 1)) self.assertTrue(v1 > v2) self.assertTrue(v1 == (4.3, 5.2))
def test_quad_damage(self): survivor = Survivor(0, 0) bullet = Bullet(Vector(0, 0), Vector(3, 0), 0, survivor) bullet.pos += bullet.vel before_4x = bullet.calc_dmg() Drop.actives["4x"] = 1 after4x = bullet.calc_dmg() self.assertEqual(after4x / before_4x, 4.0) self.assertTrue(after4x > before_4x)
def __init__(self, x, y, width=None, height=None): if width is None and height is None: self.width, self.height = Tile.length, Tile.length else: self.width, self.height = width, height self._size = Vector(self.width, self.height) self.to = None self.pos = Vector(x, y)
def test_get_and_setitem(self): v = Vector(3, 5.3) self.assertEqual(v.x, v[0], v["x"]) self.assertEqual(v.y, v[1], v["y"]) v.x = 3.2 v.y = 5 self.assertEqual(v.x, v[0], v["x"]) self.assertEqual(v.y, v[1], v["y"]) v[0] = 0.9 self.assertEqual(v.x, v[0], v["x"]) v["x"] = 4.6 self.assertEqual(v.x, v[0], v["x"])
def reset(self): self.r = 0 # rotation in radians self.p = Vector() # position self.v = Vector() # velocity self.a = Vector() # acceleration self.reset_time() self.t.up() self._update_t() self.t.down() self.t.clear()
class BaseClass: """The Baseclass for the Survivor, Zombie, Bullet and PickUp classes Params: x: The x coordinate y: The y coordinate width: (Optional) Used by the Bullet Class as it has another size. Defaults to Tile.length height: (Optional) Used by the Bullet Class as it has another size. Defaults to Tile.length""" def __init__(self, x, y, width=None, height=None): if width is None and height is None: self.width, self.height = Tile.length, Tile.length else: self.width, self.height = width, height self._size = Vector(self.width, self.height) self.to = None self.pos = Vector(x, y) def get_centre(self): return self.pos + self._size.scale(1 / 2) centre = property(get_centre, doc="""Return a vector of the pos in the middle of self >>> a = BaseClass(x=0, y=0, width=10, height=10) >>> a.centre Vector(x=5, y=5)""") def get_number(self): """Return the index of tile that self is on""" return int(self.pos.x // Tile.length + self.pos.y // Tile.length * Options.tiles_x) def get_tile(self): """Return the tile on which self is""" return Tile.instances[self.get_number()]
def set_target(self, next_tile): self.to = next_tile.pos angle = angle_between(*self.pos, *next_tile.pos) assert angle % (math.pi / 2) == 0., "angle: %s to: %s pos: %s mod %s" % ( angle, self.to, self.pos, angle % math.pi / 2) self.vel = Vector(*self.angle_to_vel[angle])
def __init__(self, x, y): self.current_gun = 0 self.direction = pi * 3 / 1 / 2 self.img = Survivor.imgs[dir2chr[self.direction]] super().__init__(x, y) self.health = 100 << 10 * Options.debug # 100 if not debug, 10*2**10 if debug self.vel = Vector(0, 0) self.init_ammo_count = 100, 50, 150, 50 self.ammo_count = list(self.init_ammo_count)
def shooting(survivor, keys): if keys[pygame.K_LEFT]: survivor.rotate(pi) Bullet(survivor.centre, Vector(-Options.bullet_vel, 0), survivor.current_gun, survivor) elif keys[pygame.K_RIGHT]: survivor.rotate(0) Bullet(survivor.centre, Vector(Options.bullet_vel, 0), survivor.current_gun, survivor) elif keys[pygame.K_UP]: survivor.rotate(pi / 2) Bullet(survivor.centre, Vector(0, -Options.bullet_vel), survivor.current_gun, survivor) elif keys[pygame.K_DOWN]: survivor.rotate(pi * 3 / 2) Bullet(survivor.centre, Vector(0, Options.bullet_vel), survivor.current_gun, survivor)
def __init__(self, x, y, is_solid): self.parent = None self.h, self.g, self.f = 0, 0, 0 self.walkable = not is_solid self.pos = Vector(x, y) self.number = Tile.amnt_tiles Tile.amnt_tiles += 1 if is_solid: Tile.solids_list.append(self) else: Tile.opens.add(self) Tile.instances.append(self)
def walking(survivor, keys): if survivor.to is not None: # If the survivor is between two tiles return if keys[pygame.K_w]: # North future_tile_num = survivor.get_number() - Options.tiles_x if Tile.on_screen(0, future_tile_num): future_tile = Tile.instances[future_tile_num] if future_tile.walkable or "trans" in Drop.actives: survivor.set_target(future_tile) survivor.rotate(pi / 2) survivor.vel = Vector(0, -Options.speed) if keys[pygame.K_s]: # South future_tile_num = survivor.get_number() + Options.tiles_x if Tile.on_screen(1, future_tile_num): future_tile = Tile.instances[future_tile_num] if future_tile.walkable or "trans" in Drop.actives: survivor.set_target(future_tile) survivor.rotate(pi * 3 / 2) survivor.vel = Vector(0, Options.speed) if keys[pygame.K_d]: # East future_tile_num = survivor.get_number() + 1 if Tile.on_screen(2, future_tile_num): future_tile = Tile.instances[future_tile_num] if future_tile.walkable or "trans" in Drop.actives: survivor.set_target(future_tile) survivor.rotate(0) survivor.vel = Vector(Options.speed, 0) if keys[pygame.K_a]: # West future_tile_num = survivor.get_number() - 1 if Tile.on_screen(3, future_tile_num): future_tile = Tile.instances[future_tile_num] if future_tile.walkable or "trans" in Drop.actives: survivor.set_target(future_tile) survivor.rotate(pi) survivor.vel = Vector(-Options.speed, 0)
def __init__(self, x, y): self.direction = math.pi type_ = randint(0, 3) self.type = type_ self.org_img = Zombie.imgs[type_] self.img = self.org_img self.speed = Zombie.speed_tuple[type_] self.health_func = Zombie.health_func_tuple[type_] self.health = self.health_func(Zombie.base_health) self.org_health = self.health self.angle_to_vel = { 0: (self.speed, 0), math.pi / 2: (0, -self.speed), math.pi: (-self.speed, 0), math.pi * 3 / 2: (0, self.speed) } self.vel = Vector(0, 0) super().__init__(x, y) Zombie.instances.add(self) self.path = [] self.last_angle = 0. self.path_colour = Colours.random(s=(0.5, 1)) logging.debug("speed: %s type. %s", self.speed, self.type)
def test_funcs(self): v1 = Vector(5.3, 2.0) self.assertEqual(v1.as_ints(), Vector(5, 2)) v2 = v1.copy() v2.x = 4.7 self.assertNotEqual(v1.x, 4.7) v = Vector(6, 8) self.assertEqual(abs(v), v.magnitude(), 10.0) self.assertEqual(v.magnitude_squared(), v.magnitude()**2) self.assertEqual(abs(v), math.sqrt(v.magnitude_squared())) v.y = -5.3 self.assertEqual(v.signs(), (1, -1)) v.x = 0 self.assertEqual(v.signs(), (0, -1)) v1 = Vector(4.0, 2.0) v2 = Vector(3.0, 1.0) self.assertEqual((v1 - v2).manhattan_dist(), 2) v2.x = 2.0 self.assertEqual((v1 - v2).manhattan_dist(), 3) self.assertEqual((v1 - v1).manhattan_dist(), 0)
class Tile: """Create a tile Tile.create() creates all tiles that fit in the map Should not be created seperately only through Tile.create() Params: x: x coordinate of the tile y: y coordinate of the tile is_solid: True if the tile is not walkable else False idx: the index of the tile starting at 0 in the topleft like this: 012 345 678""" instances, solids, opens, solids_list, loop_set = [], set(), set(), [], set() length = Options.tile_length size = Vector(length, length) amnt_tiles = 0 # Incremented when a tile is created with open(Options.mappath) as file: file_str = file.read() map_ = [x == "#" for x in file_str.replace("\n", "")] solid_nums = {i for i, x in enumerate(map_) if x} # Set of the indices of all solid tiles @classmethod def create(cls): """Create tiles and set some class variables when finished""" error_msg = """len(cls.map_) is not equal to the amount of tiles vertically multiplied by the amount of tiles horizontally. This is usually caused by tile.py being initiated before init_screen.py or options.py. note that tile.py is initiated by e.g. miscellaneous.py It could also be caused by the map file having a blank line at the end, or some lines are longer than others.""" assert len(cls.map_) == Options.tiles_x * Options.tiles_y, dedent(error_msg) map_gen = iter(cls.map_) for y in range(0, Options.height, cls.length): for x in range(0, Options.width, cls.length): cls(x, y, next(map_gen)) cls.solids = set(cls.solids_list) cls.loop_set = cls.compress_solids() @classmethod def delete(cls): cls.instances = [] cls.solids_list = [] cls.solids = set() cls.opens = set() cls.loop_set = set() cls.amnt_tiles = 0 @classmethod def compress_solids(cls): """returns a comressed cls.solids to be used when drawing It returns a list of a tuple with the first tile in a long line of solids and then how many solids are after it in the line for all lines on the map. This makes the drawing much faster, example: ##..### ####.#. | v {(tile.Tile object at 0x0..., 2), (tile.Tile ..., 3), (tile.Tile ..., 4), (tile.Tile ..., 1)} It ignores open tiles It is not necessary to compress opens since they are drawn by filling the screen. NOTE: A new group starts at a newline TODO: Make it so the loop_list is the type of tile with the fewest intances rtype: set """ splitted_map = zip(*([iter(cls.map_)] * Options.tiles_x)) # Split map on every newline compressed_map = [] # A compressed list with True if solid and False if open # Looks something like this -> [(True, 5), (False, 2), (True, 9), ...] for row in splitted_map: for group in groupby(row): compressed_map.append((group[0], len(list(group[1])))) filtered_opens = (j for i, j in compressed_map if i) # filter open tiles and remove the "True" solids_iter = iter(cls.solids_list) # Map solids onto compressed map loop_set = set() for length, tile in zip(filtered_opens, solids_iter): loop_set.add((tile, length)) for _ in range(length - 1): next(solids_iter) return loop_set def __init__(self, x, y, is_solid): self.parent = None self.h, self.g, self.f = 0, 0, 0 self.walkable = not is_solid self.pos = Vector(x, y) self.number = Tile.amnt_tiles Tile.amnt_tiles += 1 if is_solid: Tile.solids_list.append(self) else: Tile.opens.add(self) Tile.instances.append(self) def __hash__(self): return self.number def __lt__(self, other): """Necesary for the heapq in astar for determeting what path to take if two tiles have equal cost. Current implementation favours the tile with the lowest number. In other words the tile furthest up, and then the one furthest to the left.""" return self.number < other.number def __eq__(self, other): return self is other def __str__(self): return "Tile: pos=%s, num=%s, walkable=%s" \ % (self.pos, self.number, self.walkable) @classmethod def random_open_tile(cls): """:return: location of a random walkable tile""" return random.choice(tuple(cls.opens)).pos def get_centre(self): """Return a vector of the pos in the centre of the tile >>> Tile.length = 20 >>> a = Tile(x=0, y=0, is_solid=0) >>> a.get_centre() Vector(x=10, y=10)""" return self.pos + Tile.length // 2 def closest_open_tile(self): return min(Tile.opens, key=lambda x: (self.pos - x.pos).magnitude_squared()) @classmethod def on_screen(cls, direction, tile_num): """Return False if the tile is outside of the screen else True A direction is needed as a tile on the right border is outside if the direction is west Params: direction: int of direction in the list NSEW. For example South has index 1. tile_num: index of tile in Tile.instances""" if direction == 2: # East return tile_num % Options.tiles_x != 0 if direction == 3: return tile_num % Options.tiles_x != Options.tiles_x - 1 # West return 0 < tile_num < cls.amnt_tiles # North and South @classmethod def draw_all(cls, screen): """Fill the screen in light tiles, then draws the solid tiles over""" screen.fill(Options.fillcolour) length, loopcolour, draw_rect = cls.length, Options.loopcolour, pygame.draw.rect for tile, i in cls.loop_set: draw_rect(screen, loopcolour, (*tile.pos, length * i, length))
def test_iter(self): v = Vector(2.0, 5.0) self.assertTrue(len(v) == 2) self.assertIsInstance(v, Container) self.assertIsInstance(v, Iterable) self.assertTrue(5.0 in v)
def test_creation(self): v = Vector(3, 5.3) self.assertTrue(isinstance(v.x, int)) self.assertTrue(isinstance(v.y, float)) v1 = Vector.from_pair([8.3, 1.3]) self.assertTrue(hasattr(v1, "y"))