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()