def pick_spot(self): p = None while p is None: p = Pos(random.randint(0, self.w - 1), random.randint(0, self.h - 1)) for e in self.tail: if p.dist(e) < 1: p = None break return p
class Game: GRAY = (51, 51, 51) WHITE = (255, 255, 255) OFF_WHITE = (200, 200, 200) GREEN = (0, 255, 0) RED = (255, 0, 0) def __init__(self, scl, fps, w, h, load_image, play_sound, monitor, show_fps=False): self.monitor = monitor os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % ((monitor.width - (w * scl)) // 2, (monitor.height - (h * scl)) // 2) # Initializing pygame pygame.init() # Initializing the text module pygame.font.init() # Setting title pygame.display.set_caption("Snake") # Setting icon if load_image: try: icon = pygame.image.load('images/snake.png') pygame.display.set_icon(icon) except FileNotFoundError: print("Could not load image << snake.png >>") exit(1) self.scl = scl self.fps = fps self.w = w self.h = h self.load_image = load_image self.play_sound = play_sound self.screen = None self.width = None self.height = None self.keyQueue = None self.high_score = 0 self.s = None self.tail = None self.direction = None self.food = None self.hit = None self.time = None self.apple = None self.target_frame_time = None self.menu_w = None self.menu_h = None self.menu_width = None self.menu_height = None self.show_fps = show_fps # Setting refresh rate self.clock = pygame.time.Clock() self.load_options() self.set_assets(self.scl, self.fps, self.w, self.h, self.load_image, self.play_sound, self.show_fps) def set_assets(self, scl, fps, w, h, load_image, play_sound, show_fps): self.scl = scl self.fps = fps self.w = w self.h = h self.width = self.w * self.scl self.height = self.h * self.scl self.keyQueue = [] self.load_image = load_image self.play_sound = play_sound self.menu_w = 16 self.menu_h = 18 self.menu_width = self.menu_w * self.scl self.menu_height = self.menu_h * self.scl self.show_fps = show_fps # Player variables self.s = Pos(1) self.tail = [Pos()] self.direction = Pos(1) self.food = self.pick_spot() # self.reset() # Loading sound self.hit = None if play_sound: try: self.hit = pygame.mixer.Sound('music/hit.wav') except FileNotFoundError: print("Could not load << hit.wav >>") exit(1) self.apple = None if load_image: try: self.apple = pygame.image.load('images/apple.png') self.apple = pygame.transform.scale(self.apple, (scl, scl)) except FileNotFoundError: print("Could not load image << apple.png >>") exit(1) self.time = 0 self.target_frame_time = 1000 / fps def set_screen_size(self): self.screen = pygame.display.set_mode( (self.w * self.scl, self.h * self.scl)) def set_scale(self, scl): self.scl = scl self.height = self.h * self.scl self.width = self.w * self.scl self.set_screen_size() def reset(self): self.s = Pos(1) self.tail = [Pos()] self.direction = Pos(1) self.food = self.pick_spot() # Pick new random location for food. Finds a position that is not in the tail def pick_spot(self): p = None while p is None: p = Pos(random.randint(0, self.w - 1), random.randint(0, self.h - 1)) for e in self.tail: if p.dist(e) < 1: p = None break return p def run(self): self.menu() self.set_options() def menu(self): self.screen = pygame.display.set_mode( (self.menu_width, self.menu_height)) running = True menu_position = 0 menu_options = ['Play', 'Options', 'Quit'] update_required = True while running: self.clock.tick(self.fps) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if event.type == pygame.KEYDOWN: update_required = True if event.key == pygame.K_RETURN: if menu_position == 0: score = self.game() new_hs = self.check_score(score) if new_hs: self.set_options() self.game_over(score, new_hs) self.reset() elif menu_position == 1: self.options_menu() elif menu_position == 2: running = False continue self.screen = pygame.display.set_mode( (self.menu_width, self.menu_height)) if event.key == pygame.K_DOWN: menu_position = (menu_position + 1) % len(menu_options) if event.key == pygame.K_UP: menu_position -= 1 if menu_position < 0: menu_position = len(menu_options) - 1 if update_required: # Drawing background self.screen.fill(self.GRAY) for i, option in enumerate(menu_options): bg_color, fg_color = ( self.WHITE, self.GRAY) if i == menu_position else (self.GRAY, self.WHITE) self.draw_text(option, self.scl * 2, self.menu_width // 2, self.scl * 3 * (i + 1), fg_color, bg_color, padding=(self.scl // 2, self.scl // 4), bg_w=self.menu_width // 2, h_alignment='CENTER') self.draw_text(f'High score: {self.high_score}', self.scl, self.menu_width // 2, self.menu_height - self.scl, self.WHITE) pygame.display.update() update_required = False def set_fps(self, fps): self.fps = fps def options_menu(self): self.screen = pygame.display.set_mode( (self.menu_width, self.menu_height)) running = True update_required = True menu_position = 0 menu_options = [ { 'text': 'Scale', 'type': 'multi', 'value': self.scl, 'increment': 5, }, { 'text': 'Width', 'type': 'multi', 'value': self.w, 'increment': 1, }, { 'text': 'Height', 'type': 'multi', 'value': self.h, 'increment': 1, }, { 'text': 'Images', 'type': 'checkbox', 'value': self.load_image, }, { 'text': 'Music', 'type': 'checkbox', 'value': self.play_sound, }, { 'text': 'FPS', 'type': 'multi', 'value': self.fps, 'increment': 1, # 'set': self.set_fps # Idea for setting values }, { 'text': 'Show FPS', 'type': 'checkbox', 'value': self.show_fps, }, { 'text': 'Apply', 'type': 'apply', } ] while running: self.clock.tick(self.fps) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if event.type == pygame.KEYDOWN: update_required = True if event.key == pygame.K_BACKSPACE: running = False if event.key == pygame.K_RETURN: option_type = menu_options[menu_position]['type'] if option_type == 'apply': vals = list( map( lambda e: e['value'] if 'value' in e else '', menu_options)) self.set_assets( scl=vals[0], fps=vals[5], w=vals[1], h=vals[2], load_image=vals[3], play_sound=vals[4], show_fps=vals[6], ) self.set_options() running = False elif option_type == 'checkbox': menu_options[menu_position][ 'value'] = not menu_options[menu_position][ 'value'] if event.key == pygame.K_DOWN: menu_position = (menu_position + 1) % len(menu_options) if event.key == pygame.K_UP: menu_position -= 1 if menu_position < 0: menu_position = len(menu_options) - 1 if (event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT) \ and menu_options[menu_position]['type'] == 'multi': if event.key == pygame.K_LEFT: new_value = menu_options[menu_position][ 'value'] - menu_options[menu_position][ 'increment'] if new_value > 0: menu_options[menu_position][ 'value'] = new_value else: menu_options[menu_position][ 'value'] += menu_options[menu_position][ 'increment'] if update_required: self.screen.fill(self.GRAY) for i, option in enumerate(menu_options): bg_color, fg_color = ( self.WHITE, self.GRAY) if i == menu_position else (self.GRAY, self.WHITE) if option['type'] == 'apply': bg_color, fg_color = ( self.RED, self.WHITE) if i == menu_position else (self.GRAY, self.RED) value = '' if option['type'] == 'checkbox': txt = '%-9s' % (option['text']) value = '[x]' if option["value"] else '[ ]' elif option['type'] == 'multi': txt = '%-9s' % (option['text']) value = f'< {option["value"]} >' else: txt = option['text'] padding = (self.scl, self.scl // 4) self.draw_text(txt, 2 * self.scl, self.scl, self.scl * 2 * (i + 1), fg_color, bg_color, h_alignment='LEFT', text_align='LEFT', padding=padding, bg_w=2 * self.menu_width // 3) self.draw_text(value, 2 * self.scl, self.menu_width - self.scl, self.scl * 2 * (i + 1), fg_color, bg_color, h_alignment='RIGHT', text_align='RIGHT', padding=padding, bg_w=self.menu_width // 3) pygame.display.update() update_required = False def game_over(self, score, new_hs): self.screen = pygame.display.set_mode((self.width, self.height)) running = True update_required = True while running: self.clock.tick(self.fps) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if event.type == pygame.KEYDOWN: update_required = True if event.key == pygame.K_RETURN: running = False if update_required: self.screen.fill(self.GRAY) self.draw_text(f'Score: {score}', self.scl * 2, self.width // 2, self.height // 2 - (self.scl if new_hs else 0), self.WHITE) if new_hs: self.draw_text(f'New high score!', self.scl * 2, self.width // 2, (self.height // 2) + self.scl, self.RED) self.draw_text('Press enter to go back... ', self.scl, self.width // 2, self.height - self.scl, self.OFF_WHITE) pygame.display.update() update_required = False def draw_text(self, text, size, x, y, color, background=None, h_alignment='CENTER', v_alignment='CENTER', text_align='CENTER', padding=(0, 0), bg_w=0, bg_h=0): font = pygame.font.SysFont('Areal', size) text_surface = font.render(text, True, color, background) text_rect = text_surface.get_rect() bg_rect = text_surface.get_rect() if bg_h > 0: bg_rect.w = bg_h if bg_w > 0: bg_rect.w = bg_w text_w, text_h = text_rect.size if h_alignment == 'LEFT': x += bg_rect.w // 2 elif h_alignment == 'RIGHT': x -= bg_rect.w // 2 if v_alignment == 'TOP': y += text_h // 2 elif v_alignment == 'BOTTOM': y -= text_h // 2 align_x = x align_y = y width_diff = abs(text_rect.w - bg_rect.w) if text_align == 'LEFT': align_x -= width_diff // 2 elif text_align == 'RIGHT': align_x += width_diff // 2 text_rect.center = (align_x, align_y) bg_rect.center = (x, y) h_padding, v_padding = padding if background: pygame.draw.rect( self.screen, background, pygame.Rect(bg_rect.x - h_padding, bg_rect.y - v_padding, bg_rect.w + 2 * h_padding, bg_rect.h + 2 * v_padding)) self.screen.blit(text_surface, text_rect) def game(self): self.screen = pygame.display.set_mode((self.width, self.height)) os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % ( (self.monitor.width - self.width) // 2, (self.monitor.height - self.height) // 2) # Game loop running = True while running: self.clock.tick(self.fps) # Input for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: self.keyQueue.append(Pos(-1, 0)) elif event.key == pygame.K_UP: self.keyQueue.append(Pos(0, -1)) elif event.key == pygame.K_RIGHT: self.keyQueue.append(Pos(1, 0)) elif event.key == pygame.K_DOWN: self.keyQueue.append(Pos(0, 1)) # Direction update if len(self.keyQueue) > 0: n_dir = self.keyQueue[0] if self.direction.x != -n_dir.x and self.direction.y != -n_dir.y: self.direction = n_dir # Sets direction to the first element in the queue self.keyQueue.pop(0) # Removes the element from the queue # Adding last position of head to tail self.tail.append(Pos(self.s.x, self.s.y)) # Updating position of head self.s.x += self.direction.x self.s.y += self.direction.y # If overlapping with food-> Pick new spot for food. Else-> remove least recently added element of tail if self.s.dist(self.food) < 1: self.food = self.pick_spot() if self.hit is not None and self.play_sound: self.hit.play() else: self.tail.pop(0) # If out of bounds if self.s.x > self.w - 1 or self.s.x < 0 or self.s.y > self.h - 1 or self.s.y < 0: running = False continue # If crashing into itself for t in self.tail: if self.s.dist(t) < 1: running = False continue # Drawing background self.screen.fill(self.GRAY) # Draw snake pygame.draw.rect(self.screen, self.OFF_WHITE, (self.s.x * self.scl + 1, self.s.y * self.scl + 1, self.scl - 2, self.scl - 2)) for t in self.tail: pygame.draw.rect(self.screen, self.WHITE, (t.x * self.scl + 1, t.y * self.scl + 1, self.scl - 2, self.scl - 2)) # Drawing apple if self.apple is not None: self.screen.blit( self.apple, (self.food.x * self.scl, self.food.y * self.scl)) else: pygame.draw.rect(self.screen, self.GREEN, (self.food.x * self.scl, self.food.y * self.scl, self.scl, self.scl)) # Displaying score self.draw_text(str(len(self.tail) - 1), 2 * self.scl, self.scl, self.scl, self.WHITE) # Drawing high score self.draw_text(f'( {self.high_score} )', self.scl // 2, self.scl, 2 * self.scl, self.OFF_WHITE) # Drawing fps if self.show_fps: fps = 1000 / self.clock.get_time() self.draw_text('FPS: %3s' % round(fps), self.scl // 2, self.width, self.height, self.OFF_WHITE, h_alignment='RIGHT', v_alignment='BOTTOM') # Updating screen pygame.display.update() return len(self.tail) - 1 def check_score(self, score): if score > self.high_score: self.high_score = score return True return False def load_options(self): options = {} try: with open('options.json', 'r') as file: json_string = file.read() options = json.loads(json_string) except FileNotFoundError: pass if 'highScore' in options: self.high_score = options['highScore'] if 'scl' in options: self.scl = options['scl'] if 'fps' in options: self.fps = options['fps'] if 'w' in options: self.w = options['w'] if 'h' in options: self.h = options['h'] if 'loadImage' in options: self.load_image = options['loadImage'] if 'playSound' in options: self.play_sound = options['playSound'] if 'showFPS' in options: self.show_fps = options['showFPS'] def set_options(self): options = { 'highScore': self.high_score, 'scl': self.scl, 'fps': self.fps, 'w': self.w, 'h': self.h, 'loadImage': self.load_image, 'playSound': self.play_sound, 'showFPS': self.show_fps, } json_string = json.dumps(options) with open('options.json', 'w') as file: file.write(json_string)