From feafd6ca4c68a75dc16c0773f722d6586c8470f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Gia=20Phong?= Date: Sun, 6 Sep 2020 14:24:37 +0700 Subject: [PATCH] Move constants out of classes --- pyproject.toml | 5 ++ slacker_game/__init__.py | 145 ++++++++++++++++++++------------------- slacker_game/__main__.py | 8 +-- 3 files changed, 82 insertions(+), 76 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1c35463..32e3346 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,8 +18,13 @@ classifiers = [ 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', 'Natural Language :: English', 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python', 'Topic :: Games/Entertainment :: Arcade'] +requires-python = '>=3.7' keywords = 'stacker,arcade-game,pygame' license = 'GPLv3+' diff --git a/slacker_game/__init__.py b/slacker_game/__init__.py index 7635b64..d57b465 100644 --- a/slacker_game/__init__.py +++ b/slacker_game/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # A clone of the arcade game Stacker # Copyright (C) 2007 Clint Herron # Copyright (C) 2017-2020 Nguyễn Gia Phong @@ -18,14 +17,16 @@ """A clone of the arcade game Stacker""" -__version__ = '2.0.2' +__version__ = '2.1.0' __all__ = ['Slacker'] +from contextlib import ExitStack, redirect_stdout +from importlib.resources import path +from io import StringIO from math import cos, pi from random import randrange -import pygame -from pkg_resources import resource_filename +with redirect_stdout(StringIO()): import pygame TANGO = {'Butter': ((252, 233, 79), (237, 212, 0), (196, 160, 0)), 'Orange': ((252, 175, 62), (245, 121, 0), (206, 92, 0)), @@ -37,10 +38,21 @@ TANGO = {'Butter': ((252, 233, 79), (237, 212, 0), (196, 160, 0)), 'Aluminium': ((238, 238, 236), (211, 215, 207), (186, 189, 182), (136, 138, 133), (85, 87, 83), (46, 52, 54))} +BG_COLOR = TANGO['Aluminium'][5] +COLOR_MAJOR = TANGO['Scarlet Red'] +COLOR_MINOR = TANGO['Sky Blue'] +MAJOR = 5 -def data(resource): - """Return a true filesystem path for specified resource.""" - return resource_filename('slacker_game', resource) +INTRO, PLAYING, LOSE, WIN = range(4) +MAX_WIDTH = (1,)*7 + (2,)*5 + (3,)*3 +WIN_LEVEL = 15 + +SCREEN_SIZE = SCREEN_WIDTH, SCREEN_HEIGHT = 280, 600 +BOARD_SIZE = BOARD_WIDTH, BOARD_HEIGHT = 7, 15 +TILE_SIZE = 40 + +MAX_SPEED, WIN_SPEED, SPEED_DIFF = 70, 100, 5 +INIT_SPEED = MAX_SPEED + (BOARD_HEIGHT + 1)*SPEED_DIFF class SlackerTile: @@ -48,20 +60,17 @@ class SlackerTile: Slacker object for storing tiles. """ - SIZE = 40 - BG = TANGO['Aluminium'][5] - MAJOR = 5 def __init__(self, screen, x, y, state=1, missed_time=None): self.screen, self.x, self.y = screen, x, y - if state == Slacker.LOSE: + if state == LOSE: self.dim = 1 elif missed_time is None: self.dim = 0 else: self.dim = 2 self.missed_time = missed_time - self.wiggle = state in (Slacker.INTRO, Slacker.WIN) + self.wiggle = state in (INTRO, WIN) def get_xoffset(self, maxoffset, duration=820): """Return the offset on x-axis to make the tile complete an cycle of @@ -69,7 +78,7 @@ class SlackerTile: """ if self.wiggle: return maxoffset * cos((pygame.time.get_ticks()/float(duration) - + self.y/float(Slacker.BOARD_HEIGHT)) * pi) + + self.y/float(BOARD_HEIGHT)) * pi) return 0 def get_yoffset(self): @@ -80,72 +89,68 @@ class SlackerTile: def isfallen(self): """Return if the tile has fallen off the screen.""" - return self.y + self.get_yoffset() > Slacker.BOARD_HEIGHT + return self.y + self.get_yoffset() > BOARD_HEIGHT def draw(self, max_x_offset=2): """Draw the tile.""" - if self.y < self.MAJOR: - color = Slacker.COLOR_MAJOR + if self.y < MAJOR: + color = COLOR_MAJOR else: - color = Slacker.COLOR_MINOR - rect = pygame.Rect((self.x+self.get_xoffset(max_x_offset)) * self.SIZE, - (self.y+self.get_yoffset()) * self.SIZE, - self.SIZE, self.SIZE) + color = COLOR_MINOR + rect = pygame.Rect((self.x+self.get_xoffset(max_x_offset)) * TILE_SIZE, + (self.y+self.get_yoffset()) * TILE_SIZE, + TILE_SIZE, TILE_SIZE) pygame.draw.rect(self.screen, color[self.dim], rect) - pygame.draw.rect(self.screen, self.BG, rect, self.SIZE // 11) + pygame.draw.rect(self.screen, BG_COLOR, rect, TILE_SIZE // 11) class Slacker: """This class provides functions to run the game Slacker, a clone of the popular arcade game Stacker. """ - BOARD_SIZE = BOARD_WIDTH, BOARD_HEIGHT = 7, 15 - SCREEN_SIZE = SCREEN_WIDTH, SCREEN_HEIGHT = 280, 600 - TILE_SIZE = 40 - - COLOR_MAJOR = TANGO['Scarlet Red'] - COLOR_MINOR = TANGO['Sky Blue'] - BG_COLOR = TANGO['Aluminium'][5] - ICON = pygame.image.load(data('icon.png')) - - MAX_WIDTH = (1,)*7 + (2,)*5 + (3,)*3 - MAX_SPEED = 70 - SPEED_DIFF = 5 - INIT_SPEED = MAX_SPEED + (BOARD_HEIGHT + 1)*SPEED_DIFF - COLOR_CHANGE_Y = 5 - WIN_LEVEL = 15 - WIN_SPEED = 100 - INTRO, PLAYING, LOSE, WIN = range(4) def __init__(self, restart=False): - pygame.init() - pygame.display.set_icon(self.ICON) - pygame.display.set_caption('Slacker') - self.board = [[False]*self.BOARD_WIDTH - for _ in range(self.BOARD_HEIGHT)] - self.game_state = self.PLAYING if restart else self.INTRO + self.exit_stack = ExitStack() + self.font = self.data('VT323-Regular.ttf') + self.board = [[False]*BOARD_WIDTH + for _ in range(BOARD_HEIGHT)] + self.game_state = PLAYING if restart else INTRO self.falling_tiles = [] - self.screen = pygame.display.set_mode(self.SCREEN_SIZE) - self.speed = self.INIT_SPEED + randrange(5) + self.speed = INIT_SPEED + randrange(5) self.speed_ratio = 1.0 - self.width = self.MAX_WIDTH[-1] - self.y = self.BOARD_HEIGHT - 1 + self.width = MAX_WIDTH[-1] + self.y = BOARD_HEIGHT - 1 + + def __enter__(self): + pygame.init() + pygame.display.set_icon(pygame.image.load(self.data('icon.png'))) + pygame.display.set_caption('Slacker') + self.screen = pygame.display.set_mode(SCREEN_SIZE) + return self + + def __exit__(self, *exc): + pygame.quit() + self.exit_stack.close() + + def data(self, resource): + """Return a true filesystem path for specified resource.""" + return str(self.exit_stack.enter_context( + path('slacker_game', resource))) def draw_text(self, string, height): """Width-fit the string in the screen on the given height.""" font = pygame.font.Font( - data('VT323-Regular.ttf'), - int(self.SCREEN_WIDTH * 2.5 / (len(string) + 1))) - text = font.render(string, False, self.COLOR_MINOR[0]) - self.screen.blit(text, ((self.SCREEN_WIDTH - text.get_width()) // 2, - int(self.SCREEN_HEIGHT * height))) + self.font, int(SCREEN_WIDTH*2.5/(len(string)+1))) + text = font.render(string, False, COLOR_MINOR[0]) + self.screen.blit(text, ((SCREEN_WIDTH - text.get_width()) // 2, + int(SCREEN_HEIGHT * height))) def intro(self): """Draw the intro screen.""" for i in [(2, 2), (3, 2), (4, 2), (1.5, 3), (4.5, 3), (1.5, 4), (2, 5), (3, 5), (4, 5), (4.5, 6), (1.5, 7), (4.5, 7), (2, 8), (3, 8), (4, 8)]: - SlackerTile(self.screen, *i, state=self.INTRO).draw(1.5) + SlackerTile(self.screen, *i, state=INTRO).draw(1.5) if pygame.time.get_ticks() // 820 % 2: self.draw_text('Press Spacebar', 0.75) @@ -165,30 +170,30 @@ class Slacker: def update_screen(self): """Draw the whole screen and everything inside.""" - self.screen.fill(self.BG_COLOR) - if self.game_state == self.INTRO: + self.screen.fill(BG_COLOR) + if self.game_state == INTRO: self.intro() - elif self.game_state in (self.PLAYING, self.LOSE, self.WIN): + elif self.game_state in (PLAYING, LOSE, WIN): self.draw_board() pygame.display.flip() def update_movement(self): """Update the direction the blocks are moving in.""" speed = self.speed * self.speed_ratio - positions = self.BOARD_WIDTH + self.width - 2 + positions = BOARD_WIDTH + self.width - 2 p = int(round(pygame.time.get_ticks() / speed)) % (positions * 2) self.x = (-p % positions if p > positions else p) - self.width + 1 self.board[self.y] = [0 <= x - self.x < self.width - for x in range(self.BOARD_WIDTH)] + for x in range(BOARD_WIDTH)] def key_hit(self): """Process the current position of the blocks relatively to the ones underneath when user hit the switch, then decide if the user will win, lose or go to the next level of the tower. """ - if self.y < self.BOARD_HEIGHT - 1: + if self.y < BOARD_HEIGHT - 1: for x in range(max(0, self.x), - min(self.x + self.width, self.BOARD_WIDTH)): + min(self.x + self.width, BOARD_WIDTH)): # If there isn't any block underneath if not self.board[self.y + 1][x]: # Get rid of the block not standing on solid ground @@ -199,28 +204,28 @@ class Slacker: missed_time=pygame.time.get_ticks())) self.width = sum(self.board[self.y]) if not self.width: - self.game_state = self.LOSE + self.game_state = LOSE elif not self.y: - self.game_state = self.WIN + self.game_state = WIN else: self.y -= 1 - self.width = min(self.width, self.MAX_WIDTH[self.y]) - self.speed = self.MAX_SPEED + self.y*self.SPEED_DIFF + randrange(5) + self.width = min(self.width, MAX_WIDTH[self.y]) + self.speed = MAX_SPEED + self.y*SPEED_DIFF + randrange(5) def main_loop(self, loop=True): """The main loop.""" while loop: - if self.game_state == self.INTRO: + if self.game_state == INTRO: for event in pygame.event.get(): if event.type == pygame.QUIT: loop = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE: - self.game_state = self.PLAYING + self.game_state = PLAYING elif event.key in (pygame.K_ESCAPE, pygame.K_q): loop = False - elif self.game_state == self.PLAYING: + elif self.game_state == PLAYING: for event in pygame.event.get(): if event.type == pygame.QUIT: loop = False @@ -231,12 +236,12 @@ class Slacker: self.__init__() # Yes, this is a cheat. elif event.key == pygame.K_0: - self.width += self.width < self.BOARD_WIDTH + self.width += self.width < BOARD_WIDTH elif event.key in range(pygame.K_1, pygame.K_9+1): self.speed_ratio = (pygame.K_9-event.key+1) / 5.0 self.update_movement() - elif self.game_state in (self.LOSE, self.WIN): + elif self.game_state in (LOSE, WIN): for event in pygame.event.get(): if event.type == pygame.QUIT: loop = False diff --git a/slacker_game/__main__.py b/slacker_game/__main__.py index 6bf40cd..e6196ec 100644 --- a/slacker_game/__main__.py +++ b/slacker_game/__main__.py @@ -1,13 +1,9 @@ -import pygame - from slacker_game import Slacker def main(): - pygame.init() - slacker = Slacker() - slacker.main_loop() - pygame.display.quit() + with Slacker() as slacker: + slacker.main_loop() if __name__ == '__main__': main()