diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..72ab1af --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +MANIFEST +build +slacker_game.egg-info +dist diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..8dce522 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include LICENSE README.rst diff --git a/README.rst b/README.rst index b9293b7..e529829 100644 --- a/README.rst +++ b/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`` diff --git a/data/Slacker.icns b/data/Slacker.icns deleted file mode 100644 index 54cba1c..0000000 Binary files a/data/Slacker.icns and /dev/null differ diff --git a/data/_game.png b/data/_game.png deleted file mode 100644 index bdb2758..0000000 Binary files a/data/_game.png and /dev/null differ diff --git a/data/_intro.png b/data/_intro.png deleted file mode 100644 index ae042cb..0000000 Binary files a/data/_intro.png and /dev/null differ diff --git a/data/_lose.png b/data/_lose.png deleted file mode 100644 index 3c05f51..0000000 Binary files a/data/_lose.png and /dev/null differ diff --git a/data/_win.png b/data/_win.png deleted file mode 100644 index 4c6344c..0000000 Binary files a/data/_win.png and /dev/null differ diff --git a/data/game.png b/data/game.png deleted file mode 100644 index 164a7d8..0000000 Binary files a/data/game.png and /dev/null differ diff --git a/data/intro.png b/data/intro.png deleted file mode 100644 index b671b39..0000000 Binary files a/data/intro.png and /dev/null differ diff --git a/data/lose.png b/data/lose.png deleted file mode 100644 index f49b223..0000000 Binary files a/data/lose.png and /dev/null differ diff --git a/data/win.png b/data/win.png deleted file mode 100644 index c6a81ec..0000000 Binary files a/data/win.png and /dev/null differ diff --git a/setup.py b/setup.py index 57f79c3..863d85f 100755 --- a/setup.py +++ b/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']}) diff --git a/slacker-game b/slacker-game deleted file mode 100755 index 7d5b4f3..0000000 --- a/slacker-game +++ /dev/null @@ -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 . -# -# 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() diff --git a/slacker_game/OFL.txt b/slacker_game/OFL.txt new file mode 100644 index 0000000..7762069 --- /dev/null +++ b/slacker_game/OFL.txt @@ -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. diff --git a/slacker_game/VT323-Regular.ttf b/slacker_game/VT323-Regular.ttf new file mode 100644 index 0000000..afa6909 Binary files /dev/null and b/slacker_game/VT323-Regular.ttf differ diff --git a/slacker_game/__init__.py b/slacker_game/__init__.py new file mode 100644 index 0000000..50e0ec3 --- /dev/null +++ b/slacker_game/__init__.py @@ -0,0 +1 @@ +from .slacker import Slacker, main diff --git a/slacker_game/icon.png b/slacker_game/icon.png new file mode 100644 index 0000000..7c5111e Binary files /dev/null and b/slacker_game/icon.png differ diff --git a/slacker_game/slacker.py b/slacker_game/slacker.py new file mode 100755 index 0000000..f65ec8e --- /dev/null +++ b/slacker_game/slacker.py @@ -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 . +# +# 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()