Return to repo list

gatemender

Arcade game about fixing warp gates. LibreJam Dec 2020 Entry.
Return to HMagellan.com

game.py (21194B)


      1 import pygame, os, random
      2 from . import sprites
      3 from .constants import *
      4 
      5 class Game(object):
      6 
      7     def __init__(self):
      8         self.on = True
      9         self.screen = pygame.display.set_mode(SCREEN_DIM)
     10         self.screen_rect = self.screen.get_rect()
     11         self.camera_surface = pygame.Surface((SCREEN_DIM[0], SCREEN_DIM[1] - 96))
     12         self.camera_surface_rect = self.camera_surface.get_rect()
     13         self.camera_surface_rect.topleft = (0, 0)
     14         self.frameclock = pygame.time.Clock()
     15         self.font = pygame.font.Font(FONTPATH, 24)
     16         self.mode = MODES.Play
     17         self.show_info = False
     18         self.just_pressed = False
     19         self.preserve_gate = False
     20 
     21         # Options
     22         self.sound_on = True
     23         self.music_on = True
     24 
     25         # Loading
     26         self.images = { i : pygame.image.load(IMAGE_FILES[i]).convert() for i in IMAGE_FILES }
     27         for i in self.images:
     28             self.images[i].set_colorkey(COLORKEY)
     29         self.keys = { CONTROLS[k] : False for k in CONTROLS }
     30         self.sounds = { s : pygame.mixer.Sound(SOUND_FILES[s]) for s in SOUND_FILES }
     31         self.channels = [ pygame.mixer.Channel(x) for x in range(0, pygame.mixer.get_num_channels() - 1) ]
     32 
     33         # Game objects
     34         self.screen_draw_group = pygame.sprite.Group()
     35         self.camera_draw_group = pygame.sprite.Group()
     36         self.asteroids = pygame.sprite.Group()
     37         self.bullets = pygame.sprite.Group()
     38         self.pickups = pygame.sprite.Group()
     39         self.gate_pieces = pygame.sprite.Group()
     40 
     41         # Others
     42         self.score = 0
     43         self.oldscore = 0
     44         self.lifeup_score = 0
     45         self.level_factor = 1
     46         self.lives = 2
     47         self.transition_timer_max = 200
     48         self.transition_timer = self.transition_timer_max
     49         self.asteroid_spawn_timer = 100
     50         self.gate_defs = []
     51         self.game_over_text = self.font.render("GAME OVER", False, (255, 255, 255))
     52         self.game_over_text_rect = self.game_over_text.get_rect()
     53         self.game_over_text_rect.center = self.screen_rect.center
     54         self.lives_text = self.font.render(str(self.lives), False, (255, 255, 255))
     55         self.level_text = self.font.render("GATE " + str(self.level_factor), False, (255, 255, 255))
     56         self.title = self.images["Title"]
     57         self.title_rect = self.title.get_rect()
     58         self.title_rect.center = (self.screen_rect.center[0], self.screen_rect.center[1] - 100)
     59         self.start_button = self.font.render("START", False, (255, 255, 255), (20, 20, 20))
     60         self.start_button_rect = self.start_button.get_rect()
     61         self.start_button_rect.center = (self.screen_rect.centerx, self.screen_rect.centery + 10)
     62         self.instructions_button = self.font.render("INSTRUCTIONS", False, (255, 255, 255), (20, 20, 20))
     63         self.instructions_button_rect = self.instructions_button.get_rect()
     64         self.instructions_button_rect.center = (self.screen_rect.centerx, self.screen_rect.centery + 50)
     65         self.options_button = self.font.render("OPTIONS", False, (255, 255, 255), (20, 20, 20))
     66         self.options_button_rect = self.options_button.get_rect()
     67         self.options_button_rect.center = (self.screen_rect.centerx, self.screen_rect.centery + 90)
     68         self.music_on_label = self.font.render("ENABLE MUSIC", False, (255, 255, 255), (20, 20, 20))
     69         self.music_on_label_rect = self.music_on_label.get_rect()
     70         self.music_on_label_rect.center = (self.screen_rect.centerx - 200, self.screen_rect.centery - 32)
     71         self.sound_on_label = self.font.render("ENABLE SOUND", False, (255, 255, 255), (20, 20, 20))
     72         self.sound_on_label_rect = self.sound_on_label.get_rect()
     73         self.sound_on_label_rect.center = (self.screen_rect.centerx - 200, self.screen_rect.centery + 32)
     74         self.options_back_button = self.font.render("BACK", False, (255, 255, 255), (20, 20, 20))
     75         self.options_back_button_rect = self.options_back_button.get_rect()
     76         self.options_back_button_rect.center = (self.screen_rect.centerx, self.screen_rect.centery + 180)
     77         self.music_on_checkbox = None
     78         self.sound_on_checkbox = None
     79         self.change_mode(MODES.Menu)
     80 
     81     def clear_level(self):
     82         self.screen_draw_group = pygame.sprite.Group()
     83         self.camera_draw_group = pygame.sprite.Group()
     84         self.asteroids = pygame.sprite.Group()
     85         self.bullets = pygame.sprite.Group()
     86         self.pickups = pygame.sprite.Group()
     87         self.ship = None
     88         self.vortex = None
     89         self.tray = None
     90         self.alpha_icon = None
     91         self.beta_icon = None
     92         self.gamma_icon = None
     93         self.num_alpha = None
     94         self.num_beta = None
     95         self.num_gamma = None
     96         self.hud_lives_icon = None
     97         self.num_lives = None
     98         self.hp_heading = None
     99         self.burst_heading = None
    100         self.hp_meter = None
    101         self.hp_meter_fill = None
    102         self.burst_meter = None
    103         self.burst_meter_fill = None
    104         self.score_surface = None
    105         self.asteroid_spawn_timer = 100
    106 
    107     def load_level(self):
    108 
    109         # First, restore to default
    110         self.clear_level()
    111         if not self.preserve_gate:
    112 
    113             # Definitions
    114             self.gate_defs = [ 
    115                 [ bool(random.getrandbits(1)), MATERIALS(random.randint(1, 3)), 0, (416, 200), "Angle", None ],
    116                 [ bool(random.getrandbits(1)), MATERIALS(random.randint(1, 3)), 0, (480, 200), "Straight", None ],
    117                 [ bool(random.getrandbits(1)), MATERIALS(random.randint(1, 3)), 270, (544, 200), "Angle", None ],
    118                 [ bool(random.getrandbits(1)), MATERIALS(random.randint(1, 3)), 270, (544, 264), "Straight", None ],
    119                 [ bool(random.getrandbits(1)), MATERIALS(random.randint(1, 3)), 180, (544, 328), "Angle", None ],
    120                 [ bool(random.getrandbits(1)), MATERIALS(random.randint(1, 3)), 180, (480, 328), "Straight", None ],
    121                 [ bool(random.getrandbits(1)), MATERIALS(random.randint(1, 3)), 90, (416, 328), "Angle", None ],
    122                 [ bool(random.getrandbits(1)), MATERIALS(random.randint(1, 3)), 90, (416, 264), "Straight", None ],
    123             ]
    124             self.gate_pieces = pygame.sprite.Group()
    125 
    126             # Next, load level
    127             broke = min(self.level_factor + 1, 5)
    128             bc = 0
    129             for i in self.gate_defs:
    130                 if i[0]:
    131                     if bc < broke:
    132                         bc += 1
    133                     else:
    134                         i[0] = False
    135             while bc < broke:
    136                 ind = random.randint(0, 7)
    137                 if not self.gate_defs[ind][0]:
    138                     self.gate_defs[ind][0] = True
    139                     bc += 1
    140         else:
    141             self.preserve_gate = False
    142             self.gate_defs = [ [ g.broken, g.requirement, g.angle, g.rect.center, g.or_name, g.needed ] for g in self.gate_pieces ]
    143             self.gate_pieces = pygame.sprite.Group()
    144 
    145         x = 0
    146         for n in self.gate_defs:
    147             if not n[0]:
    148                 bstr = "Fixed"
    149             else:
    150                 bstr = "Broken"
    151             gp = sprites.GatePiece(self, self.images["Gate_" + n[4] + "_" + bstr], n[3], n[2], n[4], n[0], n[1], n[5])
    152             self.gate_pieces.add(gp)
    153             self.camera_draw_group.add(gp)
    154             x += 1
    155 
    156         self.ship = sprites.Ship(self, self.images["Ship"], self.screen_rect.center)
    157         self.camera_draw_group.add(self.ship)
    158         self.tray = sprites.CustomSprite(self, self.images["UI_Tray"], (self.screen_rect.centerx, 700))
    159         self.screen_draw_group.add(self.tray)
    160         self.vortex = sprites.Vortex(self, self.images["Vortex"], (480, 264))
    161         self.camera_draw_group.add(self.vortex)
    162 
    163         self.alpha_icon = sprites.CustomSprite(self, self.images["Big_Alpha_Icon"], (410, 710))
    164         self.beta_icon = sprites.CustomSprite(self, self.images["Big_Beta_Icon"], (490, 710))
    165         self.gamma_icon = sprites.CustomSprite(self, self.images["Big_Gamma_Icon"], (570, 710))
    166         self.hud_lives_icon = sprites.CustomSprite(self, self.images["Ship"], (100, 720))
    167         self.screen_draw_group.add(self.alpha_icon, self.beta_icon, self.gamma_icon, self.hud_lives_icon)
    168 
    169     def change_mode(self, mode):
    170         oldmode = self.mode
    171         self.mode = mode
    172         if mode == MODES.Play:
    173             self.load_level()
    174             self.play_sound("Game_Music", 0)
    175             self.hp_heading = self.font.render("HP", False, (255, 255, 255))
    176             self.hp_heading_rect = self.hp_heading.get_rect()
    177             self.hp_heading_rect.center = (41, 720)
    178             self.burst_heading = self.font.render("BURST", False, (0, 255, 255))
    179             self.burst_heading_rect = self.burst_heading.get_rect()
    180             self.burst_heading_rect.center = (240, 720)
    181             self.hp_meter = pygame.Surface((32, 80))
    182             self.hp_meter.fill((135, 0, 22))
    183             self.hp_meter_rect = self.hp_meter.get_rect()
    184             self.hp_meter_rect.center = (40, 720)
    185             self.burst_meter = pygame.Surface((100, 32))
    186             self.burst_meter.fill((135, 0, 22))
    187             self.burst_meter_rect = self.burst_meter.get_rect()
    188             self.burst_meter_rect.center = (236, 720)
    189         elif mode == MODES.Transition:
    190             pygame.mixer.stop()
    191             self.level_text = self.font.render("GATE " + str(self.level_factor), False, (255, 255, 255))
    192             self.transition_timer = self.transition_timer_max
    193         elif mode == MODES.GameOver:
    194             pygame.mixer.stop()
    195             self.transition_timer = self.transition_timer_max
    196         elif mode == MODES.Menu:
    197             if oldmode != MODES.Options:
    198                 self.play_sound("Main_Menu_Music", 0)
    199             self.lives = 2
    200             self.score = 0
    201             self.oldscore = 0
    202             self.lifeup_score = 0
    203             self.level_factor = 1
    204             self.preserve_gate = False
    205         elif mode == MODES.Options:
    206             self.music_on_checkbox = sprites.Checkbox(self, self.images["Checked_Checkbox"], (self.screen_rect.centerx + 140, self.screen_rect.centery - 32), 0, self.music_on)
    207             self.sound_on_checkbox = sprites.Checkbox(self, self.images["Checked_Checkbox"], (self.screen_rect.centerx + 140, self.screen_rect.centery + 32), 0, self.sound_on)
    208 
    209     def play_sound(self, soundname, channel = None, concurrent = True, loops = -1, is_music = True):
    210         if (is_music and self.music_on) or (not is_music and self.sound_on):
    211             if channel != None and not self.channels[channel].get_busy():
    212                 self.channels[channel].play(self.sounds[soundname], loops)
    213             elif concurrent or self.sounds[soundname].get_num_channels() == 0:
    214                 self.sounds[soundname].play(loops)
    215 
    216     def render_hud(self):
    217         if self.mode == MODES.Play:
    218             self.num_lives = self.font.render("X " + str(self.lives), False, (255, 255, 255))
    219             self.num_lives_rect = self.num_lives.get_rect()
    220             self.num_lives_rect.center = (150, 720)
    221             self.num_alpha = self.font.render(str(self.ship.materials[MATERIALS.Alpha]), False, (255, 255, 255))
    222             self.num_alpha_rect = self.num_alpha.get_rect()
    223             self.num_alpha_rect.center = (440, 710)
    224             self.num_beta = self.font.render(str(self.ship.materials[MATERIALS.Beta]), False, (255, 255, 255))
    225             self.num_beta_rect = self.num_beta.get_rect()
    226             self.num_beta_rect.center = (520, 710)
    227             self.num_gamma = self.font.render(str(self.ship.materials[MATERIALS.Gamma]), False, (255, 255, 255))
    228             self.num_gamma_rect = self.num_gamma.get_rect()
    229             self.num_gamma_rect.center = (600, 710)
    230             self.hp_meter_fill = pygame.Surface((32, round((self.ship.hp / 3) * 80)))
    231             self.hp_meter_fill.fill((0, 200, 0))
    232             self.hp_meter_fill_rect = self.hp_meter_fill.get_rect()
    233             self.hp_meter_fill_rect.bottom = self.hp_meter_rect.bottom - 1
    234             self.hp_meter_fill_rect.centerx = self.hp_meter_rect.centerx
    235             if self.ship.burst_counter == 100:
    236                 burst_fill_colo = (255, 255, 255)
    237             else:
    238                 burst_fill_colo = (0, 0, 200)
    239             self.burst_meter_fill = pygame.Surface((self.ship.burst_counter, 32))
    240             self.burst_meter_fill.fill(burst_fill_colo)
    241             self.burst_meter_fill_rect = self.burst_meter_fill.get_rect()
    242             self.burst_meter_fill_rect.bottom = self.burst_meter_rect.bottom - 1
    243             self.burst_meter_fill_rect.centerx = self.burst_meter_rect.centerx
    244             self.score_surface = self.font.render("SCORE: " + str(self.score), False, (255, 255, 255))
    245             self.score_surface_rect = self.score_surface.get_rect()
    246             self.score_surface_rect.topleft = (740, 700)
    247 
    248     def shift_frames(self):
    249         self.frameclock.tick(FRAMERATE)
    250 
    251     def handle_events(self):
    252         for event in pygame.event.get():
    253             if event.type == pygame.QUIT:
    254                 self.on = False
    255             elif event.type == pygame.KEYDOWN:
    256                 if event.key in self.keys.keys():
    257                     self.keys[event.key] = True
    258             elif event.type == pygame.KEYUP:
    259                 if event.key in self.keys.keys():
    260                     self.keys[event.key] = False
    261 
    262     def handle_input(self):
    263         if self.mode == MODES.Play:
    264             if self.ship != None and not self.ship.dead:
    265                 self.ship.turning_left = self.keys[CONTROLS["TURN_LEFT_KEY"]]
    266                 self.ship.turning_right = self.keys[CONTROLS["TURN_RIGHT_KEY"]]
    267                 self.ship.braking = self.keys[CONTROLS["BRAKE_KEY"]]
    268                 self.ship.shooting = self.keys[CONTROLS["SHOOT_KEY"]]
    269                 self.ship.thrusting = self.keys[CONTROLS["THRUST_KEY"]]
    270                 self.ship.fixing = self.keys[CONTROLS["REPAIR_KEY"]]
    271                 self.ship.bursting = self.keys[CONTROLS["BURST_KEY"]]
    272         elif self.mode == MODES.Menu:
    273             if self.keys[CONTROLS["CONFIRM_KEY"]] or self.keys[CONTROLS["SHOOT_KEY"]]:
    274                 self.change_mode(MODES.Transition)
    275             if pygame.mouse.get_pressed()[0]:
    276                 mousepos = pygame.mouse.get_pos()
    277                 if not self.show_info:
    278                     if self.start_button_rect.collidepoint(mousepos):
    279                         self.change_mode(MODES.Transition)
    280                     elif self.options_button_rect.collidepoint(mousepos):
    281                         self.change_mode(MODES.Options)
    282                     elif self.instructions_button_rect.collidepoint(mousepos):
    283                         self.show_info = True
    284                         self.just_pressed = True
    285                 elif not self.just_pressed:
    286                     self.show_info = False
    287             else:
    288                 self.just_pressed = False
    289         elif self.mode == MODES.Options:
    290             if pygame.mouse.get_pressed()[0]:
    291                 mousepos = pygame.mouse.get_pos()
    292                 if self.music_on_checkbox.rect.collidepoint(mousepos) and self.music_on_checkbox.check_cooldown == 0:
    293                     self.music_on_checkbox.be_checked()
    294                     if self.music_on_checkbox.checked:
    295                         self.music_on = True
    296                         self.play_sound("Main_Menu_Music", 0)
    297                     else:
    298                         self.music_on = False
    299                         self.channels[0].stop()
    300                 elif self.sound_on_checkbox.rect.collidepoint(mousepos) and self.sound_on_checkbox.check_cooldown == 0:
    301                     self.sound_on_checkbox.be_checked()
    302                     if self.sound_on_checkbox.checked:
    303                         self.sound_on = True
    304                         self.play_sound("Click", 1, True, 0, False)
    305                     else:
    306                         self.sound_on = False
    307                         self.channels[1].stop()
    308                 elif self.options_back_button_rect.collidepoint(mousepos):
    309                     self.change_mode(MODES.Menu)
    310 
    311     def update_screen(self):
    312         if self.mode == MODES.Play:
    313             self.screen.fill((44, 44, 44))
    314             self.camera_surface.blit(self.images["Space_Background"], (0, 0))
    315             self.camera_draw_group.update(self.camera_surface)
    316             self.render_hud()
    317             self.screen.blit(self.camera_surface, self.camera_surface_rect)
    318             self.screen_draw_group.update(self.screen)
    319             self.screen.blit(self.num_alpha, self.num_alpha_rect)
    320             self.screen.blit(self.num_beta, self.num_beta_rect)
    321             self.screen.blit(self.num_gamma, self.num_gamma_rect)
    322             self.screen.blit(self.num_lives, self.num_lives_rect)
    323             self.screen.blit(self.hp_meter, self.hp_meter_rect)
    324             self.screen.blit(self.hp_meter_fill, self.hp_meter_fill_rect)
    325             self.screen.blit(self.hp_heading, self.hp_heading_rect)
    326             self.screen.blit(self.burst_meter, self.burst_meter_rect)
    327             self.screen.blit(self.burst_meter_fill, self.burst_meter_fill_rect)
    328             self.screen.blit(self.burst_heading, self.burst_heading_rect)
    329             self.screen.blit(self.score_surface, self.score_surface_rect)
    330         elif self.mode == MODES.Menu:
    331             if not self.show_info:
    332                 self.screen.fill((0, 0, 0))
    333                 self.screen.blit(self.title, self.title_rect)
    334                 self.screen.blit(self.start_button, self.start_button_rect)
    335                 self.screen.blit(self.instructions_button, self.instructions_button_rect)
    336                 self.screen.blit(self.options_button, self.options_button_rect)
    337             else:
    338                 self.screen.blit(self.images["Info"], (0, 0))
    339         elif self.mode == MODES.Transition:
    340             self.lives_text = self.font.render("X " + str(self.lives), False, (255, 255, 255))
    341             self.screen.fill((0, 0, 0))
    342             self.screen.blit(self.images["Ship"], (self.screen_rect.centerx - 54, 280))
    343             self.screen.blit(self.lives_text, (self.screen_rect.centerx - 10, 280))
    344             self.screen.blit(self.level_text, (self.screen_rect.centerx - 50, 380))
    345         elif self.mode == MODES.GameOver:
    346             self.screen.fill((0, 0, 0))
    347             self.screen.blit(self.game_over_text, self.game_over_text_rect)
    348         elif self.mode == MODES.Options:
    349             self.screen.fill((0, 0, 0))
    350             self.screen.blit(self.music_on_label, self.music_on_label_rect)
    351             self.screen.blit(self.sound_on_label, self.sound_on_label_rect)
    352             self.screen.blit(self.options_back_button, self.options_back_button_rect)
    353             self.music_on_checkbox.update(self.screen)
    354             self.sound_on_checkbox.update(self.screen)
    355         pygame.display.flip()
    356 
    357     def update_logic(self):
    358         # Non-modal logic
    359         self.lifeup_score += self.score - self.oldscore
    360         self.oldscore = self.score
    361         if self.lifeup_score >= 10000:
    362             self.lifeup_score -= 10000
    363             self.lives += 1
    364             self.play_sound("Complete", 0, True, 0, False)
    365         # Modal
    366         if self.mode == MODES.Play:
    367             self.spawn_asteroids()
    368             self.check_gate_complete()
    369         elif self.mode == MODES.Transition:
    370             if self.transition_timer > 0:
    371                 self.transition_timer -= 1
    372             else:
    373                 self.change_mode(MODES.Play)
    374         elif self.mode == MODES.GameOver:
    375             if self.transition_timer > 0:
    376                 self.transition_timer -= 1
    377             else:
    378                 HIGHSCORES["SCORES"].append(self.score)
    379                 HIGHSCORES["SCORES"].sort()
    380                 self.change_mode(MODES.Menu)
    381 
    382     def spawn_asteroids(self):
    383         if self.asteroid_spawn_timer <= 0:
    384             size = random.randint(0, 2)
    385             if size == 0:
    386                 im = self.images["Small_Asteroid"]
    387             elif size == 1:
    388                 im = self.images["Medium_Asteroid"]
    389             else:
    390                 im = self.images["Big_Asteroid"]
    391             spawnpos = (random.randint(0, SCREEN_DIM[1]), -65)
    392             a = sprites.Asteroid(self, im, spawnpos, 270, size)
    393             self.camera_draw_group.add(a)
    394             self.asteroids.add(a)
    395             self.asteroid_spawn_timer = 10 * (10 // self.level_factor)
    396         else:
    397             self.asteroid_spawn_timer -= 1
    398 
    399     def check_gate_complete(self):
    400         if not self.vortex.visible:
    401             for g in self.gate_pieces.sprites():
    402                 if g.broken:
    403                     return
    404             self.play_sound("Complete", 0, True, 0, False)
    405             self.vortex.visible = True
    406             self.vortex.passthru = True
    407 
    408     def increment_level(self):
    409         for m in self.ship.materials:
    410             self.score += self.ship.materials[m] * 100 * self.level_factor
    411         self.level_factor += 1
    412         self.change_mode(MODES.Transition)
    413     
    414     def run(self):
    415         while self.on:
    416             self.shift_frames()
    417             self.handle_events()
    418             self.handle_input()
    419             self.update_logic()
    420             self.update_screen()
    421 
    422         # Write highscores and quit
    423         with open("hs.json", "w") as j: json.dump(HIGHSCORES, j)
    424         pygame.quit()