Development tool: pycham
The game is introduced: players eliminate the cake by exchanging the position of the cake. If there are three or more of the same cakes in a straight line or T-shaped position, the elimination will occur and a new cake will be loaded. The more cakes are eliminated in a given time, the higher the score.
Screenshot of game running:
Game making steps
1, Prepare material
Collect and process materials needed (including pictures, sounds, fonts, etc.)
2, Write code to realize game interface
1. Define the config.py file to store relevant parameters, including the width and height of the interface, the number of rows and columns of the whole grid, the total number of grids, etc.
Parameter setting:
WIDTH = 600 HEIGHT = 600 NUMGRID = 8 GRIDSIZE = 64 XMARGIN = (WIDTH - GRIDSIZE * NUMGRID) // 2 YMARGIN = (HEIGHT - GRIDSIZE * NUMGRID) // 2 ROOTDIR = os.getcwd() FPS = 30
2. Define the classes and functions of utils.py file used to store the foundation: including the whole elimination puzzle class, game class, puzzle block moving function, coordinate setting and obtaining function, start the main function of the game, initialize the randomly generated puzzle function, time countdown display function, display score function, plus score function, elimination function, new puzzle block generation function after elimination, puzzle exchange bit Set function and so on.
1) Import library: time timing, random random number generation, pygame cross platform Python module is mainly used for game graphical interface generation and audio playback, and relevant variables defined by config itself
import sys import time import random import pygame from config import *
2) Define puzzle class
class gemSprite(pygame.sprite.Sprite):
There are mainly functions:
Initialization function: self = class materializes the object itself, img_path = the path of the picture file, size = the size of the whole interface, position = the location of the puzzle block, downlen = the number of dropped cells after the puzzle block is eliminated
def __init__(self, img_path, size, position, downlen, **kwargs):
Movement function: it mainly controls the direction of the puzzle block exchange through the mouse, which has four directions: up, down, left and right
def move(self):
Get coordinate function: get the coordinates of the current puzzle block, mainly through the left and top directions to define the coordinate distance
def getPosition(self):
Set coordinate function: set puzzle coordinates
def setPosition(self, position):
3) Define game class:
class gemGame():
The main functions are:
Initialization function: self = class concrete object itself, screen = screen, sounds = audio, font = font, gem_imgs = puzzle picture
def __init__(self, screen, sounds, font, gem_imgs, **kwargs):
Start game function: game start main cycle
def start(self):
Random puzzle block generating function: a new puzzle block generated randomly after elimination
def reset(self):
Show game countdown function:
def showRemainingTime(self):
Display score function:
def drawScore(self):
Fraction calculation function:
def drawAddScore(self, add_score):
New puzzle block generation function:
def generateNewGems(self, res_match):
Eliminate successfully matched puzzle pieces: eliminate three identical puzzle pieces in row or column
def removeMatched(self, res_match):
Interface grid drawing:
def drawGrids(self):
Draw a rectangle:
def drawBlock(self, block, color=(255, 240, 245), size=4):
The new puzzle piece has a drop effect:
def dropGems(self, x, y):
Is there a jigsaw puzzle at each location
def isFull(self):
Check if the puzzle block is selected:
def checkSelected(self, position):
Whether there are rows and columns, three pieces of puzzle are the same judgment:
def isMatch(self):
According to the coordinates, obtain the corresponding position of the puzzle object:
def getGemByPos(self, x, y):
Exchange puzzle function:
def swapGem(self, gem1_pos, gem2_pos):
3. Define main.py main function: mainly used to initialize the interface and open the game main program
1) Import library
import os import pygame from utils import * from config import *
2) Definition of game main program:
def main():
Main functions:
Initialization of game interface:
pygame.init() size = width, height = (WIDTH,HEIGHT) screen = pygame.display.set_mode(size, pygame.RESIZABLE) # Window mode bg = pygame.image.load("bg.jpg") screen.blit(pygame.transform.scale(bg, size), (0, 0)) # Background energy scaling clock = pygame.time.Clock() pygame.display.set_caption('Xiao Xiao le')
Load sound resources:
# Load background music pygame.mixer.init() pygame.mixer.music.load(os.path.join(ROOTDIR, "resources/audios/bg.mp3")) pygame.mixer.music.set_volume(0.6) pygame.mixer.music.play(-1) # Loading sound effects sounds = {} sounds['mismatch'] = pygame.mixer.Sound(os.path.join(ROOTDIR, 'resources/audios/badswap.wav')) sounds['match'] = [] for i in range(6): sounds['match'].append(pygame.mixer.Sound(os.path.join(ROOTDIR, 'resources/audios/match%s.wav' % i)))
Load font resources:
font = pygame.font.Font(os.path.join(ROOTDIR, 'resources/font.TTF'), 25)
Load picture resources:
gem_imgs = [] for i in range(1, 8): gem_imgs.append(os.path.join(ROOTDIR, 'resources/images/gem%s.png' % i))
Main game cycle:
game = gemGame(screen, sounds, font, gem_imgs) while True: score = game.start() flag = False # After a round of games, players choose to play again or quit while True: # clock.tick(60) for event in pygame.event.get(): if event.type == pygame.QUIT or (event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE): pygame.quit() sys.exit() elif event.type == pygame.KEYUP and event.key == pygame.K_r: size = width, height = event.size[0], event.size[1] # Get new size # screen = pygame.display.set_mode(size, pygame.RESIZABLE) # screen.blit(pygame.transform.scale(bg, size), (0, 0)) # Background energy scaling flag = True if flag: break # screen.blit(back_image, (0, 0)) # screen.fill((135, 206, 235)) screen.blit(pygame.transform.scale(bg, size), (0, 0)) # Background energy scaling pygame.display.flip() text0 = 'Final score: %s' % score text1 = 'Press <R> to restart the game.' text2 = 'Press <Esc> to quit the game.' text3 = 'Producer: KongXinger & HeJin' y = 150 for idx, text in enumerate([text0, text1, text2, text3]): text_render = font.render(text, 1, (85, 65, 0)) rect = text_render.get_rect() if idx == 0: rect.left, rect.top = (212, y) elif idx == 1: rect.left, rect.top = (122.5, y) elif idx == 1: rect.left, rect.top = (126.5, y) else: rect.left, rect.top = (130.5, y) y += 80 screen.blit(text_render, rect) pygame.display.update() game.reset()
Main program:
if __name__ == '__main__': main()
3, Run tests
Full reference code:
config.py
import os WIDTH = 600 HEIGHT = 600 NUMGRID = 8 GRIDSIZE = 64 XMARGIN = (WIDTH - GRIDSIZE * NUMGRID) // 2 YMARGIN = (HEIGHT - GRIDSIZE * NUMGRID) // 2 ROOTDIR = os.getcwd() FPS = 30
utils.py
import sys import time import random import pygame from config import * '''Jigsaw elves''' class gemSprite(pygame.sprite.Sprite): def __init__(self, img_path, size, position, downlen, **kwargs): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(img_path) self.image = pygame.transform.smoothscale(self.image, size) self.rect = self.image.get_rect() self.rect.left, self.rect.top = position self.downlen = downlen self.target_x = position[0] self.target_y = position[1] + downlen self.type = img_path.split('/')[-1].split('.')[0] self.fixed = False self.speed_x = 10 self.speed_y = 10 self.direction = 'down' '''Jigsaw movement''' def move(self): if self.direction == 'down': self.rect.top = min(self.target_y, self.rect.top+self.speed_y) if self.target_y == self.rect.top: self.fixed = True elif self.direction == 'up': self.rect.top = max(self.target_y, self.rect.top-self.speed_y) if self.target_y == self.rect.top: self.fixed = True elif self.direction == 'left': self.rect.left = max(self.target_x, self.rect.left-self.speed_x) if self.target_x == self.rect.left: self.fixed = True elif self.direction == 'right': self.rect.left = min(self.target_x, self.rect.left+self.speed_x) if self.target_x == self.rect.left: self.fixed = True '''Get coordinates''' def getPosition(self): return self.rect.left, self.rect.top '''Set coordinates''' def setPosition(self, position): self.rect.left, self.rect.top = position '''Game class''' class gemGame(): def __init__(self, screen, sounds, font, gem_imgs, **kwargs): self.info = 'Gemgem' self.screen = screen self.sounds = sounds self.font = font self.gem_imgs = gem_imgs self.reset() '''Start the game''' def start(self): clock = pygame.time.Clock() # Traverse the entire game interface to update the location overall_moving = True # Specify the update location of some individual objects individual_moving = False # Define some necessary variables gem_selected_xy = None gem_selected_xy2 = None swap_again = False add_score = 0 add_score_showtimes = 10 time_pre = int(time.time()) # Game main cycle while True: for event in pygame.event.get(): if event.type == pygame.QUIT or (event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE): pygame.quit() sys.exit() elif event.type == pygame.MOUSEBUTTONUP: if (not overall_moving) and (not individual_moving) and (not add_score): position = pygame.mouse.get_pos() if gem_selected_xy is None: gem_selected_xy = self.checkSelected(position) else: gem_selected_xy2 = self.checkSelected(position) if gem_selected_xy2: if self.swapGem(gem_selected_xy, gem_selected_xy2): individual_moving = True swap_again = False else: gem_selected_xy = None if overall_moving: overall_moving = not self.dropGems(0, 0) # You may be able to spell multiple 3 pieces at a time if not overall_moving: res_match = self.isMatch() add_score = self.removeMatched(res_match) if add_score > 0: overall_moving = True if individual_moving: gem1 = self.getGemByPos(*gem_selected_xy) gem2 = self.getGemByPos(*gem_selected_xy2) gem1.move() gem2.move() if gem1.fixed and gem2.fixed: res_match = self.isMatch() if res_match[0] == 0 and not swap_again: swap_again = True self.swapGem(gem_selected_xy, gem_selected_xy2) self.sounds['mismatch'].play() else: add_score = self.removeMatched(res_match) overall_moving = True individual_moving = False gem_selected_xy = None gem_selected_xy2 = None self.screen.fill((135, 206, 235)) self.drawGrids() self.gems_group.draw(self.screen) if gem_selected_xy: self.drawBlock(self.getGemByPos(*gem_selected_xy).rect) if add_score: if add_score_showtimes == 10: random.choice(self.sounds['match']).play() self.drawAddScore(add_score) add_score_showtimes -= 1 if add_score_showtimes < 1: add_score_showtimes = 10 add_score = 0 self.remaining_time -= (int(time.time()) - time_pre) time_pre = int(time.time()) self.showRemainingTime() self.drawScore() if self.remaining_time <= 0: return self.score pygame.display.update() clock.tick(FPS) '''Initialization''' def reset(self): # Randomly generate blocks (i.e. initialize game map elements) while True: self.all_gems = [] self.gems_group = pygame.sprite.Group() for x in range(NUMGRID): self.all_gems.append([]) for y in range(NUMGRID): gem = gemSprite(img_path=random.choice(self.gem_imgs), size=(GRIDSIZE, GRIDSIZE), position=[XMARGIN+x*GRIDSIZE, YMARGIN+y*GRIDSIZE-NUMGRID*GRIDSIZE], downlen=NUMGRID*GRIDSIZE) self.all_gems[x].append(gem) self.gems_group.add(gem) if self.isMatch()[0] == 0: break # Score self.score = 0 # A reward for putting together one self.reward = 10 # time self.remaining_time = 20 '''Show time remaining''' def showRemainingTime(self): remaining_time_render = self.font.render('CountDown: %ss' % str(self.remaining_time), 1, (85, 65, 0)) rect = remaining_time_render.get_rect() rect.left, rect.top = (WIDTH-201, 6) self.screen.blit(remaining_time_render, rect) '''Display score''' def drawScore(self): score_render = self.font.render('SCORE:'+str(self.score), 1, (85, 65, 0)) rect = score_render.get_rect() rect.left, rect.top = (10, 6) self.screen.blit(score_render, rect) '''Display bonus points''' def drawAddScore(self, add_score): score_render = self.font.render('+'+str(add_score), 1, (255, 100, 100)) rect = score_render.get_rect() rect.left, rect.top = (250, 250) self.screen.blit(score_render, rect) '''Generate a new puzzle block''' def generateNewGems(self, res_match): if res_match[0] == 1: start = res_match[2] while start > -2: for each in [res_match[1], res_match[1]+1, res_match[1]+2]: gem = self.getGemByPos(*[each, start]) if start == res_match[2]: self.gems_group.remove(gem) self.all_gems[each][start] = None elif start >= 0: gem.target_y += GRIDSIZE gem.fixed = False gem.direction = 'down' self.all_gems[each][start+1] = gem else: gem = gemSprite(img_path=random.choice(self.gem_imgs), size=(GRIDSIZE, GRIDSIZE), position=[XMARGIN+each*GRIDSIZE, YMARGIN-GRIDSIZE], downlen=GRIDSIZE) self.gems_group.add(gem) self.all_gems[each][start+1] = gem start -= 1 elif res_match[0] == 2: start = res_match[2] while start > -4: if start == res_match[2]: for each in range(0, 3): gem = self.getGemByPos(*[res_match[1], start+each]) self.gems_group.remove(gem) self.all_gems[res_match[1]][start+each] = None elif start >= 0: gem = self.getGemByPos(*[res_match[1], start]) gem.target_y += GRIDSIZE * 3 gem.fixed = False gem.direction = 'down' self.all_gems[res_match[1]][start+3] = gem else: gem = gemSprite(img_path=random.choice(self.gem_imgs), size=(GRIDSIZE, GRIDSIZE), position=[XMARGIN+res_match[1]*GRIDSIZE, YMARGIN+start*GRIDSIZE], downlen=GRIDSIZE*3) self.gems_group.add(gem) self.all_gems[res_match[1]][start+3] = gem start -= 1 '''Remove matching gem''' def removeMatched(self, res_match): if res_match[0] > 0: self.generateNewGems(res_match) self.score += self.reward return self.reward return 0 '''Grid drawing of game interface''' def drawGrids(self): for x in range(NUMGRID): for y in range(NUMGRID): rect = pygame.Rect((XMARGIN+x*GRIDSIZE, YMARGIN+y*GRIDSIZE, GRIDSIZE, GRIDSIZE)) self.drawBlock(rect, color=(255,240,245), size=1) '''Draw rectangle block frame''' def drawBlock(self, block, color=(255, 240, 245), size=4): pygame.draw.rect(self.screen, color, block, size) '''Drop effect''' def dropGems(self, x, y): if not self.getGemByPos(x, y).fixed: self.getGemByPos(x, y).move() if x < NUMGRID-1: x += 1 return self.dropGems(x, y) elif y < NUMGRID-1: x = 0 y += 1 return self.dropGems(x, y) else: return self.isFull() '''Is there a puzzle in every position''' def isFull(self): for x in range(NUMGRID): for y in range(NUMGRID): if not self.getGemByPos(x, y).fixed: return False return True '''Check if the puzzle block is selected''' def checkSelected(self, position): for x in range(NUMGRID): for y in range(NUMGRID): if self.getGemByPos(x, y).rect.collidepoint(*position): return [x, y] return None '''Are there three consecutive blocks(nothing--Return to 0/level--Return to 1/vertical--Return to 2)''' def isMatch(self): for x in range(NUMGRID): for y in range(NUMGRID): if x + 2 < NUMGRID: if self.getGemByPos(x, y).type == self.getGemByPos(x+1, y).type == self.getGemByPos(x+2, y).type: return [1, x, y] if y + 2 < NUMGRID: if self.getGemByPos(x, y).type == self.getGemByPos(x, y+1).type == self.getGemByPos(x, y+2).type: return [2, x, y] return [0, x, y] '''Obtain the corresponding position of the puzzle object according to the coordinates''' def getGemByPos(self, x, y): return self.all_gems[x][y] '''Swap puzzle''' def swapGem(self, gem1_pos, gem2_pos): margin = gem1_pos[0] - gem2_pos[0] + gem1_pos[1] - gem2_pos[1] if abs(margin) != 1: return False gem1 = self.getGemByPos(*gem1_pos) gem2 = self.getGemByPos(*gem2_pos) if gem1_pos[0] - gem2_pos[0] == 1: gem1.direction = 'left' gem2.direction = 'right' elif gem1_pos[0] - gem2_pos[0] == -1: gem2.direction = 'left' gem1.direction = 'right' elif gem1_pos[1] - gem2_pos[1] == 1: gem1.direction = 'up' gem2.direction = 'down' elif gem1_pos[1] - gem2_pos[1] == -1: gem2.direction = 'up' gem1.direction = 'down' gem1.target_x = gem2.rect.left gem1.target_y = gem2.rect.top gem1.fixed = False gem2.target_x = gem1.rect.left gem2.target_y = gem1.rect.top gem2.fixed = False self.all_gems[gem2_pos[0]][gem2_pos[1]] = gem1 self.all_gems[gem1_pos[0]][gem1_pos[1]] = gem2 return True '''info''' def __repr__(self): return self.info
main.py
import os import pygame from utils import * from config import * '''Game master''' def main(): pygame.init() # screen = pygame.display.set_mode((WIDTH, HEIGHT)) # back_image = pygame.image.load('bg.jpg') size = width, height = (WIDTH,HEIGHT) screen = pygame.display.set_mode(size, pygame.RESIZABLE) # Window mode bg = pygame.image.load("bg.jpg") screen.blit(pygame.transform.scale(bg, size), (0, 0)) # Background energy scaling clock = pygame.time.Clock() pygame.display.set_caption('Xiao Xiao le') # Load background music pygame.mixer.init() pygame.mixer.music.load(os.path.join(ROOTDIR, "resources/audios/bg.mp3")) pygame.mixer.music.set_volume(0.6) pygame.mixer.music.play(-1) # Loading sound effects sounds = {} sounds['mismatch'] = pygame.mixer.Sound(os.path.join(ROOTDIR, 'resources/audios/badswap.wav')) sounds['match'] = [] for i in range(6): sounds['match'].append(pygame.mixer.Sound(os.path.join(ROOTDIR, 'resources/audios/match%s.wav' % i))) # Load font font = pygame.font.Font(os.path.join(ROOTDIR, 'resources/font.TTF'), 25) # Picture loading gem_imgs = [] for i in range(1, 8): gem_imgs.append(os.path.join(ROOTDIR, 'resources/images/gem%s.png' % i)) # Main cycle game = gemGame(screen, sounds, font, gem_imgs) while True: score = game.start() flag = False # After a round of games, players choose to play again or quit while True: # clock.tick(60) for event in pygame.event.get(): if event.type == pygame.QUIT or (event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE): pygame.quit() sys.exit() elif event.type == pygame.KEYUP and event.key == pygame.K_r: size = width, height = event.size[0], event.size[1] # Get new size # screen = pygame.display.set_mode(size, pygame.RESIZABLE) # screen.blit(pygame.transform.scale(bg, size), (0, 0)) # Background energy scaling flag = True if flag: break # screen.blit(back_image, (0, 0)) # screen.fill((135, 206, 235)) screen.blit(pygame.transform.scale(bg, size), (0, 0)) # Background energy scaling pygame.display.flip() text0 = 'Final score: %s' % score text1 = 'Press <R> to restart the game.' text2 = 'Press <Esc> to quit the game.' text3 = 'Producer: KongXinger & HeJin' y = 150 for idx, text in enumerate([text0, text1, text2, text3]): text_render = font.render(text, 1, (85, 65, 0)) rect = text_render.get_rect() if idx == 0: rect.left, rect.top = (212, y) elif idx == 1: rect.left, rect.top = (122.5, y) elif idx == 1: rect.left, rect.top = (126.5, y) else: rect.left, rect.top = (130.5, y) y += 80 screen.blit(text_render, rect) pygame.display.update() game.reset() '''test''' if __name__ == '__main__': main()
Learning reference: Jack HCC, blogger