class Game: """ Термины: round - Круг. За один круг все персонажи делают по одному ходу. turn - Ход. За один ход, персонаж делает заданное количество действий. action - Действиею Переход на соседнюю клетку, атака. """ kb_mode: str = "controls" # keyboard mode, map/attack/backpack kb_locked: bool = False # нажата любая кнопка # счётчики turns_counter: int = 0 # счётчик ходов rounds_counter: int = 0 # счетчик кругов monster_waves_counter: int = 0 # счетчик волн монстров rounds_between_monster_wave: int = 5 # количество кругов между волнами монстров is_world_motion = False # происходит сдвиг игрового мира is_game_over = False world_shift = (0, 0) def __init__(self, screen: Surface, map_: str, heroes: int, monsters: int, items: int): """ Создаёт игру :param heroes: колличество героев в игре. Если players_count=0, то создаст читера. """ self.start_time = datetime.now() self.screen = screen # map self.map: Map = self._init_map(map_) # карта self.maps: Group = Group(self.map) # sprites self.characters: Group = Group() # спрайты с героями и монстрами self.heroes: Group = self._init_heroes(heroes) # спрайты с героями self.monsters: Group = self._init_monsters( monsters) # спрайты с монстрами self.items: Group = self._init_items(items) # спрайты с вещами self._start_turn() self.dashboard = Dashboard(self) # приборная панель self.backpack: Backpack = Backpack(self) # рюкзак self.controls: Controls = Controls(self) # help self.monsters_killed = 0 def _init_map(self, name: str = None) -> Map: """ Создаёт карту """ if name == "map": map_ = Map(name=name, ascii_=s.MAP_1, game=self) elif name == "SANDBOX": map_ = Map(name="SANDBOX", ascii_=s.MAP_SANDBOX, game=self) else: map_ = Map(name="SANDBOX_NO_WALLS", ascii_=s.MAP_SANDBOX_NO_WALLS, game=self) return map_ def _init_heroes(self, count: int) -> Group: """ Создаёт группу из героев, добавляет на карту, в список спрайтов если count=0, спросит количество героев """ if not count: count = 1 heroes = Group() attrs = [ dict(name="hero1", image="hero1.png", xy=(12, 8), game=self), dict(name="hero2", image="hero2.png", xy=(11, 8), game=self), dict(name="hero3", image="hero3.png", xy=(11, 9), game=self), dict(name="hero4", image="hero4.png", xy=(12, 9), game=self), ][:count] for attr in attrs: hero = Hero(**attr) # создадим героя heroes.add(hero) # добавим героя в спрайты героев self.characters.add(hero) # добавим героя в спрайты персонажей cell = self.map.get_cell(attr["xy"]) # добавим героя на карту cell.characters.append(hero) return heroes def _init_monsters(self, count: int) -> Group: """ Создаёт группу из монстров, добавляет на карту, в список спрайтов """ monsters = Group() attrs = [ dict(xy=(13, 7), game=self), dict(xy=(13, 7), game=self), dict(xy=(13, 7), game=self), dict(xy=(2, 4), game=self), ][:count] for attr in attrs: monster = Monster.little(**attr) # создадим монстра monsters.add(monster) # добавим монстра в спрайты монстрв self.characters.add( monster) # добавим монстра в спрайты персонажей cell = self.map.get_cell(attr["xy"]) # добавим монстра на карту cell.characters.append(monster) return monsters def _init_items(self, count: int) -> Group: """ Создаёт и помещает вещи на карту """ # сгенерим вещи в нужном количестве и добавим в спрйты и на карту items = Group() items_ = items_generator(count=count, game=self) for item in items_: items.add(item) # добавим вещи на карту cell = random.choice(self.map.cells) cell.items.append(item) item.xy = cell.xy item.update_rect() # Sprite.rect return items def intro_2(self) -> bool: """ заставка перед игрой, карта появляется из темноты return True пока полностью не появится """ # прозрачность time_delta = datetime.now() - self.start_time seconds = time_delta.seconds + time_delta.microseconds / 1000000 alpha_max = (256 + 100) alpha = int(alpha_max - seconds * 30) # прозрачность > 256 = black # рисуем карту self.screen.fill(s.BLACK) self.maps.draw(self.screen) # дропаем вещи на карту items = self.items.sprites() count = int(len(items) * (1 - alpha / alpha_max)) items = items[:count] items_group = Group() items_group.add(items) items_group.draw(self.screen) # карта появляется из темноты screen_rect = self.screen.get_rect() surface = pygame.Surface(screen_rect.bottomright) surface.set_alpha(alpha * 5) surface.fill(s.BROUN) self.screen.blit(surface, screen_rect.topleft) widht = min(self.screen.get_rect().size) size = (widht, widht) surface = pygame.image.load(s.I_ZASTAVKA).convert() surface = pygame.transform.scale(surface, size) rect = surface.get_rect() rect.center = self.screen.get_rect().center surface.set_alpha(alpha) self.screen.blit(surface, rect.topleft) # карта появилась if alpha <= 0: return False # карта ещё не появилась return True def _start_turn(self): """ перый персонаж в списке спрайтов начинает игру (становится активный) """ characters = self.characters.sprites() if characters: character = characters[0] character.start_turn() def _init_monsters_wave(self) -> None: """ Создаёт волну монстров. Добавляет монстров в группу спрайтов. """ # 1-ая волна монстров if self.monster_waves_counter == 1: monster1 = Monster.little(xy=(1, 1), game=self) self.monsters.add(monster1) self.characters.add(monster1) self.map.add_characters([monster1]) return # 2-ая волна монстров if self.monster_waves_counter == 2: monster2 = Monster.little(xy=(13, 1), game=self) monster3 = Monster.little(xy=(1, 9), game=self) self.monsters.add(monster2, monster3) self.characters.add(monster2, monster3) self.map.add_characters([monster2, monster3]) return # 3-ая волна монстров if self.monster_waves_counter == 3: monster4 = Monster.big(xy=(5, 4), game=self) self.monsters.add(monster4) self.characters.add(monster4) self.map.add_characters([monster4]) # 4-ая волна монстров if self.monster_waves_counter == 4: monster5 = Monster.big(xy=(10, 8), game=self) monster6 = Monster.little(xy=(3, 3), game=self) self.monsters.add(monster5, monster6) self.characters.add(monster5, monster6) self.map.add_characters([monster5, monster6]) # 5-ая волна монстров if self.monster_waves_counter == 5: monster7 = Monster.big(xy=(7, 1), game=self) monster8 = Monster.little(xy=(6, 6), game=self) self.monsters.add(monster7, monster8) self.characters.add(monster7, monster8) self.map.add_characters([monster7, monster8]) # 6-ая волна монстров if self.monster_waves_counter == 6: monster9 = Monster.boss_1(xy=(11, 8), game=self) monster7_ = Monster.big(xy=(7, 1), game=self) self.monsters.add(monster9, monster7_) self.characters.add(monster9, monster7_) self.map.add_characters([monster9, monster7_]) # 7-ая волна монстров if self.monster_waves_counter == 7: monster10 = Monster.fast(xy=(2, 5), game=self) monster11 = Monster.big(xy=(9, 9), game=self) monster12 = Monster.little(xy=(8, 4), game=self) self.monsters.add(monster10, monster11, monster12) self.characters.add(monster10, monster11, monster12) self.map.add_characters([monster10, monster11, monster12]) # 8-ая волна монстров if self.monster_waves_counter == 8: monster14 = Monster.fast(xy=(5, 8), game=self) monster19 = Monster.fast(xy=(10, 2), game=self) monster15 = Monster.big(xy=(3, 9), game=self) self.monsters.add(monster14, monster15, monster19) self.characters.add(monster14, monster15, monster19) self.map.add_characters([monster14, monster15, monster19]) # 9-ая волна монстров if self.monster_waves_counter == 9: monster24 = Monster.eye(xy=(7, 3), game=self) monster23 = Monster.big(xy=(6, 3), game=self) monster21 = Monster.little(xy=(4, 6), game=self) self.monsters.add(monster21, monster23, monster24) self.characters.add(monster21, monster23, monster24) self.map.add_characters([monster21, monster23, monster24]) # 10-ая волна монстров if self.monster_waves_counter == 10: monster25 = Monster.eye(xy=(1, 4), game=self) monster29 = Monster.fast(xy=(3, 2), game=self) monster28 = Monster.little(xy=(9, 6), game=self) self.monsters.add(monster25, monster28) self.characters.add(monster25, monster28, monster29) self.map.add_characters([monster25, monster28, monster29]) # 11-ая волна монстров if self.monster_waves_counter == 11: monster31 = Monster.eye(xy=(1, 4), game=self) monster30 = Monster.eye(xy=(1, 4), game=self) monster32 = Monster.little(xy=(12, 2), game=self) self.monsters.add(monster30, monster31, monster32) self.characters.add(monster30, monster31, monster32) self.map.add_characters([monster30, monster31, monster32]) # 12-ая волна монстров if self.monster_waves_counter == 12: monster33 = Monster.boss_2(xy=(9, 8), game=self) monster17 = Monster.fast(xy=(3, 2), game=self) monster18 = Monster.big(xy=(6, 3), game=self) self.monsters.add(monster33, monster17, monster18) self.characters.add(monster33, monster17, monster18) self.map.add_characters([monster33, monster17, monster18]) # 13-ая волна монстров if self.monster_waves_counter == 13: monster34 = Monster.shooting(xy=(10, 2), game=self) monster38 = Monster.eye(xy=(4, 1), game=self) monster37 = Monster.fast(xy=(3, 2), game=self) monster35 = Monster.little(xy=(12, 2), game=self) self.monsters.add(monster34, monster35, monster37, monster38) self.characters.add(monster34, monster35, monster37, monster38) self.map.add_characters( [monster34, monster35, monster37, monster38]) # 14-ая волна монстров if self.monster_waves_counter == 14: monster40 = Monster.shooting(xy=(10, 2), game=self) monster41 = Monster.shooting(xy=(10, 2), game=self) monster42 = Monster.eye(xy=(4, 1), game=self) monster43 = Monster.eye(xy=(3, 5), game=self) self.monsters.add(monster40, monster41, monster42, monster43) self.characters.add(monster40, monster41, monster42, monster43) self.map.add_characters( [monster40, monster41, monster42, monster43]) # 15-ая волна монстров if self.monster_waves_counter == 15: monster52 = Monster.smart(xy=(14, 9), game=self) monster51 = Monster.fast(xy=(7, 5), game=self) monster48 = Monster.little(xy=(7, 7), game=self) monster47 = Monster.little(xy=(6, 6), game=self) self.monsters.add(monster47, monster48, monster51, monster52) self.characters.add(monster47, monster48, monster51, monster52) self.map.add_characters( [monster47, monster48, monster51, monster52]) # 16-ая волна монстров if self.monster_waves_counter == 16: monster53 = Monster.smart(xy=(7, 5), game=self) monster54 = Monster.shooting(xy=(5, 4), game=self) monster55 = Monster.fast(xy=(14, 9), game=self) monster56 = Monster.big(xy=(12, 6), game=self) self.monsters.add(monster56, monster54, monster55, monster53) self.characters.add(monster56, monster54, monster55, monster53) self.map.add_characters( [monster56, monster54, monster55, monster53]) # 17-ая волна монстров if self.monster_waves_counter == 17: monster57 = Monster.smart(xy=(3, 8), game=self) monster58 = Monster.smart(xy=(11, 4), game=self) monster59 = Monster.eye(xy=(1, 7), game=self) monster60 = Monster.little(xy=(1, 1), game=self) self.monsters.add(monster57, monster58, monster59, monster60) self.characters.add(monster57, monster58, monster59, monster60) self.map.add_characters( [monster57, monster58, monster59, monster60]) # 18-ая волна монстров if self.monster_waves_counter == 18: monster61 = Monster.boss_3(xy=(6, 6), game=self) monster62 = Monster.shooting(xy=(0, 0), game=self) monster63 = Monster.eye(xy=(7, 2), game=self) monster64 = Monster.little(xy=(4, 9), game=self) self.monsters.add(monster61, monster62, monster63, monster64) self.characters.add(monster61, monster62, monster63, monster64) self.map.add_characters( [monster61, monster62, monster63, monster64]) # 19-ая волна монстров if self.monster_waves_counter == 19: monster65 = Monster.walking(xy=(3, 1), game=self) monster66 = Monster.shooting(xy=(6, 2), game=self) monster67 = Monster.shooting(xy=(3, 9), game=self) monster68 = Monster.fast(xy=(12, 3), game=self) monster69 = Monster.big(xy=(7, 5), game=self) self.monsters.add(monster65, monster66, monster67, monster68, monster69) self.characters.add(monster65, monster66, monster67, monster68, monster69) self.map.add_characters( [monster65, monster67, monster66, monster68, monster69]) # 20-ая волна монстров if self.monster_waves_counter == 20: monster70 = Monster.walking(xy=(13, 6), game=self) monster71 = Monster.walking(xy=(14, 7), game=self) monster72 = Monster.smart(xy=(12, 8), game=self) monster73 = Monster.fast(xy=(11, 9), game=self) monster74 = Monster.little(xy=(10, 5), game=self) self.monsters.add(monster70, monster71, monster72, monster73, monster74) self.characters.add(monster70, monster71, monster72, monster73, monster74) self.map.add_characters( [monster70, monster71, monster72, monster73, monster74]) # 21-ая волна монстров if self.monster_waves_counter == 21: monster75 = Monster.ghost(xy=(13, 6), game=self) monster76 = Monster.smart(xy=(12, 8), game=self) monster77 = Monster.fast(xy=(11, 9), game=self) monster78 = Monster.little(xy=(10, 5), game=self) monster79 = Monster.little(xy=(10, 5), game=self) self.monsters.add(monster75, monster76, monster77, monster78, monster79) self.characters.add(monster75, monster76, monster77, monster78, monster79) self.map.add_characters( [monster75, monster77, monster76, monster78, monster79]) # 22-ая волна монстров if self.monster_waves_counter == 22: monster80 = Monster.ghost(xy=(3, 2), game=self) monster81 = Monster.walking(xy=(1, 9), game=self) monster82 = Monster.shooting(xy=(4, 2), game=self) monster83 = Monster.eye(xy=(4, 4), game=self) monster84 = Monster.big(xy=(8, 8), game=self) self.monsters.add(monster80, monster81, monster82, monster83, monster84) self.characters.add(monster80, monster81, monster82, monster83, monster84) self.map.add_characters( [monster80, monster81, monster82, monster83, monster84]) # 23-ая волна монстров if self.monster_waves_counter == 23: monster85 = Monster.ghost(xy=(13, 6), game=self) monster86 = Monster.walking(xy=(13, 6), game=self) monster87 = Monster.shooting(xy=(3, 9), game=self) monster88 = Monster.fast(xy=(11, 9), game=self) monster89 = Monster.little(xy=(10, 5), game=self) self.monsters.add(monster85, monster86, monster87, monster88, monster89) self.characters.add(monster85, monster86, monster87, monster88, monster89) self.map.add_characters( [monster85, monster86, monster87, monster88, monster89]) # 24-ая волна монстров if self.monster_waves_counter == 24: monster91 = Monster.boss_4(xy=(2, 9), game=self) monster92 = Monster.ghost(xy=(2, 1), game=self) monster93 = Monster.shooting(xy=(5, 2), game=self) monster94 = Monster.eye(xy=(13, 5), game=self) monster90 = Monster.big(xy=(12, 7), game=self) self.monsters.add(monster90, monster91, monster92, monster93, monster94) self.characters.add(monster90, monster91, monster92, monster93, monster94) self.map.add_characters( [monster90, monster91, monster92, monster93, monster94]) # 25-ая волна монстров if self.monster_waves_counter == 25: monster100 = Monster.bat(xy=(4, 8), game=self) monster96 = Monster.walking(xy=(8, 2), game=self) monster97 = Monster.smart(xy=(3, 7), game=self) monster98 = Monster.fast(xy=(10, 1), game=self) monster99 = Monster.fast(xy=(1, 2), game=self) monster95 = Monster.little(xy=(0, 0), game=self) self.monsters.add(monster95, monster99, monster98, monster97, monster96, monster100) self.characters.add(monster95, monster99, monster98, monster97, monster96, monster100) self.map.add_characters([ monster95, monster99, monster98, monster96, monster97, monster100 ]) # 26-ая волна монстров if self.monster_waves_counter == 26: monster101 = Monster.bat(xy=(3, 6), game=self) monster102 = Monster.bat(xy=(13, 3), game=self) monster103 = Monster.ghost(xy=(12, 2), game=self) monster104 = Monster.shooting(xy=(1, 9), game=self) monster105 = Monster.eye(xy=(13, 1), game=self) monster106 = Monster.little(xy=(8, 6), game=self) self.monsters.add(monster101, monster102, monster103, monster104, monster105, monster106) self.characters.add(monster101, monster102, monster103, monster104, monster105, monster106) self.map.add_characters([ monster101, monster102, monster103, monster104, monster105, monster106 ]) # 27-ая волна монстров if self.monster_waves_counter == 27: monster112 = Monster.vampier(xy=(1, 4), game=self) monster111 = Monster.walking(xy=(9, 1), game=self) monster110 = Monster.smart(xy=(0, 9), game=self) monster109 = Monster.fast(xy=(14, 9), game=self) monster108 = Monster.big(xy=(13, 2), game=self) monster107 = Monster.little(xy=(2, 2), game=self) self.monsters.add(monster107, monster108, monster109, monster110, monster111, monster112) self.characters.add(monster107, monster108, monster109, monster110, monster111, monster112) self.map.add_characters([ monster107, monster108, monster109, monster110, monster111, monster112 ]) # 28-ая волна монстров if self.monster_waves_counter == 28: monster113 = Monster.vampier(xy=(4, 8), game=self) monster114 = Monster.bat(xy=(6, 3), game=self) monster115 = Monster.walking(xy=(3, 2), game=self) monster116 = Monster.shooting(xy=(1, 9), game=self) monster117 = Monster.eye(xy=(10, 2), game=self) monster118 = Monster.little(xy=(12, 5), game=self) self.monsters.add(monster113, monster114, monster115, monster116, monster117, monster118) self.characters.add(monster113, monster114, monster115, monster116, monster117, monster118) self.map.add_characters([ monster113, monster114, monster115, monster116, monster117, monster118 ]) # 29-ая волна монстров if self.monster_waves_counter == 29: monster124 = Monster.bat(xy=(4, 8), game=self) monster123 = Monster.bat(xy=(6, 3), game=self) monster122 = Monster.walking(xy=(3, 2), game=self) monster121 = Monster.shooting(xy=(1, 9), game=self) monster120 = Monster.fast(xy=(10, 2), game=self) monster119 = Monster.big(xy=(12, 5), game=self) self.monsters.add(monster119, monster120, monster121, monster122, monster123, monster124) self.characters.add(monster119, monster120, monster121, monster122, monster123, monster124) self.map.add_characters([ monster119, monster120, monster121, monster122, monster123, monster124 ]) # 30-ая волна монстров if self.monster_waves_counter == 30: monster130 = Monster.boss_5(xy=(0, 0), game=self) monster129 = Monster.vampier(xy=(0, 0), game=self) monster128 = Monster.ghost(xy=(0, 0), game=self) monster127 = Monster.smart(xy=(0, 0), game=self) monster126 = Monster.eye(xy=(0, 0), game=self) monster125 = Monster.big(xy=(0, 0), game=self) self.monsters.add(monster125, monster126, monster127, monster128, monster129, monster130) self.characters.add(monster125, monster126, monster127, monster128, monster129, monster130) self.map.add_characters([ monster125, monster126, monster127, monster128, monster129, monster130 ]) def all_heroes_dead(self) -> bool: """ return True если все герои умерли """ characters = self.characters.sprites() heroes = [o for o in characters if o.type == "hero"] if heroes: return False return True def get_active_character(self) -> Optional[CharacterHM]: """ возвращает активного персонажа """ characters = self.characters.sprites() for character in characters: if character.active: return character return None def get_active_hero(self) -> Optional[Hero]: """ возвращает активного героя """ character = self.get_active_character() if character.type == "hero": return character return None def get_active_monster(self) -> Optional[Monster]: """ возвращает активного монстра """ character = self.get_active_character() if character.type == "monster": return character return None def get_next_character(self) -> CharacterHM: """ возвращает следующего персонажа после активного """ characters = self.characters.sprites() characters_count = len(characters) # найдём активного персонажа for i, character in enumerate(characters): if character.active: # если активный персонаж не последний в списке, вернём следующего if characters_count > i + 1: return characters[i + 1] # если активный персонаж последний в списке, вернём первого return characters[0] raise ValueError("нет активного персонажа") def is_motion(self) -> bool: """ True - если хоть один персонаж в движении False - если все персонажи закончили движение и стоят в своих клетках """ if self.is_world_motion: return True characters = self.characters.sprites() if not characters: return False for character in characters: cell = character.my_cell() if character.rect.center != cell.rect.center: return True return False def update_counters(self) -> Counters: """ Меняет активного персонажа и обновляет счётчики. Если у персонажа закончился действия/actions, то ход/turn переходит к следующему персонажу, Если закончился круг/round, обновим счётчик кругов и волн-монстров, """ # активный персонаж active_character = self.get_active_character() # выходим если у персонажа ещё не закончился действия/actions if active_character.actions > 0: return Counters(turn=False, round=False, wave=False) # если у персонажа закончился действия/actions, ход/turn переходит к следующему персонажу self.turns_counter += 1 next_character = self.get_next_character() active_character.end_turn() next_character.start_turn() # если закончился круг/round, обновим счётчик кругов, начинает ходить первый игрок first_character = self.characters.sprites()[0] if next_character == first_character: self.rounds_counter += 1 self.turns_counter = 0 # если счётчик кругов кратен 5, то обновим счётчик волн-монстров if not self.rounds_counter % self.rounds_between_monster_wave: self.monster_waves_counter += 1 # закончилась волн-монстров return Counters(turn=True, round=True, wave=True) # закончился круг/round return Counters(turn=True, round=True, wave=False) # закончился ход/turn return Counters(turn=True, round=False, wave=False) def hero_actions(self) -> None: """ В зависимости от нажатой кнопки меняет управление клавиатуры по умолчанию - управление на карте UP, DOWN, LEFT, RIGHT F1 - help, описание кнопок I - управление на рюкзак A - атакует """ hero = self.get_active_hero() if not hero: return keys = pygame.key.get_pressed() is_any_key_pressed = bool([i for i in keys if i]) # нажата любая кнопка # если ни одна кнопка не нажата, снимает блокировку клавиатуру if not is_any_key_pressed: self.kb_locked = False return # если нажата клавиша и клавиатура заблокирована, то клавиши не проверяем if self.kb_locked and is_any_key_pressed: return # блокирует клавиатуру, пока не будут отпущены все кнопки if not self.kb_locked and is_any_key_pressed: self.kb_locked = True if self.kb_mode == "map": if keys[pygame.K_F1]: self.kb_mode = "controls" return # меняет режим клавиатуры с карты на рюкзак if keys[pygame.K_i]: self.kb_mode = "backpack" self.backpack.active_items_id = 0 return # меняет режим клавиатуры с карты на атаку if keys[pygame.K_a]: self.kb_mode = "attack" return # Передвижение персонажа по карте if keys[pygame.K_UP]: hero.move(pygame.K_UP) return if keys[pygame.K_DOWN]: hero.move(pygame.K_DOWN) return if keys[pygame.K_LEFT]: hero.move(pygame.K_LEFT) return if keys[pygame.K_RIGHT]: hero.move(pygame.K_RIGHT) return # герой поднимает вещь на карте if keys[pygame.K_e]: hero.pickup_item() return if keys[pygame.K_d]: hero.drop_down_item() return if keys[pygame.K_w]: hero.wear() return if keys[pygame.K_u]: hero.use() return # управление в рюкзаке elif self.kb_mode == "controls": # переключает управление на карту if keys[pygame.K_ESCAPE] or keys[pygame.K_F1]: self.kb_mode = "map" return elif self.kb_mode == "backpack": # переключает управление на карту if keys[pygame.K_ESCAPE] or keys[pygame.K_i]: self.kb_mode = "map" return # выбирает вещь в рюкзаке if keys[pygame.K_UP]: self.backpack.select_item(pygame.K_UP) return if keys[pygame.K_DOWN]: self.backpack.select_item(pygame.K_DOWN) return if keys[pygame.K_RETURN]: self.backpack.item_to_hands() return elif self.kb_mode == "attack": if keys[pygame.K_ESCAPE] or keys[pygame.K_a]: self.kb_mode = "map" return if keys[pygame.K_UP]: hero = self.get_active_hero() hero.attack(pygame.K_UP) self.kb_mode = "map" return if keys[pygame.K_DOWN]: hero = self.get_active_hero() hero.attack(pygame.K_DOWN) self.kb_mode = "map" return if keys[pygame.K_LEFT]: hero = self.get_active_hero() hero.attack(pygame.K_LEFT) self.kb_mode = "map" return if keys[pygame.K_RIGHT]: hero = self.get_active_hero() hero.attack(pygame.K_RIGHT) self.kb_mode = "map" return def monster_actions(self) -> None: """ двигает монстров """ monster = self.get_active_monster() if not monster: return monster.move() monster.attack() def draw(self) -> None: """ Рисует карту, героев, мрнстров """ self.screen.fill(s.BLACK) self.maps.draw(self.screen) if s.DEBUG: # self.map.draw_cells(self.screen) self.draw_cells_xy() self.items.draw(self.screen) self.characters.draw(self.screen) self.draw_items_in_hands() self.draw_characters_count_in_cell() if s.DEBUG: self.draw_monster_path() # draw dashboard, backpack, controls window self.dashboard.draw() if self.kb_mode == "backpack": self.backpack.draw() if self.kb_mode == "controls": self.controls.draw() def draw_items_in_hands(self): """ Рисует вещь в руках героя """ hero = self.get_active_hero() if hero and hero.item_in_hands: pic_ = pygame.transform.scale(hero.item_in_hands.image2, (hero.rect.w, hero.rect.h)) self.screen.blit(pic_, hero.rect.topleft) def draw_characters_count_in_cell(self) -> None: """ Рисует количество персонажей в клетке""" height = 30 color = s.BLACK font = pygame.font.SysFont(pygame.font.get_default_font(), height) shift = 5 # сместим текс на есколько пикселей от края клетки for cell in self.map.cells: count = len(cell.characters) if count >= 2: render = font.render(str(count), True, color) rect = render.get_rect() self.screen.blit(render, cell.bottom_right(rect, shift)) def draw_cells_xy(self) -> None: """ рисует на карте координаты клетки xy """ height = 15 color = s.BLACK font = pygame.font.SysFont(pygame.font.get_default_font(), height) shift = 3 # сместим текс на есколько пикселей от края клетки for cell in self.map.cells: # координаты клетки (x, y): top, left render = font.render(f"{cell.xy[0]},{cell.xy[1]}", True, color) xy1 = cell.top_left(shift) self.screen.blit(render, xy1) # координаты экрана (пиксели): bottom, right render = font.render(f"{cell.rect.right},{cell.rect.bottom}", True, color) rect2 = render.get_rect() xy2 = cell.bottom_right(rect2, shift) self.screen.blit(render, xy2) def draw_monster_path(self) -> None: """ рисует на карте путь монстра """ monster = self.get_active_monster() if not monster: return route = monster.route for i_from, cell_from in enumerate(route): i_to = i_from + 1 if i_to >= len(route): break cell_to = monster.route[i_to] pygame.draw.line(self.screen, s.RED, cell_from.center(), cell_to.center(), 10) pygame.display.update() def game_over(self) -> None: """ рисуем надпись GameOver """ font = pygame.font.SysFont("consolas", 100) text = font.render("Game Over", True, s.RED) text_rect = text.get_rect() text_rect.center = self.screen.get_rect().center size = (text_rect.w + 50, text_rect.h + 50) background = pygame.Surface(size) background.fill(s.BLACK) background_rect = background.get_rect() background_rect.center = text_rect.center self.screen.blit(background, background_rect.topleft) self.screen.blit(text, text_rect.topleft) # ch2 = Channel(2) # Channel(2).play(Sound(s.S_GAME_OVER)) # import time # time.sleep(3) if not self.is_game_over: Channel(2).play(Sound(s.S_GAME_OVER)) self.is_game_over = True def win(self) -> None: text = Text(self.screen) text.size = 100 text.draw_list( ["You win", f"you killed {self.monsters_killed} monsters"], 200, 400) time.sleep(5) def update_sprites(self) -> None: """ обновим передвигающиеся спрайты на экране """ self.shifting_world() self.items.update() self.characters.update() def shifting_world(self): """" Выполняем сдвиг игрового мира, персонаж в центре """ hero = self.get_active_hero() if not hero: return direction = self.sprite_center_out_of_screen(hero) if not direction: self.is_world_motion = False return # смещаем мир speed = s.SPEED self.is_world_motion = True map_rect = self.map.rect shift_x, shift_y = self.shift_world_direction(direction) diff_x = map_rect.x - shift_x if diff_x > 0: diff_x = min(speed, diff_x) elif diff_x < 0: diff_x = max(-speed, diff_x) diff_y = map_rect.y - shift_y if diff_y > 0: diff_y = min(speed, diff_y) elif diff_y < 0: diff_y = max(-speed, diff_y) # движение мира закончилось if not (diff_x or diff_y): return # движение мира, смещаем спрайты self.world_shift = (shift_x, shift_y) map_rect.topleft = (shift_x, shift_y) for cell in self.map.cells: cell.update_rect() for item in self.items: item.update_rect() for character in self.characters: character.update_rect() def sprite_center_out_of_screen(self, sprite) -> str: """ return direction если спрайт вышел за пределы экрана """ map_rect = self.map.rect if sprite.rect.centerx < 0: return "left" if sprite.rect.centery < 0: return "top" if sprite.rect.centerx > map_rect.width: return "right" if sprite.rect.centery > map_rect.height: return "down" return "" def shift_world_direction(self, direction: str) -> Tuple[int, int]: """ return x,y сдвига игрового мира в направлении direction """ hero = self.get_active_hero() cell = hero.my_cell() cell_r = cell.rect map_rect = self.map.rect map_size = self.map.size shift = [0, 0] if direction == "top": shift[0] = self.world_shift[0] shift[1] = -((cell.xy[1] + 1) * s.CELL_W - map_rect.height) elif direction == "down": shift[0] = self.world_shift[0] shift[1] = -(cell.xy[1] * s.CELL_W) elif direction == "right": shift[0] = -(cell.xy[0] * s.CELL_W) shift[1] = self.world_shift[1] elif direction == "left": shift[0] = -((cell.xy[0] + 1) * s.CELL_W - map_rect.width) shift[1] = self.world_shift[1] if map_size.width - cell_r.x < map_rect.width: shift[0] = map_rect.width - map_size.width if shift[0] > 0: shift[0] = 0 if map_size.height - cell_r.y < map_rect.height: shift[1] = map_rect.height - map_size.height if shift[1] > 0: shift[1] = 0 return tuple(shift)
class GameView(arcade.View): def __init__(self): """ Sets up the initial conditions of the game :param width: Screen width :param height: Screen height """ super().__init__() self.background = arcade.load_texture("images/space1.png") self._keys = set() self.score = 0 self.lives = PLAYER_LIVES self.delay = START_DELAY self.window.set_mouse_visible(False) #Objects self.dashboard = Dashboard() self.ship = Ship() # bullet Sprites self.bullets = SpriteList() # how many bullets you're allowed to make self.ammo = 5 self.meteors = SpriteList() self.animations = SpriteList() self.lifeList = SpriteList() #Explosion Frames self.explosion_texture_list = createExplosionTextureList() self.animationsLoaded = False self.load_animantion_frames() # Load sounds. Sounds from kenney.nl self.gun_sound = arcade.sound.load_sound( ":resources:sounds/laser2.wav") self.hit_sound = arcade.sound.load_sound( ":resources:sounds/explosion2.wav") arcade.set_background_color(arcade.color.ARSENIC) #This is for the lives start_x = 20 x = 0 start_y = SCREEN_HEIGHT - 125 for life in range(self.lives): heart = Sprite('images/heart.png') heart.scale = 0.1 x = start_x + (life * (heart.width / 2) + 10) heart.center_x = x heart.center_y = start_y self.lifeList.append(heart) def on_draw(self): """ Called automatically by the arcade framework. Handles the responsiblity of drawing all elements. """ # clear the screen to begin drawing arcade.start_render() arcade.draw_lrwh_rectangle_textured(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, self.background) self.dashboard.draw() self.draw_score() self.bullets.draw() self.meteors.draw() #Check if the ship is still alive if (self.ship.alive): self.ship.draw() #Render current animations self.animations.draw() #Verify if animations have been loaded else display a "get ready" message if (not self.animationsLoaded): self.draw_get_ready() #Draw the hearts on the screen self.lifeList.draw() def draw_score(self): """ Puts the current score on the screen """ score_text = f"Score: {self.score}" start_x = 20 start_y = SCREEN_HEIGHT - 100 arcade.draw_rectangle_filled(self.dashboard.right // 2, SCREEN_HEIGHT - 70, self.dashboard.right + 1, 140, arcade.color.DARK_MIDNIGHT_BLUE) arcade.draw_text(score_text, start_x=start_x, start_y=start_y, font_size=30, color=arcade.color.WHITE) text = f"Ammo: {self.ammo}" arcade.draw_text(text, start_x, SCREEN_HEIGHT - (SCREEN_HEIGHT // 1.10), font_size=30, color=arcade.color.WHITE) def draw_get_ready(self): """ Writes a get ready message on the screen """ arcade.draw_text("Get ready", ((SCREEN_WIDTH - DASHBOARD_WIDTH) / 2) + DASHBOARD_WIDTH, SCREEN_HEIGHT / 2, arcade.color.WHITE, font_size=30, anchor_x="center") def update(self, delta_time): """ Update each object in the game. :param delta_time: tells us how much time has actually elapsed """ # Check to see if keys are being held, and then # take appropriate action self.check_keys() self.check_off_screen() self.bullets.update() self.meteors.update() if (self.animationsLoaded): if randint(0, self.delay) == 1: self.create_meteor() self.ship.update() self.check_collisions() self.animations.update() #Check if animations have already been loaded if (not self.animationsLoaded): self.check_animations_loaded() #Check if the ship is still alive if (not self.ship.alive): #Check if there ship has frames left #This gives time for the explosion of the ship to appear on the screen if (self.ship.framesAfterDead == 0): self.gameOver() else: self.ship.framesAfterDead = self.ship.framesAfterDead - 1 def check_collisions(self): for meteor in self.meteors: #Check if a meteor collided with a bullet if meteor.collides_with_list(self.bullets): meteor.alive = False # Instantiate an explosion self.create_explosion(meteor.center_x, meteor.center_y) arcade.sound.play_sound(self.hit_sound) self.score += 10 #Create a temp list for the ship ship = SpriteList() ship.append(self.ship) #Check if a meteor collided with the ship if meteor.collides_with_list(ship): meteor.alive = False self.ship.alive = False # Instantiate an explosion self.create_explosion(meteor.center_x, meteor.center_y) arcade.sound.play_sound(self.hit_sound) #Check if a meteor crossed the bottom screen elif meteor.bottom <= 10: self.create_explosion(meteor.center_x, meteor.center_y) arcade.sound.play_sound(self.hit_sound) self.lives -= 1 self.lifeList.remove(self.lifeList[-1]) meteor.alive = False if self.lives <= 0: self.gameOver() #Check if a bullet collided with a meteor for bullet in self.bullets: if bullet.collides_with_list(self.meteors): bullet.alive = False #Remove all dead sprites self.clean_up_zombies() def clean_up_zombies(self): for meteor in self.meteors: if not meteor.alive: self.meteors.remove(meteor) for bullet in self.bullets: if not bullet.alive: self.bullets.remove(bullet) def check_off_screen(self): """ Removes sprites that have gone off screen """ for bullet in self.bullets: if bullet.bottom > SCREEN_HEIGHT + bullet.height: self.bullets.remove(bullet) def check_keys(self): """ Checks to see if the user is holding down an arrow key, and if so, takes appropriate action. """ if arcade.key.LEFT in self._keys: self.ship.move_left() elif arcade.key.RIGHT in self._keys: self.ship.move_right() def on_key_press(self, key, key_modifiers): """ Called when a key is pressed. Sets the state of holding an arrow key. :param key: The key that was pressed :param key_modifiers: Things like shift, ctrl, etc """ #Wait for animations to be loaded before accepting user input if (self.animationsLoaded): if key == arcade.key.LEFT: self._keys.add(key) if key == arcade.key.RIGHT: self._keys.add(key) if key == arcade.key.SPACE: self.create_bullet() if key == arcade.key.A or key == arcade.key.S or key == arcade.key.D or key == arcade.key.F: if self.dashboard.check_answer(key): self.reload() # !!!!!!!!!!!!! # TODO: Add display for right/wrong answer and its consequences # !!!!!!!!!!!!! else: self.lives -= 1 self.lifeList.remove(self.lifeList[-1]) if self.lives <= 0: self.gameOver() if key == arcade.key.F1: # User hits s. Flip between full and not full screen. self.window.set_fullscreen(not self.window.fullscreen) # Instead of a one-to-one mapping, stretch/squash window to match the # constants. This does NOT respect aspect ratio. You'd need to # do a bit of math for that. self.window.set_viewport(0, SCREEN_WIDTH, 0, SCREEN_HEIGHT) if key == arcade.key.ESCAPE: self.gameOver() def on_key_release(self, key, key_modifiers): """ Called when a key is released. Sets the state of the arrow key as being not held anymore. :param key: The key that was pressed :param key_modifiers: Things like shift, ctrl, etc """ #The game is waiting for animations to be loaded before accepting user input if (self.animationsLoaded) and len(self._keys) > 0: if key == arcade.key.LEFT: self._keys.remove(key) if key == arcade.key.RIGHT: self._keys.remove(key) def create_bullet(self): """ Creates an instance of laser and appends it to the bullets sprite list """ if self.ammo > 0: laser = Laser(self.ship.center_x, self.ship.center_y) self.bullets.append(laser) arcade.sound.play_sound(self.gun_sound) self.ammo -= 1 def reload(self): self.ammo += 5 def create_meteor(self): """ Creates an instance of Meteor and appends it to the meteors sprite list """ self.meteors.append(Meteor()) def create_explosion(self, x, y): """ Creates an instance of Explosion and appends it to the animations sprite list """ new_explosion = Explosion(self.explosion_texture_list, x, y) # Call update() because it sets which image we start on new_explosion.update() self.animations.append(new_explosion) def load_animantion_frames(self): """ Creates an instance of all possible animations so that they are loaded before the game starts """ # Initialize an explosion outside of the screen to load the explosion animation frames self.create_explosion(-50, -50) def check_animations_loaded(self): """ Sets the self.animationsLoaded variable to true if the animations list is empty """ if (len(self.animations) == 0): self.animationsLoaded = True def gameOver(self): """ Renders the GemeOverView and sends the score to the server """ self.window.show_view(GameOverView(self.score))
class Game: def __init__(self, surface): self.surface = surface self.state = const.MENU self.level = 1 self.dashboard = Dashboard(self.surface) self.ship = Ship(self.surface, LEVEL_DATA[self.level]['start_fire']) self.active_text_box = None # can only have one at a time self.f_tick_time = 5 # seconds between fire ticks self.f_anim_time = 0.4 # seconds between fire animation frames self.last_f_tick = time.time() self.last_f_anim = time.time() self.s_tick_time = 2 # seconds between sprinkler ticks self.last_s_tick = time.time() - 0.5 # offset so they don't happen simultaneously self.event_room = None self.event_target_flvl = 0 self.event_time = 20 self.event_start_time = 0 self.repair_room = None self.repair_time = 7 self.repair_start_time = 0 self.r_tick_time = 2 # seconds between radar ticks self.last_r_tick = time.time() - 1.5 # offset so they don't happen simultaneously self.lightyear_length = 10 # seconds it takes to travel 1 lightyear self.last_lightyear_tick = time.time() self.lightyears_left = 9 self.damage_anim_start = 0 self.is_paused = False self.time_paused = None self.shield_room_id = 0 self.tut_progress = 0 def begin(self): """Resets game state and begins a new game. """ self.__init__(self.surface) def begin_level(self): if self.level == 1: self.state = const.TUTORIAL self.active_text_box = tutorial.get_text(self.tut_progress) self.pause() elif self.level == 2: self.state = const.INSTALLING self.pause() install_text = 'Seeing as there is an asteroid field between you and the next spaceport, you decide to purchase an ' + \ 'asteroid shield that will protect you from them as long as it\'s not burning. (Click on a room to place it there)' self.active_text_box = TextBox(install_text, const.MED, 'NEW SYSTEM') self.active_text_box.add_button("Resume", const.GREEN) elif self.level == 3: self.ship.room_list[self.shield_room_id].type = const.SHIELD self.state = const.INSTALLING self.pause() install_text = 'Antiipating that your systems will soon start breaking down from the fire, you buy a repair system. ' + \ '(Click on a room to place it there)' self.active_text_box = TextBox(install_text, const.MED, 'NEW SYSTEM') self.active_text_box.add_button("Resume", const.GREEN) def level_up(self): self.level += 1 self.state = const.PLAYING self.dashboard = Dashboard(self.surface) # reset dash self.ship = Ship(self.surface, LEVEL_DATA[self.level]['start_fire']) # reset ship self.active_text_box = None # clear text box self.last_f_tick = time.time() self.last_s_tick = time.time() - 0.5 # offset so they don't happen simultaneously self.lightyears_left = 9 self.last_lightyear_tick = time.time() self.is_paused = False self.time_paused = None self.begin_level() def press_key(self, key): if key == pygame.K_SPACE: # spacebar if self.state == const.MENU: self.begin_level() if key == pygame.K_UP: if self.state == const.PLAYING and not self.ship.is_disabled(const.LASER_PORT): self.dashboard.radar.fire_laser(const.NORTH) if key == pygame.K_DOWN: if self.state == const.PLAYING and not self.ship.is_disabled(const.LASER_STBD): self.dashboard.radar.fire_laser(const.SOUTH) def move_mouse(self, mouse_x, mouse_y): for room in self.ship.room_list: if mouse_x > room.x_pos and mouse_x < room.x_pos + room.size and \ mouse_y > room.y_pos and mouse_y < room.y_pos + room.size: room.moused_over = True else: room.moused_over = False if self.active_text_box and len(self.active_text_box.buttons) > 0: for btn in self.active_text_box.buttons: btn.check_mouse_hover(mouse_x, mouse_y) # dashboard buttons must be checked individually self.dashboard.laser_button_n.check_mouse_hover(mouse_x, mouse_y) self.dashboard.laser_button_s.check_mouse_hover(mouse_x, mouse_y) self.dashboard.cactus_button.check_mouse_hover(mouse_x, mouse_y) self.dashboard.repair_switch.check_mouse_hover(mouse_x, mouse_y) def click_mouse(self): # This function currently does not need mouse_x or mouse_y, but # it might need it in the future depending on what we use it for. if self.state == const.MENU: self.begin_level() return if self.state == const.PLAYING or self.state == const.REPAIRING: for room in self.ship.room_list: if room.moused_over: if self.state == const.PLAYING: if room.sprinkling: room.sprinkling = False self.ship.num_sprinkling -= 1 SOUNDS['press'].play() elif not room.sprinkling and self.ship.num_sprinkling < SPRINKLER_LIMIT: room.sprinkling = True self.ship.num_sprinkling += 1 SOUNDS['press'].play() elif not room.sprinkling: # sprinkler limit reached SOUNDS['invalid'].play() elif self.state == const.REPAIRING: if room.is_breaking: self.repair_room.is_breaking = False self.repair_room = None SOUNDS['press'].play() # Switch back to sprinkler mode for convenience self.state = const.PLAYING else: SOUNDS['invalid'].play() # dashboard buttons must be checked individually if self.dashboard.laser_button_n.moused_over and \ self.ship.is_working(const.LASER_PORT): self.dashboard.radar.fire_laser(const.NORTH) if self.dashboard.laser_button_s.moused_over and \ self.ship.is_working(const.LASER_STBD): self.dashboard.radar.fire_laser(const.SOUTH) if self.dashboard.cactus_button.moused_over: self.pause() self.tut_progress = 0 self.state = const.TUTORIAL self.active_text_box = tutorial.get_text(self.tut_progress) if self.dashboard.repair_switch.moused_over and \ self.ship.is_working(const.REPAIR): if self.level == 3: if self.state == const.PLAYING: self.state = const.REPAIRING elif self.state == const.REPAIRING: self.state = const.PLAYING SOUNDS['press'].play() if self.state == const.INSTALLING: for room_id in range(len(self.ship.room_list)): if self.ship.room_list[room_id].moused_over and self.ship.room_list[room_id].type == const.EMPTY: if self.level == 2: self.ship.room_list[room_id].type = const.SHIELD self.shield_room_id = room_id elif self.level == 3: self.ship.room_list[room_id].type = const.REPAIR self.state = const.PLAYING SOUNDS['press'].play() self.unpause() elif self.ship.room_list[room_id].moused_over: SOUNDS['invalid'].play() if self.active_text_box and len(self.active_text_box.buttons) > 0: # Here is where I'll have to figure out how to add functionality # to the buttons. I might just have to do this on a case-by-case basis. button_list = self.active_text_box.buttons # I know the first button of the GAME OVER text goes to the title screen. if button_list[0].moused_over and (self.state == const.FIRE_OUT or \ self.state == const.HULL_OUT or self.state == const.BRIDGE_OUT): self.begin() return # The first button of the WIN_LEVEL text advances to the next level. if button_list[0].moused_over and self.state == const.WIN_LEVEL: self.level_up() return # The first button of the WIN_GAME text brings you back to the main menu if button_list[0].moused_over and self.state == const.WIN_GAME: self.begin() return # The first button of the event text returns to normal gameplay if button_list[0].moused_over and self.state == const.EVENT: self.unpause() self.state = const.PLAYING self.active_text_box = None return if button_list[0].moused_over and self.state == const.INSTALLING: self.active_text_box = None # The first button of tutorial text advances the tutorial, unless tutorial is finished. if button_list[0].moused_over and self.state == const.TUTORIAL: if self.tut_progress >= 10: # last tutorial text self.unpause() self.state = const.PLAYING self.active_text_box = None return else: self.tut_progress += 1 self.active_text_box = tutorial.get_text(self.tut_progress) return # The second button of tutorial text skips the tutorial. if len(button_list) >= 2 and button_list[1].moused_over and self.state == const.TUTORIAL: self.unpause() self.state = const.PLAYING self.active_text_box = None return def pause(self): self.is_paused = True self.time_paused = time.time() def unpause(self): self.is_paused = False time_missed = time.time() - self.time_paused self.last_lightyear_tick += time_missed self.last_f_tick += time_missed self.last_f_anim += time_missed self.last_s_tick += time_missed self.last_r_tick += time_missed self.event_start_time += time_missed self.repair_start_time += time_missed def start_event(self): if self.level == 1: num_systems = 4 elif self.level == 2: num_systems = 5 elif self.level == 3: num_systems = 6 event_room_id = random.randint(2, num_systems) for i in self.ship.room_list: if i.type == event_room_id: self.event_room = i self.event_room.is_event = True if self.event_room.fire_level <= 1: self.event_target_flvl = 2 elif self.event_room.fire_level == 2: self.event_target_flvl = 0 self.event_start_time = time.time() self.state = const.EVENT self.pause() if self.event_target_flvl == 0: event_text = 'The fire in your ship\'s ' + const.ROOM_NAMES[event_room_id] + ' room is starting to wear down the hull. ' + \ 'If you don\'t extinguish it soon, the ship will take damage.' else: event_text = 'A particularly annoying species of space termites has infested the ' + const.ROOM_NAMES[event_room_id] + ' room! ' + \ 'You can exterminate them by setting the room on fire, but if you take too long they\'ll damage your hull. ' self.active_text_box = TextBox(event_text, const.MED, 'EVENT') self.active_text_box.add_button('Resume', const.GREEN) def start_repair(self, room_id): for i in self.ship.room_list: if i.type == room_id: self.repair_room = i self.repair_room.is_breaking = True self.repair_start_time = time.time() self.state = const.EVENT self.pause() repair_text = 'The ' + const.ROOM_NAMES[room_id] + ' system is breaking! If it isn\'t fixed soon,' + \ 'it will become permanently disabled. (To repair it, click on the repair button and then ' + \ 'on the room you want to repair)' self.active_text_box = TextBox(repair_text, const.MED, 'EVENT') self.active_text_box.add_button("Resume", const.GREEN) def tick(self): """Checks if fire and sprinklers need to activate, checks if you've won or lost, and does whatever other things need to happen each frame. """ if self.state == const.PLAYING or self.state == const.REPAIRING: # Check if you've lost the game if self.ship.num_onfire == 0: self.state = const.FIRE_OUT self.pause() game_over_text = 'The fire went out! You\'re relieved for a moment, but then ' + \ 'you feel your ship begin to stall. Your engines can no longer be powered. ' + \ 'You and your cactus are screwed.' self.active_text_box = TextBox(game_over_text, const.MED, 'GAME OVER') self.active_text_box.add_button('Back to Title', const.RED) elif self.ship.is_bridge_burning(): self.state = const.BRIDGE_OUT self.pause() game_over_text = 'The bridge has burned up, and you along with it! You were ' + \ 'unable to control the flames, and they consumed you. Not even your brave ' + \ 'cactus survived.' self.active_text_box = TextBox(game_over_text, const.MED, 'GAME OVER') self.active_text_box.add_button('Back to Title', const.RED) elif self.dashboard.get_health() <= 0: self.state = const.HULL_OUT self.pause() game_over_text = 'The hull is breached! The air in your space ship rushes out into ' + \ 'the vacuum of space, sucking you out with it. Luckily, by some miracle, your ' + \ 'cactus manages to survive, and lives to tell your tale.' self.active_text_box = TextBox(game_over_text, const.MED, 'GAME OVER') self.active_text_box.add_button('Back to Title', const.RED) # Check if you've won the level or game if self.lightyears_left <= 0: if self.level < 3: self.state = const.WIN_LEVEL self.pause() win_text = 'Your ship manages to reach the spaceport intact! You get a much-needed ' + \ 'chance to refill your water, repair your hull, and catch your breath. But you are ' + \ 'still a long way from home, so you must keep going!' self.active_text_box = TextBox(win_text, const.MED, 'SPACEPORT REACHED') self.active_text_box.add_button('Continue to level ' + str(self.level + 1), const.GREEN) else: self.state = const.WIN_GAME self.pause() win_text = 'You glance away from the hectic state of your ship for a moment only ' + \ 'to be greeted by the blue and green planet you call home. You\'re finally back ' + \ 'to Earth! You and your cactus will remember this journey forever.' self.active_text_box = TextBox(win_text, const.MED, 'EARTH REACHED') self.active_text_box.add_button('Woohoo!', const.GREEN) # Check if you've travelled another lightyear current_time = time.time() if current_time - self.last_lightyear_tick >= self.lightyear_length: self.lightyears_left -= 1 self.last_lightyear_tick = time.time() level_str = LEVEL_DATA[self.level][str(self.lightyears_left)] if 'event' in level_str: self.start_event() if 'ship_n' in level_str: self.dashboard.radar.add_alien(const.NORTH) if 'ship_s' in level_str: self.dashboard.radar.add_alien(const.SOUTH) if 'ast_nw' in level_str: self.dashboard.radar.add_asteroid(const.NORTHWEST) if 'ast_ne' in level_str: self.dashboard.radar.add_asteroid(const.NORTHEAST) if 'ast_sw' in level_str: self.dashboard.radar.add_asteroid(const.SOUTHWEST) if 'ast_se' in level_str: self.dashboard.radar.add_asteroid(const.SOUTHEAST) if 'repair' in level_str: if 'repair_sn' in level_str: room_id = const.SENSORS elif 'repair_rd' in level_str: room_id = const.RADAR elif 'repair_sh' in level_str: room_id = const.SHIELD # make a module break and require repairs self.start_repair(room_id) if self.state == const.REPAIRING and self.ship.disabled_systems[7]: self.state = const.PLAYING # Check fire if current_time - self.last_f_tick >= self.f_tick_time: self.ship.fire_tick() self.last_f_tick = current_time # Update which systems are disabled self.ship.check_systems() self.dashboard.sensors.disabled = self.ship.disabled_systems[2] self.dashboard.sensors.broken = self.ship.broken_systems[2] self.dashboard.radar.disabled = self.ship.disabled_systems[3] self.dashboard.radar.broken = self.ship.broken_systems[3] self.dashboard.laser_n_disabled = self.ship.disabled_systems[4] self.dashboard.laser_s_disabled = self.ship.disabled_systems[5] self.dashboard.repair_disabled = self.ship.disabled_systems[7] # Checks event goal if self.event_room is not None and self.event_target_flvl == 2 and self.event_room.fire_level == 2: self.event_room.is_event = False self.event_room = None if current_time - self.last_f_anim >= self.f_anim_time: for i in self.ship.room_list: if i.fire_anim_state >= 2: i.fire_anim_state = 0 else: i.fire_anim_state += 1 self.last_f_anim = current_time # Check sprinklers if current_time - self.last_s_tick >= self.s_tick_time: if self.ship.num_sprinkling <= self.dashboard.get_water(): self.ship.sprinkler_tick() self.dashboard.lose_water(self.ship.num_sprinkling) self.last_s_tick = current_time else: self.ship.sprinkler_tick(self.dashboard.get_water()) self.dashboard.lose_water(self.dashboard.get_water()) self.last_s_tick = current_time # Update which systems are disabled self.ship.check_systems() self.dashboard.sensors.disabled = self.ship.disabled_systems[2] self.dashboard.sensors.broken = self.ship.broken_systems[2] self.dashboard.radar.disabled = self.ship.disabled_systems[3] self.dashboard.radar.broken = self.ship.broken_systems[3] self.dashboard.laser_n_disabled = self.ship.disabled_systems[4] self.dashboard.laser_s_disabled = self.ship.disabled_systems[5] self.dashboard.repair_disabled = self.ship.disabled_systems[7] # Checks event goal if self.event_room is not None and self.event_target_flvl == 0 and self.event_room.fire_level <= 1: self.event_room.is_event = False self.event_room = None # Check radar if current_time - self.last_r_tick >= self.r_tick_time: shields_up = self.ship.is_working(const.SHIELD) damage_taken = self.dashboard.radar.radar_tick(shields_up) self.dashboard.take_damage(damage_taken) if damage_taken > 0: self.damage_anim_start = time.time() self.last_r_tick = current_time # Check events if self.event_room is not None and current_time - self.event_start_time >= self.event_time: if self.event_target_flvl == 0 and self.event_room.fire_level == 2 or \ self.event_target_flvl == 2 and self.event_room.fire_level <= 1: self.dashboard.take_damage(3) self.damage_anim_start = time.time() self.event_room.is_event = False self.event_room = None # Check repair events if self.repair_room is not None and current_time - self.repair_start_time >= self.repair_time: self.repair_room.is_broken = True self.repair_room.is_breaking = False self.repair_room = None def draw(self): if self.state == const.MENU: draw_menu(self.surface) else: self.ship.draw() self.dashboard.draw(SPRINKLER_LIMIT - self.ship.num_sprinkling, self.lightyears_left, \ self.state == const.REPAIRING) if self.active_text_box: self.active_text_box.draw(self.surface) # red flash when you take damage current_time = time.time() # from 0 to 0.1, transparency goes from 0 to 100, and then from 0.1 to 0.2 it goes back down if current_time - self.damage_anim_start <= 0.1: transparency = (current_time - self.damage_anim_start) * 1000 else: transparency = (self.damage_anim_start - current_time + 0.2) * 1000 if current_time - self.damage_anim_start <= 0.2: overlay_surface = pygame.Surface((const.WIN_LENGTH, const.WIN_HEIGHT), pygame.HWSURFACE) overlay_surface.set_alpha(transparency) # the surface is now semi-transparent util.bevelled_rect(overlay_surface, (255, 0, 0), (0, 0, const.WIN_LENGTH, const.WIN_HEIGHT), \ 15) self.surface.blit(overlay_surface, (0, 0))