Browse Source

Use flit as PEP 517 backend

master
Nguyễn Gia Phong 2 years ago
parent
commit
14c06fec57
  1. 106
      .gitignore
  2. 1
      MANIFEST.in
  3. 33
      pyproject.toml
  4. 2
      setup.cfg
  5. 33
      setup.py
  6. 245
      slacker_game/__init__.py
  7. 13
      slacker_game/__main__.py
  8. 248
      slacker_game/slacker.py
  9. 19
      tox.ini

106
.gitignore vendored

@ -1,4 +1,104 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
build
slacker_game.egg-info
dist
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/

1
MANIFEST.in

@ -1 +0,0 @@
include README.rst LICENSE slacker_game/OFL.txt

33
pyproject.toml

@ -0,0 +1,33 @@
[build-system]
requires = ['flit_core >=2,<3']
build-backend = 'flit_core.buildapi'
[tool.flit.metadata]
module = 'slacker_game'
author = 'Nguyễn Gia Phong'
author-email = 'mcsinyx@disroot.org'
home-page = 'https://git.disroot.org/McSinyx/slacker-game'
requires = ['pygame']
description-file = 'README.rst'
classifiers = [
'Development Status :: 4 - Beta',
'Environment :: MacOS X',
'Environment :: Win32 (MS Windows)',
'Environment :: X11 Applications',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3 :: Only',
'Topic :: Games/Entertainment :: Arcade']
requires-python = '>=3.6'
keywords = 'stacker,arcade-game,pygame'
license = 'GPLv3+'
[tool.flit.entrypoints.gui_scripts]
slacker-game = 'slacker_game.slacker:main'

2
setup.cfg

@ -1,2 +0,0 @@
[bdist_wheel]
universal=1

33
setup.py

@ -1,33 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from setuptools import setup
with open('README.rst') as f:
long_description = f.read()
setup(
name='slacker-game',
version='2.0.2',
description='A clone of the arcade game Stacker',
long_description=long_description,
url='https://github.com/McSinyx/slacker-game',
author='Clint Herron',
maintainer='Nguyễn Gia Phong',
maintainer_email='vn.mcsinyx@gmail.com',
license='GPLv3+',
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: MacOS X',
'Environment :: Win32 (MS Windows)',
'Environment :: X11 Applications',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Games/Entertainment :: Arcade'],
keywords='stacker arcade-game pygame-application',
packages=['slacker_game'],
install_requires=['pygame'],
package_data={'slacker_game': ['VT323-Regular.ttf', 'icon.png']},
entry_points={'gui_scripts': ['slacker-game = slacker_game:main']})

245
slacker_game/__init__.py

@ -1 +1,244 @@
from .slacker import Slacker, main
# A clone of the arcade game Stacker
# Copyright (C) 2007 Clint Herron
# Copyright (C) 2017, 2020 Nguyễn Gia Phong
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""A clone of the arcade game Stacker"""
__version__ = '2.0.2'
__all__ = ['Slacker']
from math import cos, pi
from random import randrange
import pygame
from pkg_resources import resource_filename
TANGO = {'Butter': ((252, 233, 79), (237, 212, 0), (196, 160, 0)),
'Orange': ((252, 175, 62), (245, 121, 0), (206, 92, 0)),
'Chocolate': ((233, 185, 110), (193, 125, 17), (143, 89, 2)),
'Chameleon': ((138, 226, 52), (115, 210, 22), (78, 154, 6)),
'Sky Blue': ((114, 159, 207), (52, 101, 164), (32, 74, 135)),
'Plum': ((173, 127, 168), (117, 80, 123), (92, 53, 102)),
'Scarlet Red': ((239, 41, 41), (204, 0, 0), (164, 0, 0)),
'Aluminium': ((238, 238, 236), (211, 215, 207), (186, 189, 182),
(136, 138, 133), (85, 87, 83), (46, 52, 54))}
def data(resource):
"""Return a true filesystem path for specified resource."""
return resource_filename('slacker_game', resource)
class SlackerTile:
"""SlackerTile(x, y) -> 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:
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)
def get_xoffset(self, maxoffset, duration=820):
"""Return the offset on x-axis to make the tile complete an cycle of
wiggling oscillation in given duration (in milliseconds).
"""
if self.wiggle:
return maxoffset * cos((pygame.time.get_ticks()/float(duration)
+ self.y/float(Slacker.BOARD_HEIGHT)) * pi)
return 0
def get_yoffset(self):
"""Return the offset on y-axis when the tile is falling."""
if self.missed_time is None:
return 0
return (pygame.time.get_ticks() - self.missed_time)**2 / 25000.0
def isfallen(self):
"""Return if the tile has fallen off the screen."""
return self.y + self.get_yoffset() > Slacker.BOARD_HEIGHT
def draw(self, max_x_offset=2):
"""Draw the tile."""
if self.y < self.MAJOR:
color = Slacker.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)
pygame.draw.rect(self.screen, color[self.dim], rect)
pygame.draw.rect(self.screen, self.BG, rect, self.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.falling_tiles = []
self.screen = pygame.display.set_mode(self.SCREEN_SIZE)
self.speed = self.INIT_SPEED + randrange(5)
self.speed_ratio = 1.0
self.width = self.MAX_WIDTH[-1]
self.y = self.BOARD_HEIGHT - 1
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)))
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)
if pygame.time.get_ticks() // 820 % 2:
self.draw_text('Press Spacebar', 0.75)
def draw_board(self):
"""Draw the board and the tiles inside."""
for y, row in enumerate(self.board):
for x, block in enumerate(row):
if block:
SlackerTile(self.screen, x, y, self.game_state).draw()
# Draw the falling tiles
for ft in self.falling_tiles:
if ft.isfallen():
self.falling_tiles.remove(ft)
else:
ft.draw()
def update_screen(self):
"""Draw the whole screen and everything inside."""
self.screen.fill(self.BG_COLOR)
if self.game_state == self.INTRO:
self.intro()
elif self.game_state in (self.PLAYING, self.LOSE, self.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
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)]
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:
for x in range(max(0, self.x),
min(self.x + self.width, self.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
self.board[self.y][x] = False
# Then, add that falling block to falling_tiles
self.falling_tiles.append(SlackerTile(
self.screen, x, self.y,
missed_time=pygame.time.get_ticks()))
self.width = sum(self.board[self.y])
if not self.width:
self.game_state = self.LOSE
elif not self.y:
self.game_state = self.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)
def main_loop(self, loop=True):
"""The main loop."""
while loop:
if self.game_state == self.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
elif event.key in (pygame.K_ESCAPE, pygame.K_q):
loop = False
elif self.game_state == self.PLAYING:
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.key_hit()
elif event.key in (pygame.K_ESCAPE, pygame.K_q):
self.__init__()
# Yes, this is a cheat.
elif event.key == pygame.K_0:
self.width += self.width < self.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):
for event in pygame.event.get():
if event.type == pygame.QUIT:
loop = False
elif event.type == pygame.KEYDOWN:
self.__init__(restart=True)
self.update_screen()

13
slacker_game/__main__.py

@ -0,0 +1,13 @@
import pygame
from slacker_game import Slacker
def main():
pygame.init()
slacker = Slacker()
slacker.main_loop()
pygame.display.quit()
if __name__ == '__main__': main()

248
slacker_game/slacker.py

@ -1,248 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# slacker-game - A clone of the arcade game Stacker
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Copyright (C) 2007 Clint Herron
# Copyright (C) 2017 Nguyễn Gia Phong
from math import cos, pi
from random import randrange
import pygame
from pkg_resources import resource_filename
TANGO = {'Butter': ((252, 233, 79), (237, 212, 0), (196, 160, 0)),
'Orange': ((252, 175, 62), (245, 121, 0), (206, 92, 0)),
'Chocolate': ((233, 185, 110), (193, 125, 17), (143, 89, 2)),
'Chameleon': ((138, 226, 52), (115, 210, 22), (78, 154, 6)),
'Sky Blue': ((114, 159, 207), (52, 101, 164), (32, 74, 135)),
'Plum': ((173, 127, 168), (117, 80, 123), (92, 53, 102)),
'Scarlet Red': ((239, 41, 41), (204, 0, 0), (164, 0, 0)),
'Aluminium': ((238, 238, 236), (211, 215, 207), (186, 189, 182),
(136, 138, 133), (85, 87, 83), (46, 52, 54))}
def data(resource):
"""Return a true filesystem path for specified resource."""
return resource_filename('slacker_game', resource)
class SlackerTile:
"""SlackerTile(x, y) -> 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:
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)
def get_xoffset(self, maxoffset, duration=820):
"""Return the offset on x-axis to make the tile complete an cycle of
wiggling oscillation in given duration (in milliseconds).
"""
if self.wiggle:
return maxoffset * cos((pygame.time.get_ticks()/float(duration)
+ self.y/float(Slacker.BOARD_HEIGHT)) * pi)
return 0
def get_yoffset(self):
"""Return the offset on y-axis when the tile is falling."""
if self.missed_time is None:
return 0
return (pygame.time.get_ticks() - self.missed_time)**2 / 25000.0
def isfallen(self):
"""Return if the tile has fallen off the screen."""
return self.y + self.get_yoffset() > Slacker.BOARD_HEIGHT
def draw(self, max_x_offset=2):
"""Draw the tile."""
color = (Slacker.COLOR_MAJOR if self.y < self.MAJOR else Slacker.COLOR_MINOR)[self.dim]
rect = pygame.Rect((self.x+self.get_xoffset(max_x_offset)) * self.SIZE,
(self.y+self.get_yoffset()) * self.SIZE,
self.SIZE, self.SIZE)
pygame.draw.rect(self.screen, color, rect)
pygame.draw.rect(self.screen, self.BG, rect, self.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 # blocks below which are displayed in the alternate color
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.falling_tiles = []
self.screen = pygame.display.set_mode(self.SCREEN_SIZE)
self.speed = self.INIT_SPEED + randrange(5)
self.speed_ratio = 1.0
self.width = self.MAX_WIDTH[-1]
self.y = self.BOARD_HEIGHT - 1
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)))
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)
if pygame.time.get_ticks() // 820 % 2:
self.draw_text('Press Spacebar', 0.75)
def draw_board(self):
"""Draw the board and the tiles inside."""
for y, row in enumerate(self.board):
for x, block in enumerate(row):
if block:
SlackerTile(self.screen, x, y, state=self.game_state).draw()
# Draw the falling tiles
for ft in self.falling_tiles:
if ft.isfallen():
self.falling_tiles.remove(ft)
else:
ft.draw()
def update_screen(self):
"""Draw the whole screen and everything inside."""
self.screen.fill(self.BG_COLOR)
if self.game_state == self.INTRO:
self.intro()
elif self.game_state in (self.PLAYING, self.LOSE, self.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
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)]
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:
for x in range(max(0, self.x),
min(self.x + self.width, self.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
self.board[self.y][x] = False
# Then, add that falling block to falling_tiles
self.falling_tiles.append(SlackerTile(
self.screen, x, self.y, missed_time=pygame.time.get_ticks()))
self.width = sum(self.board[self.y])
if not self.width:
self.game_state = self.LOSE
elif not self.y:
self.game_state = self.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)
def main_loop(self, loop=True):
"""The main loop."""
while loop:
if self.game_state == self.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
elif event.key in (pygame.K_ESCAPE, pygame.K_q):
loop = False
elif self.game_state == self.PLAYING:
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.key_hit()
elif event.key in (pygame.K_ESCAPE, pygame.K_q):
self.__init__()
# Yes, this is a cheat.
elif event.key == pygame.K_0 and self.width < self.BOARD_WIDTH:
self.width += 1
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):
for event in pygame.event.get():
if event.type == pygame.QUIT:
loop = False
elif event.type == pygame.KEYDOWN:
self.__init__(restart=True)
self.update_screen()
def main():
pygame.init()
slacker = Slacker()
slacker.main_loop()
pygame.display.quit()
if __name__ == '__main__':
main()

19
tox.ini

@ -0,0 +1,19 @@
[tox]
envlist = py
minversion = 3.3
isolated_build = True
[testenv]
deps =
flake8-builtins
isort
commands =
flake8
isort . --check --diff
[flake8]
ignore = E226, E701, E704, W503
[isort]
balanced_wrapping = True
combine_as_imports = True
Loading…
Cancel
Save