Refine packaging and remove background images
|
@ -0,0 +1,4 @@
|
|||
MANIFEST
|
||||
build
|
||||
slacker_game.egg-info
|
||||
dist
|
|
@ -0,0 +1 @@
|
|||
include LICENSE README.rst
|
20
README.rst
|
@ -3,12 +3,11 @@ Slacker
|
|||
|
||||
**Slacker** is a clone/parody of the popular arcade game *Stacker*, in which
|
||||
you must stack blocks to the top of the screen in order to win the game. Boxes
|
||||
will move back and forth at varying speeds. Hit the switch (in this game it's
|
||||
mapped to either Return or Space) to stop the boxes and move on to the next
|
||||
row. Only boxes that have something underneath them will be stacked. As the
|
||||
tower rises, the game will make your tower thinner. You win a minor prize at
|
||||
the 10th level (the blocks change color), and if you reach the 15th level, you
|
||||
will win the major prize. Good luck!
|
||||
will move back and forth at varying speeds. Press Space to stop the boxes and
|
||||
move on to the next row. Only boxes that have something underneath them will be
|
||||
stacked. As the tower rises, the game will make your tower thinner. You win a
|
||||
minor prize at the 10th level (the blocks change color), and if you reach the
|
||||
15th level, you will win the major prize. Good luck!
|
||||
|
||||
In the arcade version, credits are expensive (often $1 per play), but the
|
||||
prizes are excellent (the one that I played let you choose between an iPod, a
|
||||
|
@ -19,14 +18,15 @@ win a good prize. Despite the hard nature of the game, I hope will enjoy it,
|
|||
and if you wind up practicing with the game and getting so good that you win an
|
||||
extra iPod, I wouldn't mind if you sent it my way. :)
|
||||
|
||||
This game was originally made by Clint and Jennifer Herron (see credits). They
|
||||
wrote the above description, too. I (McSinyx) fork the game to improve the
|
||||
codebase, make it more similar to the arcade game and maybe add more features.
|
||||
This game and the above description was originally written by Clint and
|
||||
Jennifer Herron (see credits). I (McSinyx) fork the game to improve the
|
||||
codebase, make it more similar to the arcade game and hopefully add more
|
||||
features.
|
||||
|
||||
Control
|
||||
-------
|
||||
|
||||
Return, Space
|
||||
Space
|
||||
Start playing or place your stacking boxes.
|
||||
|
||||
Escape, ``q``
|
||||
|
|
BIN
data/_game.png
Before Width: | Height: | Size: 608 B |
BIN
data/_intro.png
Before Width: | Height: | Size: 40 KiB |
BIN
data/_lose.png
Before Width: | Height: | Size: 3.3 KiB |
BIN
data/_win.png
Before Width: | Height: | Size: 4.9 KiB |
BIN
data/game.png
Before Width: | Height: | Size: 6.6 KiB |
BIN
data/intro.png
Before Width: | Height: | Size: 25 KiB |
BIN
data/lose.png
Before Width: | Height: | Size: 8.8 KiB |
BIN
data/win.png
Before Width: | Height: | Size: 12 KiB |
9
setup.py
|
@ -1,7 +1,8 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from setuptools import setup
|
||||
|
||||
with open('README.txt') as f:
|
||||
with open('README.rst') as f:
|
||||
long_description = f.read()
|
||||
|
||||
setup(
|
||||
|
@ -17,7 +18,6 @@ setup(
|
|||
classifiers=[
|
||||
'Development Status :: 4 - Beta',
|
||||
'Environment :: MacOS X',
|
||||
'Environment :: MacOS X',
|
||||
'Environment :: Win32 (MS Windows)',
|
||||
'Environment :: X11 Applications',
|
||||
'Intended Audience :: End Users/Desktop',
|
||||
|
@ -27,6 +27,7 @@ setup(
|
|||
'Programming Language :: Python',
|
||||
'Topic :: Games/Entertainment :: Arcade'],
|
||||
keywords='stacker arcade-game pygame-application',
|
||||
packages=['slacker_game'],
|
||||
install_requires=['pygame'],
|
||||
data_files=[('data/*', ['slacker-game'])],
|
||||
scripts=['slacker-game'])
|
||||
package_data={'slacker_game': ['*.png', 'VT323-Regular.ttf']},
|
||||
entry_points={'gui_scripts': ['slacker-game = slacker_game:main']})
|
||||
|
|
228
slacker-game
|
@ -1,228 +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 sin
|
||||
from random import choice, randint
|
||||
|
||||
import pygame
|
||||
from pygame.locals import *
|
||||
|
||||
|
||||
class SlackerMissedTile:
|
||||
"""SlackerMissedTile(x, y, time) => SlackerMissedTile
|
||||
Slacker object for storing missed tiles.
|
||||
"""
|
||||
def __init__(self, x, y, time):
|
||||
self.x, self.y, self.time = x, y, time
|
||||
|
||||
def get_time_delta(self):
|
||||
"""Return the duration the missed tile has been falling in."""
|
||||
return (pygame.time.get_ticks() - self.time) / 125.0
|
||||
|
||||
|
||||
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 = TILE_WIDTH, TILE_HEIGHT = 40, 40
|
||||
|
||||
TILE_COLOR = 127, 127, 255
|
||||
TILE_COLOR_ALT = 255, 127, 127
|
||||
TILE_COLOR_LOSE = 64, 64, 128
|
||||
TILE_COLOR_ALT_LOSE = 127, 64, 64
|
||||
BLACK = 0, 0, 0
|
||||
|
||||
LEVEL_SPEED = 80, 80, 75, 75, 70, 70, 65, 60, 55, 50, 45, 40, 35, 30, 32
|
||||
MAX_WIDTH = (3,)*4 + (2,)*4 + (1,)*7
|
||||
|
||||
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)
|
||||
|
||||
BG_IMAGES = [pygame.image.load('data/_{}.png'.format(i))
|
||||
for i in ('intro', 'game', 'lose', 'win')]
|
||||
BG_IMAGES[WIN].set_colorkey(BLACK)
|
||||
BG_IMAGES[LOSE].set_colorkey(BLACK)
|
||||
|
||||
def __init__(self):
|
||||
self.board = [[False] * self.BOARD_WIDTH for _ in range(self.BOARD_HEIGHT)]
|
||||
self.direction = choice([1, -1])
|
||||
self.game_state = self.INTRO
|
||||
self.level = 0
|
||||
self.last_time = 0
|
||||
self.missed_tiles = []
|
||||
self.screen = pygame.display.set_mode(self.SCREEN_SIZE)
|
||||
self.speed = self.LEVEL_SPEED[0]
|
||||
self.speed_ratio = 1.0
|
||||
self.width = self.MAX_WIDTH[0]
|
||||
self.x = randint(0, self.BOARD_WIDTH - self.width)
|
||||
self.y = self.BOARD_HEIGHT - 1
|
||||
|
||||
def draw_tile(self, x, y):
|
||||
"""Draw the tile at position (x, y)."""
|
||||
if self.game_state == self.LOSE:
|
||||
if y < self.COLOR_CHANGE_Y:
|
||||
color = self.TILE_COLOR_ALT_LOSE
|
||||
else:
|
||||
color = self.TILE_COLOR_LOSE
|
||||
else:
|
||||
if y < self.COLOR_CHANGE_Y:
|
||||
color = self.TILE_COLOR_ALT
|
||||
else:
|
||||
color = self.TILE_COLOR
|
||||
|
||||
# XOffset is used to draw some wiggle in the tower when you win
|
||||
if self.game_state == self.WIN:
|
||||
xoffset = (sin(pygame.time.get_ticks()*0.004 + y*0.5)
|
||||
* (self.SCREEN_WIDTH / 4))
|
||||
else:
|
||||
xoffset = 0
|
||||
|
||||
rect = pygame.Rect(x*self.TILE_WIDTH + xoffset, y * self.TILE_HEIGHT,
|
||||
self.TILE_WIDTH, self.TILE_HEIGHT)
|
||||
pygame.draw.rect(self.screen, color, rect)
|
||||
pygame.draw.rect(self.screen, self.BLACK, rect, 2)
|
||||
|
||||
def draw_board(self):
|
||||
"""Draw the board and the tiles inside."""
|
||||
for x in range(self.BOARD_WIDTH):
|
||||
for y in range(self.BOARD_HEIGHT):
|
||||
if self.board[y][x]: self.draw_tile(x, y)
|
||||
|
||||
# Draw the missed tiles
|
||||
for mt in self.missed_tiles:
|
||||
time_delta = mt.get_time_delta()
|
||||
x = mt.x * self.TILE_WIDTH
|
||||
y = mt.y*self.TILE_HEIGHT + time_delta**2
|
||||
|
||||
if mt.y < self.COLOR_CHANGE_Y:
|
||||
color = self.TILE_COLOR_ALT_LOSE
|
||||
else:
|
||||
color = self.TILE_COLOR_LOSE
|
||||
|
||||
if y > self.SCREEN_HEIGHT:
|
||||
self.missed_tiles.remove(mt)
|
||||
else:
|
||||
pygame.draw.rect(
|
||||
self.screen, color,
|
||||
pygame.Rect(x + 2, y + 2, self.TILE_WIDTH - 3, self.TILE_HEIGHT - 3))
|
||||
|
||||
def draw_background(self):
|
||||
"""Draw the background image according to current game_state."""
|
||||
self.screen.blit(self.BG_IMAGES[self.game_state],
|
||||
(0, 0, self.SCREEN_WIDTH, self.SCREEN_HEIGHT))
|
||||
|
||||
def update_screen(self):
|
||||
"""Draw the whole screen and everything inside."""
|
||||
if self.game_state == self.PLAYING:
|
||||
self.draw_background()
|
||||
self.draw_board()
|
||||
elif self.game_state == self.INTRO:
|
||||
self.draw_background()
|
||||
elif self.game_state in (self.LOSE, self.WIN):
|
||||
self.screen.fill(self.BLACK)
|
||||
self.draw_board()
|
||||
self.draw_background()
|
||||
pygame.display.flip()
|
||||
|
||||
def update_movement(self):
|
||||
"""Update the direction the blocks are moving in."""
|
||||
self.time = pygame.time.get_ticks()
|
||||
if self.last_time + self.speed * self.speed_ratio <= self.time:
|
||||
if not -self.width < self.x + self.direction < self.BOARD_WIDTH:
|
||||
self.direction *= -1
|
||||
self.x += self.direction
|
||||
self.last_time = self.time
|
||||
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 missed block to missed_tiles
|
||||
self.missed_tiles.append(
|
||||
SlackerMissedTile(x, self.y, pygame.time.get_ticks()))
|
||||
self.width = sum(self.board[self.y])
|
||||
if not self.width:
|
||||
self.game_state = self.LOSE
|
||||
elif self.level + 1 == self.WIN_LEVEL:
|
||||
self.speed, self.game_state = self.WIN_SPEED, self.WIN
|
||||
else:
|
||||
self.direction = choice([1, -1])
|
||||
self.level += 1
|
||||
self.speed = self.LEVEL_SPEED[self.level]
|
||||
self.width = min(self.width, self.MAX_WIDTH[self.level])
|
||||
self.x = randint(0, self.BOARD_WIDTH - self.width)
|
||||
self.y -= 1
|
||||
|
||||
def main_loop(self, loop=True):
|
||||
"""The main loop."""
|
||||
while loop:
|
||||
self.update_screen()
|
||||
if self.game_state == self.INTRO:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
loop = False
|
||||
elif event.type == KEYDOWN:
|
||||
if event.key in (K_RETURN, K_SPACE):
|
||||
self.game_state = self.PLAYING
|
||||
elif event.key in (K_ESCAPE, 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 == KEYDOWN:
|
||||
if event.key in (K_RETURN, K_SPACE):
|
||||
self.key_hit()
|
||||
elif event.key in (K_ESCAPE, K_q):
|
||||
self.__init__()
|
||||
# Yes, this is a cheat.
|
||||
elif event.key == K_F1 and self.width < self.BOARD_WIDTH:
|
||||
self.x -= self.direction
|
||||
self.width += 1
|
||||
elif event.key in range(K_1, K_9 + 1):
|
||||
self.speed_ratio = (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 == KEYDOWN:
|
||||
self.__init__()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pygame.init()
|
||||
slacker = Slacker()
|
||||
slacker.main_loop()
|
||||
pygame.display.quit()
|
|
@ -0,0 +1,92 @@
|
|||
Copyright 2011, The VT323 Project Authors (peter.hull@oikoi.com)
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
@ -0,0 +1 @@
|
|||
from .slacker import Slacker, main
|
After Width: | Height: | Size: 7.1 KiB |
|
@ -0,0 +1,245 @@
|
|||
#!/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
|
||||
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 = 84 + 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 = 42 + self.y*3 + 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()
|