Is the bubble Hall of version Q in your memory like this! Python implementation of simple Q version bubble hall games!!!

Posted by natman3 on Mon, 17 Jan 2022 23:45:14 +0100

Related documents

Pay attention to Xiaobian and receive private letter Xiaobian!
Of course, don't forget a triple~~

Yes, we can pay attention to the official account of Xiaobian yo ~ ~
Python log

development environment

Python version: 3.6.4
Related modules:
pygame module;
And some Python built-in modules.

Environment construction

Install Python and add it to the environment variable. pip can install the relevant modules required.

Principle introduction

Rules of the game:

The player controls the character Zelda (green) action through the ↑↓←→ key. When the player presses the space bar, he can place a bomb at the current position. Other characters (dk and batman) are randomly controlled by computers. When all characters are burned by the flame generated by the bomb (including their own bombs), they will lose a certain amount of life; When all characters eat fruit, they can restore a certain amount of health. In addition, the wall can prevent the further spread of the flame generated by the bomb.
When our character zelda's HP is 0, the game fails; When the health value of all characters on the computer side is 0, the game wins and enters the next level.
Step by step:
First, let's clarify which game sprites are included in the game:

  • Bomb class
  • Role class
  • Wall class
  • Background class
  • Fruits

The wall class and background class are well defined. You only need to import the picture and bind the picture to the specified location:

'''Wall class'''
class Wall(pygame.sprite.Sprite):
  def __init__(self, imagepath, coordinate, blocksize, **kwargs):
    pygame.sprite.Sprite.__init__(self)
    self.image = pygame.image.load(imagepath)
    self.image = pygame.transform.scale(self.image, (blocksize, blocksize))
    self.rect = self.image.get_rect()
    self.rect.left, self.rect.top = coordinate[0] * blocksize, coordinate[1] * blocksize
    self.coordinate = coordinate
    self.blocksize = blocksize
  '''Draw to screen'''
  def draw(self, screen):
    screen.blit(self.image, self.rect)
    return True


'''Background class'''
class Background(pygame.sprite.Sprite):
  def __init__(self, imagepath, coordinate, blocksize, **kwargs):
    pygame.sprite.Sprite.__init__(self)
    self.image = pygame.image.load(imagepath)
    self.image = pygame.transform.scale(self.image, (blocksize, blocksize))
    self.rect = self.image.get_rect()
    self.rect.left, self.rect.top = coordinate[0] * blocksize, coordinate[1] * blocksize
    self.coordinate = coordinate
    self.blocksize = blocksize
  '''Draw to screen'''
  def draw(self, screen):
    screen.blit(self.image, self.rect)
    return True

In fact, the definition of fruits is similar, but different fruits can help characters restore different values of health:

'''Fruits'''
class Fruit(pygame.sprite.Sprite):
  def __init__(self, imagepath, coordinate, blocksize, **kwargs):
    pygame.sprite.Sprite.__init__(self)
    self.kind = imagepath.split('/')[-1].split('.')[0]
    if self.kind == 'banana':
      self.value = 5
    elif self.kind == 'cherry':
      self.value = 10
    else:
      raise ValueError('Unknow fruit <%s>...' % self.kind)
    self.image = pygame.image.load(imagepath)
    self.image = pygame.transform.scale(self.image, (blocksize, blocksize))
    self.rect = self.image.get_rect()
    self.rect.left, self.rect.top = coordinate[0] * blocksize, coordinate[1] * blocksize
    self.coordinate = coordinate
    self.blocksize = blocksize
  '''Draw to screen'''
  def draw(self, screen):
    screen.blit(self.image, self.rect)
    return True

The definition of bomb class and character class is a little more complicated. Characters need to move up, down, left and right according to the instructions of the player or computer. At the same time, they can generate bombs in their own position and restore a certain amount of health after eating fruit:

'''Role class'''
class Hero(pygame.sprite.Sprite):
  def __init__(self, imagepaths, coordinate, blocksize, map_parser, **kwargs):
    pygame.sprite.Sprite.__init__(self)
    self.imagepaths = imagepaths
    self.image = pygame.image.load(imagepaths[-1])
    self.image = pygame.transform.scale(self.image, (blocksize, blocksize))
    self.rect = self.image.get_rect()
    self.rect.left, self.rect.top = coordinate[0] * blocksize, coordinate[1] * blocksize
    self.coordinate = coordinate
    self.blocksize = blocksize
    self.map_parser = map_parser
    self.hero_name = kwargs.get('hero_name')
    # Life value
    self.health_value = 50
    # Bomb cooldown
    self.bomb_cooling_time = 5000
    self.bomb_cooling_count = 0
    # Random movement cooling time (for AI computers only)
    self.randommove_cooling_time = 100
    self.randommove_cooling_count = 0
  '''Character movement'''
  def move(self, direction):
    self.__updateImage(direction)
    if direction == 'left':
      if self.coordinate[0]-1 < 0 or self.map_parser.getElemByCoordinate([self.coordinate[0]-1, self.coordinate[1]]) in ['w', 'x', 'z']:
        return False
      self.coordinate[0] = self.coordinate[0] - 1
    elif direction == 'right':
      if self.coordinate[0]+1 >= self.map_parser.width or self.map_parser.getElemByCoordinate([self.coordinate[0]+1, self.coordinate[1]]) in ['w', 'x', 'z']:
        return False
      self.coordinate[0] = self.coordinate[0] + 1
    elif direction == 'up':
      if self.coordinate[1]-1 < 0 or self.map_parser.getElemByCoordinate([self.coordinate[0], self.coordinate[1]-1]) in ['w', 'x', 'z']:
        return False
      self.coordinate[1] = self.coordinate[1] - 1
    elif direction == 'down':
      if self.coordinate[1]+1 >= self.map_parser.height or self.map_parser.getElemByCoordinate([self.coordinate[0], self.coordinate[1]+1]) in ['w', 'x', 'z']:
        return False
      self.coordinate[1] = self.coordinate[1] + 1
    else:
      raise ValueError('Unknow direction <%s>...' % direction)
    self.rect.left, self.rect.top = self.coordinate[0] * self.blocksize, self.coordinate[1] * self.blocksize
    return True
  '''Random action(AI Computer use)'''
  def randomAction(self, dt):
    # Cooling down countdown
    if self.randommove_cooling_count > 0:
      self.randommove_cooling_count -= dt
    action = random.choice(['left', 'left', 'right', 'right', 'up', 'up', 'down', 'down', 'dropbomb'])
    flag = False
    if action in ['left', 'right', 'up', 'down']:
      if self.randommove_cooling_count <= 0:
        flag = True
        self.move(action)
        self.randommove_cooling_count = self.randommove_cooling_time
    elif action in ['dropbomb']:
      if self.bomb_cooling_count <= 0:
        flag = True
        self.bomb_cooling_count = self.bomb_cooling_time
    return action, flag
  '''Generate bomb'''
  def generateBomb(self, imagepath, digitalcolor, explode_imagepath):
    return Bomb(imagepath=imagepath, coordinate=copy.deepcopy(self.coordinate), blocksize=self.blocksize, digitalcolor=digitalcolor, explode_imagepath=explode_imagepath)
  '''Draw to screen'''
  def draw(self, screen, dt):
    # Cooling down countdown
    if self.bomb_cooling_count > 0:
      self.bomb_cooling_count -= dt
    screen.blit(self.image, self.rect)
    return True
  '''eat fruit'''
  def eatFruit(self, fruit_sprite_group):
    eaten_fruit = pygame.sprite.spritecollide(self, fruit_sprite_group, True, None)
    for fruit in eaten_fruit:
      self.health_value += fruit.value
  '''Update character orientation'''
  def __updateImage(self, direction):
    directions = ['left', 'right', 'up', 'down']
    idx = directions.index(direction)
    self.image = pygame.image.load(self.imagepaths[idx])
    self.image = pygame.transform.scale(self.image, (self.blocksize, self.blocksize))

Bombs need to have a countdown prompt function, and generate flame effects within the killing range of bombs after the countdown (poor, it is estimated that the special effects T_T worth only 10 cents, please bear the burden):

'''Bomb class'''
class Bomb(pygame.sprite.Sprite):
  def __init__(self, imagepath, coordinate, blocksize, digitalcolor, explode_imagepath, **kwargs):
    pygame.sprite.Sprite.__init__(self)
    self.image = pygame.image.load(imagepath)
    self.image = pygame.transform.scale(self.image, (blocksize, blocksize))
    self.explode_imagepath = explode_imagepath
    self.rect = self.image.get_rect()
    # Pixel position
    self.rect.left, self.rect.top = coordinate[0] * blocksize, coordinate[1] * blocksize
    # Coordinates (unit length of element block)
    self.coordinate = coordinate
    self.blocksize = blocksize
    # Explosion countdown
    self.explode_millisecond = 6000 * 1 - 1
    self.explode_second = int(self.explode_millisecond / 1000)
    self.start_explode = False
    # Explosion duration
    self.exploding_count = 1000 * 1
    # Bomb damage ability
    self.harm_value = 1
    # Does the bomb still exist
    self.is_being = True
    self.font = pygame.font.SysFont('Consolas', 20)
    self.digitalcolor = digitalcolor
  '''Draw to screen'''
  def draw(self, screen, dt, map_parser):
    if not self.start_explode:
      # Explosion countdown
      self.explode_millisecond -= dt
      self.explode_second = int(self.explode_millisecond / 1000)
      if self.explode_millisecond < 0:
        self.start_explode = True
      screen.blit(self.image, self.rect)
      text = self.font.render(str(self.explode_second), True, self.digitalcolor)
      rect = text.get_rect(center=(self.rect.centerx-5, self.rect.centery+5))
      screen.blit(text, rect)
      return False
    else:
      # Continuous countdown to explosion
      self.exploding_count -= dt
      if self.exploding_count > 0:
        return self.__explode(screen, map_parser)
      else:
        self.is_being = False
        return False
  '''Explosion effect'''
  def __explode(self, screen, map_parser):
    explode_area = self.__calcExplodeArea(map_parser.instances_list)
    for each in explode_area:
      image = pygame.image.load(self.explode_imagepath)
      image = pygame.transform.scale(image, (self.blocksize, self.blocksize))
      rect = image.get_rect()
      rect.left, rect.top = each[0] * self.blocksize, each[1] * self.blocksize
      screen.blit(image, rect)
    return explode_area
  '''Calculate explosion area'''
  def __calcExplodeArea(self, instances_list):
    explode_area = []
    # The area calculation rule is that the wall can prevent the explosion from spreading, and the explosion range is only within the range of the game map
    for ymin in range(self.coordinate[1], self.coordinate[1]-5, -1):
      if ymin < 0 or instances_list[ymin][self.coordinate[0]] in ['w', 'x', 'z']:
        break
      explode_area.append([self.coordinate[0], ymin])
    for ymax in range(self.coordinate[1]+1, self.coordinate[1]+5):
      if ymax >= len(instances_list) or instances_list[ymax][self.coordinate[0]] in ['w', 'x', 'z']:
        break
      explode_area.append([self.coordinate[0], ymax])
    for xmin in range(self.coordinate[0], self.coordinate[0]-5, -1):
      if xmin < 0 or instances_list[self.coordinate[1]][xmin] in ['w', 'x', 'z']:
        break
      explode_area.append([xmin, self.coordinate[1]])
    for xmax in range(self.coordinate[0]+1, self.coordinate[0]+5):
      if xmax >= len(instances_list[0]) or instances_list[self.coordinate[1]][xmax] in ['w', 'x', 'z']:
        break
      explode_area.append([xmax, self.coordinate[1]])
    return explode_area

Because the bomb class and character class are bound to the game screen every frame, some countdown operations are written into the draw function. Of course, it is best to rewrite a function to realize this function, so the code structure will look clearer.

Next, we're in Design our game map in the map file:

Then it is parsed through a map parsing class Map file, so that you only need to import a new one each time you switch levels The map file is OK, which is also convenient for subsequent expansion of the game:

'''.map file parser '''
class mapParser():
  def __init__(self, mapfilepath, bg_paths, wall_paths, blocksize, **kwargs):
    self.instances_list = self.__parse(mapfilepath)
    self.bg_paths = bg_paths
    self.wall_paths = wall_paths
    self.blocksize = blocksize
    self.height = len(self.instances_list)
    self.width = len(self.instances_list[0])
    self.screen_size = (blocksize * self.width, blocksize * self.height)
  '''Draw the map on the screen'''
  def draw(self, screen):
    for j in range(self.height):
      for i in range(self.width):
        instance = self.instances_list[j][i]
        if instance == 'w':
          elem = Wall(self.wall_paths[0], [i, j], self.blocksize)
        elif instance == 'x':
          elem = Wall(self.wall_paths[1], [i, j], self.blocksize)
        elif instance == 'z':
          elem = Wall(self.wall_paths[2], [i, j], self.blocksize)
        elif instance == '0':
          elem = Background(self.bg_paths[0], [i, j], self.blocksize)
        elif instance == '1':
          elem = Background(self.bg_paths[1], [i, j], self.blocksize)
        elif instance == '2':
          elem = Background(self.bg_paths[2], [i, j], self.blocksize)
        else:
          raise ValueError('instance parse error in mapParser.draw...')
        elem.draw(screen)
  '''Random acquisition of an open space'''
  def randomGetSpace(self, used_spaces=None):
    while True:
      i = random.randint(0, self.width-1)
      j = random.randint(0, self.height-1)
      coordinate = [i, j]
      if used_spaces and coordinate in used_spaces:
        continue
      instance = self.instances_list[j][i]
      if instance in ['0', '1', '2']:
        break
    return coordinate
  '''Get element type from coordinates'''
  def getElemByCoordinate(self, coordinate):
    return self.instances_list[coordinate[1]][coordinate[0]]
  '''analysis.map file'''
  def __parse(self, mapfilepath):
    instances_list = []
    with open(mapfilepath) as f:
      for line in f.readlines():
        instances_line_list = []
        for c in line:
          if c in ['w', 'x', 'z', '0', '1', '2']:
            instances_line_list.append(c)
        instances_list.append(instances_line_list)
    return instances_list

OK, after these preparations, you can start to write the main cycle of the game:

'''Game main program'''
def main(cfg):
  # initialization
  pygame.init()
  pygame.mixer.init()
  pygame.mixer.music.load(cfg.BGMPATH)
  pygame.mixer.music.play(-1, 0.0)
  screen = pygame.display.set_mode(cfg.SCREENSIZE)
  pygame.display.set_caption('Q Version bubble Hall - more daring')
  # Start interface
  Interface(screen, cfg, mode='game_start')
  # Game main loop
  font = pygame.font.SysFont('Consolas', 15)
  for gamemap_path in cfg.GAMEMAPPATHS:
    # -Maps
    map_parser = mapParser(gamemap_path, bg_paths=cfg.BACKGROUNDPATHS, wall_paths=cfg.WALLPATHS, blocksize=cfg.BLOCKSIZE)
    # -Fruit
    fruit_sprite_group = pygame.sprite.Group()
    used_spaces = []
    for i in range(5):
      coordinate = map_parser.randomGetSpace(used_spaces)
      used_spaces.append(coordinate)
      fruit_sprite_group.add(Fruit(random.choice(cfg.FRUITPATHS), coordinate=coordinate, blocksize=cfg.BLOCKSIZE))
    # -Our Hero
    coordinate = map_parser.randomGetSpace(used_spaces)
    used_spaces.append(coordinate)
    ourhero = Hero(imagepaths=cfg.HEROZELDAPATHS, coordinate=coordinate, blocksize=cfg.BLOCKSIZE, map_parser=map_parser, hero_name='ZELDA')
    # -Computer Hero
    aihero_sprite_group = pygame.sprite.Group()
    coordinate = map_parser.randomGetSpace(used_spaces)
    aihero_sprite_group.add(Hero(imagepaths=cfg.HEROBATMANPATHS, coordinate=coordinate, blocksize=cfg.BLOCKSIZE, map_parser=map_parser, hero_name='BATMAN'))
    used_spaces.append(coordinate)
    coordinate = map_parser.randomGetSpace(used_spaces)
    aihero_sprite_group.add(Hero(imagepaths=cfg.HERODKPATHS, coordinate=coordinate, blocksize=cfg.BLOCKSIZE, map_parser=map_parser, hero_name='DK'))
    used_spaces.append(coordinate)
    # -bomb
    bomb_sprite_group = pygame.sprite.Group()
    # -flag used to judge the victory or failure of the game
    is_win_flag = False
    # -Main cycle
    screen = pygame.display.set_mode(map_parser.screen_size)
    clock = pygame.time.Clock()
    while True:
      dt = clock.tick(cfg.FPS)
      for event in pygame.event.get():
        if event.type == pygame.QUIT:
          pygame.quit()
          sys.exit(-1)
        # --↑↓←→ key to control up, down, left and right, space bar to drop bomb
        elif event.type == pygame.KEYDOWN:
          if event.key == pygame.K_UP:
            ourhero.move('up')
          elif event.key == pygame.K_DOWN:
            ourhero.move('down')
          elif event.key == pygame.K_LEFT:
            ourhero.move('left')
          elif event.key == pygame.K_RIGHT:
            ourhero.move('right')
          elif event.key == pygame.K_SPACE:
            if ourhero.bomb_cooling_count <= 0:
              bomb_sprite_group.add(ourhero.generateBomb(imagepath=cfg.BOMBPATH, digitalcolor=cfg.YELLOW, explode_imagepath=cfg.FIREPATH))
      screen.fill(cfg.WHITE)
      # --Computer Hero random action
      for hero in aihero_sprite_group:
        action, flag = hero.randomAction(dt)
        if flag and action == 'dropbomb':
          bomb_sprite_group.add(hero.generateBomb(imagepath=cfg.BOMBPATH, digitalcolor=cfg.YELLOW, explode_imagepath=cfg.FIREPATH))
      # --Eat fruit and add HP (as long as Hero can add)
      ourhero.eatFruit(fruit_sprite_group)
      for hero in aihero_sprite_group:
        hero.eatFruit(fruit_sprite_group)
      # --Game elements are bound to the screen
      map_parser.draw(screen)
      for bomb in bomb_sprite_group:
        if not bomb.is_being:
          bomb_sprite_group.remove(bomb)
        explode_area = bomb.draw(screen, dt, map_parser)
        if explode_area:
          # --Hero's health within the explosion flame range will continue to decrease
          if ourhero.coordinate in explode_area:
            ourhero.health_value -= bomb.harm_value
          for hero in aihero_sprite_group:
            if hero.coordinate in explode_area:
              hero.health_value -= bomb.harm_value
      fruit_sprite_group.draw(screen)
      for hero in aihero_sprite_group:
        hero.draw(screen, dt)
      ourhero.draw(screen, dt)
      # --The upper left corner displays health
      pos_x = showText(screen, font, text=ourhero.hero_name+'(our):'+str(ourhero.health_value), color=cfg.YELLOW, position=[5, 5])
      for hero in aihero_sprite_group:
        pos_x, pos_y = pos_x+15, 5
        pos_x = showText(screen, font, text=hero.hero_name+'(ai):'+str(hero.health_value), color=cfg.YELLOW, position=[pos_x, pos_y])
      # --If our player's HP is less than or equal to 0 / the computer player's HP is less than or equal to 0, the game is judged to be over
      if ourhero.health_value <= 0:
        is_win_flag = False
        break
      for hero in aihero_sprite_group:
        if hero.health_value <= 0:
          aihero_sprite_group.remove(hero)
      if len(aihero_sprite_group) == 0:
        is_win_flag = True
        break
      pygame.display.update()
      clock.tick(cfg.FPS)
    if is_win_flag:
      Interface(screen, cfg, mode='game_switch')
    else:
      break
  Interface(screen, cfg, mode='game_end')

Effect display

Start interface

Game interface

The logic is very simple, that is, after initialization, import the level map to start the game. After the end of a level, judge whether the game wins or fails. If the game wins, enter the next level, otherwise exit the main cycle and let the player choose whether to restart the game. I can understand the specific details by looking at the code. I've added all the necessary comments.

Many children always encounter some problems and bottlenecks when learning python. They have no sense of direction and don't know where to start to improve. I have sorted out some materials and hope to help them. You can add Python learning and communication skirt: 773162165

Well, today's game is for everyone here, Amway, what do not understand, you can comment below, you need to find the source code can be found Xiaobian yo, remember to pay attention to Xiaobian official account ha.

Topics: Python pygame