2014-05-16 00:07:48 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
import pygame
|
2014-05-16 07:05:51 +00:00
|
|
|
import random
|
2014-05-16 00:07:48 +00:00
|
|
|
|
|
|
|
|
2014-05-16 06:08:33 +00:00
|
|
|
#
|
|
|
|
# Tetrad
|
|
|
|
#
|
|
|
|
|
|
|
|
class Tetrad:
|
|
|
|
block_rotations = 4
|
|
|
|
block_configs = [
|
2014-05-19 00:13:57 +00:00
|
|
|
(1, 0x00f0222200f02222), # Shape I
|
|
|
|
(2, 0x0232007202620270), # Shape T
|
|
|
|
(3, 0x0033003300330033), # Shape O
|
|
|
|
(4, 0x0170022300740622), # Shape L
|
|
|
|
(5, 0x0071022604700322), # Shape J
|
|
|
|
(6, 0x0462036004620360), # Shape S
|
|
|
|
(7, 0x0264063002640630) # Shape Z
|
2014-05-16 06:08:33 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
|
2014-05-16 07:05:51 +00:00
|
|
|
def __init__(self, position, config, rotation):
|
2014-05-16 06:08:33 +00:00
|
|
|
self.position = position
|
2014-05-16 07:05:51 +00:00
|
|
|
self.config = config
|
2014-05-16 06:08:33 +00:00
|
|
|
self.rotation = rotation
|
|
|
|
|
2014-05-16 06:34:53 +00:00
|
|
|
|
|
|
|
def color(self):
|
|
|
|
return self.block_configs[self.config][0]
|
|
|
|
|
2014-05-16 06:44:50 +00:00
|
|
|
|
2014-05-16 06:08:33 +00:00
|
|
|
def layout(self):
|
2014-05-16 23:47:45 +00:00
|
|
|
layout = list()
|
|
|
|
mask = self.block_configs[self.config][1] >> (16 * self.rotation)
|
|
|
|
for bit in xrange(16):
|
|
|
|
position = bit % 4, bit / 4
|
|
|
|
if mask & (1 << bit):
|
|
|
|
layout.append((self.position[0] + position[0], self.position[1] + position[1]))
|
|
|
|
return layout
|
2014-05-16 06:29:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
def moved_left(self):
|
2014-05-16 07:05:51 +00:00
|
|
|
return Tetrad((self.position[0]-1, self.position[1]), self.config, self.rotation)
|
2014-05-16 06:29:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
def moved_right(self):
|
2014-05-16 07:05:51 +00:00
|
|
|
return Tetrad((self.position[0]+1, self.position[1]), self.config, self.rotation)
|
2014-05-16 06:29:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
def moved_down(self):
|
2014-05-16 07:05:51 +00:00
|
|
|
return Tetrad((self.position[0], self.position[1]+1), self.config, self.rotation)
|
2014-05-16 06:29:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
def rotated(self):
|
2014-05-16 07:05:51 +00:00
|
|
|
return Tetrad(self.position, self.config, (self.rotation + 1) % self.block_rotations)
|
|
|
|
|
|
|
|
|
2014-05-19 03:56:14 +00:00
|
|
|
def centered(self, width):
|
|
|
|
return Tetrad((width / 2 - 2, 0), self.config, self.rotation)
|
|
|
|
|
|
|
|
|
2014-05-16 07:05:51 +00:00
|
|
|
@staticmethod
|
|
|
|
def random(position=(0, 0)):
|
|
|
|
config = random.randrange(len(Tetrad.block_configs))
|
|
|
|
rotation = random.randrange(Tetrad.block_rotations)
|
|
|
|
return Tetrad(position, config, rotation)
|
2014-05-16 06:08:33 +00:00
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# Board
|
|
|
|
#
|
|
|
|
|
2014-05-16 04:58:16 +00:00
|
|
|
class Board:
|
2014-05-19 03:07:49 +00:00
|
|
|
border_color = 0xeeeeec
|
2014-05-19 03:43:32 +00:00
|
|
|
preview_color = 0xd3d7cf
|
2014-05-16 06:08:33 +00:00
|
|
|
block_colors = [
|
2014-05-19 03:11:34 +00:00
|
|
|
(0x555753, 0x2e3436), # Aluminium
|
2014-05-19 03:07:49 +00:00
|
|
|
(0xedd400, 0xfce94f), # Butter
|
|
|
|
(0xf57900, 0xfcaf3e), # Orange
|
|
|
|
(0xc17d11, 0xe9b96e), # Chocolate
|
|
|
|
(0x73d216, 0x8ae234), # Chameleon
|
|
|
|
(0x3465a4, 0x729fcf), # Sky Blue
|
|
|
|
(0x75507b, 0xad7fa8), # Plum
|
|
|
|
(0xcc0000, 0xef2929) # Scarlet Red
|
2014-05-16 06:08:33 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
|
2014-05-16 05:18:06 +00:00
|
|
|
def __init__(self, grid_position, grid_dims, grid_border_width, block_dims):
|
|
|
|
self.grid_dims = grid_dims
|
|
|
|
self.grid_border_width = grid_border_width
|
2014-05-16 04:58:16 +00:00
|
|
|
self.block_dims = block_dims
|
2014-05-16 05:18:06 +00:00
|
|
|
|
2014-05-16 11:04:42 +00:00
|
|
|
grid_screen_dims = grid_border_width*2 + grid_dims[0]*block_dims[0], grid_border_width*2 + grid_dims[1]*block_dims[1]
|
2014-05-16 05:18:06 +00:00
|
|
|
self.grid_rect = pygame.Rect(grid_position, grid_screen_dims)
|
2014-05-16 05:37:15 +00:00
|
|
|
|
2014-05-16 07:43:17 +00:00
|
|
|
self.blocks = [[0]*grid_dims[0] for i in range(grid_dims[1])]
|
2014-05-16 04:58:16 +00:00
|
|
|
|
2014-05-16 06:08:33 +00:00
|
|
|
|
2014-05-19 03:43:32 +00:00
|
|
|
def render(self, surface):
|
2014-05-16 04:58:16 +00:00
|
|
|
self.render_frame(surface)
|
2014-05-16 05:18:06 +00:00
|
|
|
self.render_blocks(surface)
|
2014-05-16 04:58:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
def render_frame(self, surface):
|
2014-05-16 08:10:28 +00:00
|
|
|
pygame.draw.rect(surface, self.border_color, self.grid_rect, self.grid_border_width)
|
2014-05-16 04:58:16 +00:00
|
|
|
|
|
|
|
|
2014-05-16 05:18:06 +00:00
|
|
|
def render_blocks(self, surface):
|
|
|
|
for y in xrange(self.grid_dims[1]):
|
|
|
|
for x in xrange(self.grid_dims[0]):
|
2014-05-19 03:43:32 +00:00
|
|
|
self.render_block(surface, self.blocks[y][x], (x, y))
|
2014-05-16 05:18:06 +00:00
|
|
|
|
|
|
|
|
2014-05-19 03:43:32 +00:00
|
|
|
def render_tetrad(self, surface, tetrad, preview=False):
|
2014-05-16 07:05:51 +00:00
|
|
|
color = tetrad.color()
|
2014-05-16 06:29:12 +00:00
|
|
|
for point in tetrad.layout():
|
2014-05-19 03:43:32 +00:00
|
|
|
self.render_block(surface, color, point, preview)
|
2014-05-16 06:29:12 +00:00
|
|
|
|
|
|
|
|
2014-05-19 03:43:32 +00:00
|
|
|
def render_block(self, surface, color, position, preview=False):
|
2014-05-16 05:18:06 +00:00
|
|
|
block_rect = self.block_screen_rect(position)
|
2014-05-19 03:43:32 +00:00
|
|
|
if preview:
|
|
|
|
color = 0
|
|
|
|
|
|
|
|
color_outer, color_inner = self.block_colors[color]
|
|
|
|
pygame.draw.rect(surface, color_inner, block_rect)
|
|
|
|
pygame.draw.rect(surface, color_outer, block_rect, 1)
|
|
|
|
|
|
|
|
if preview:
|
|
|
|
position = block_rect.centerx, block_rect.centery
|
|
|
|
pygame.draw.circle(surface, self.preview_color, position, 2)
|
2014-05-16 05:18:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
def block_screen_rect(self, position):
|
2014-05-16 06:29:12 +00:00
|
|
|
top_left = (
|
|
|
|
self.grid_border_width+self.grid_rect.x+self.block_dims[0]*position[0],
|
|
|
|
self.grid_border_width+self.grid_rect.y+self.block_dims[1]*position[1]
|
|
|
|
)
|
2014-05-16 05:18:06 +00:00
|
|
|
return pygame.Rect(top_left, self.block_dims)
|
2014-05-16 04:58:16 +00:00
|
|
|
|
|
|
|
|
2014-05-16 06:44:50 +00:00
|
|
|
def can_place_tetrad(self, tetrad):
|
|
|
|
for point in tetrad.layout():
|
|
|
|
if point[0] < 0 or point[1] < 0:
|
|
|
|
return False
|
|
|
|
if point[0] >= self.grid_dims[0] or point[1] >= self.grid_dims[1]:
|
|
|
|
return False
|
2014-05-16 07:43:17 +00:00
|
|
|
if self.blocks[point[1]][point[0]] != 0:
|
2014-05-16 06:44:50 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2014-05-16 07:05:51 +00:00
|
|
|
def place_tetrad(self, tetrad):
|
|
|
|
color = tetrad.color()
|
|
|
|
for point in tetrad.layout():
|
2014-05-16 07:43:17 +00:00
|
|
|
self.blocks[point[1]][point[0]] = color
|
2014-05-16 07:05:51 +00:00
|
|
|
|
|
|
|
|
2014-05-16 07:38:01 +00:00
|
|
|
def settle(self):
|
2014-05-19 04:17:04 +00:00
|
|
|
settled_count = 0
|
|
|
|
|
2014-05-16 07:49:37 +00:00
|
|
|
row_src = row_dst = self.grid_dims[1] - 1
|
2014-05-16 07:38:01 +00:00
|
|
|
while row_dst >= 0:
|
|
|
|
row_data = self.blocks[row_src] if row_src >= 0 else self.grid_dims[0] * [0]
|
|
|
|
self.blocks[row_dst] = row_data
|
|
|
|
row_src -= 1
|
|
|
|
if 0 in row_data:
|
2014-05-16 07:49:37 +00:00
|
|
|
row_dst -= 1
|
2014-05-19 04:17:04 +00:00
|
|
|
else:
|
|
|
|
settled_count += 1
|
|
|
|
|
|
|
|
return settled_count
|
2014-05-16 07:38:01 +00:00
|
|
|
|
|
|
|
|
2014-05-16 06:08:33 +00:00
|
|
|
#
|
|
|
|
# Game
|
|
|
|
#
|
|
|
|
|
|
|
|
class Game:
|
2014-05-19 04:17:04 +00:00
|
|
|
text_color = 0xeeeeecff
|
|
|
|
text_bg_color = 0x000000ff
|
2014-05-19 04:33:38 +00:00
|
|
|
line_multipliers = [100, 300, 500, 800]
|
|
|
|
lines_per_level = 10
|
|
|
|
initial_speed = 800
|
|
|
|
speed_multiplier = 2
|
2014-05-16 08:10:28 +00:00
|
|
|
|
2014-05-16 04:58:16 +00:00
|
|
|
def __init__(self):
|
2014-05-19 04:17:04 +00:00
|
|
|
font_path = pygame.font.get_default_font()
|
|
|
|
self.font = pygame.font.Font(font_path, 16)
|
|
|
|
|
2014-05-16 08:10:28 +00:00
|
|
|
self.new_game()
|
|
|
|
|
|
|
|
|
|
|
|
def new_game(self):
|
2014-05-19 02:23:22 +00:00
|
|
|
border_width = 3
|
|
|
|
block_dims = 20, 20
|
|
|
|
padding = 10
|
|
|
|
self.board = Board((padding, padding), (10, 20), border_width, block_dims)
|
|
|
|
self.board_prev = Board((self.board.grid_rect.right+padding, padding), (4, 4), border_width, block_dims)
|
2014-05-19 04:17:04 +00:00
|
|
|
self.score_position = self.board_prev.grid_rect.left, self.board_prev.grid_rect.bottom+padding
|
2014-05-19 02:23:22 +00:00
|
|
|
|
2014-05-19 02:13:56 +00:00
|
|
|
self.tetrad = Tetrad.random()
|
2014-05-19 03:56:14 +00:00
|
|
|
self.tetrad = self.tetrad.centered(self.board.grid_dims[0])
|
2014-05-19 02:13:56 +00:00
|
|
|
self.tetrad_next = Tetrad.random()
|
2014-05-19 03:43:32 +00:00
|
|
|
self.tetrad_preview = None
|
2014-05-19 02:23:22 +00:00
|
|
|
|
2014-05-19 04:33:38 +00:00
|
|
|
self.ticker = 0
|
|
|
|
self.score = 0
|
|
|
|
self.cleared_lines = 0
|
2014-05-19 02:15:33 +00:00
|
|
|
self.active = True
|
2014-05-19 02:13:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
def end_game(self):
|
2014-05-19 02:15:33 +00:00
|
|
|
self.active = False
|
2014-05-19 02:13:56 +00:00
|
|
|
|
|
|
|
|
2014-05-16 06:08:33 +00:00
|
|
|
def render(self, surface):
|
2014-05-19 03:11:34 +00:00
|
|
|
self.board.render(surface)
|
2014-05-19 03:43:32 +00:00
|
|
|
if self.tetrad_preview is not None:
|
|
|
|
self.board.render_tetrad(surface, self.tetrad_preview, True)
|
2014-05-19 03:11:34 +00:00
|
|
|
self.board.render_tetrad(surface, self.tetrad)
|
|
|
|
|
|
|
|
self.board_prev.render(surface)
|
|
|
|
self.board_prev.render_tetrad(surface, self.tetrad_next)
|
2014-05-16 06:08:33 +00:00
|
|
|
|
2014-05-19 04:33:38 +00:00
|
|
|
font_text = 'Lines: {0}'.format(self.cleared_lines)
|
2014-05-19 04:17:04 +00:00
|
|
|
font_surface = self.font.render(font_text, False, pygame.Color(self.text_color), pygame.Color(self.text_bg_color))
|
|
|
|
surface.blit(font_surface, self.score_position)
|
|
|
|
|
2014-05-16 06:08:33 +00:00
|
|
|
|
2014-05-16 08:10:28 +00:00
|
|
|
def advance(self, elapsed):
|
2014-05-19 02:15:33 +00:00
|
|
|
if not self.active:
|
|
|
|
return
|
|
|
|
|
2014-05-19 03:43:32 +00:00
|
|
|
self.tetrad_preview = None
|
|
|
|
tetrad_preview = self.tetrad.moved_down()
|
|
|
|
while self.board.can_place_tetrad(tetrad_preview):
|
|
|
|
self.tetrad_preview = tetrad_preview
|
|
|
|
tetrad_preview = self.tetrad_preview.moved_down()
|
|
|
|
|
2014-05-19 04:33:38 +00:00
|
|
|
self.ticker += elapsed
|
|
|
|
if self.ticker > self.current_speed():
|
2014-05-19 02:15:33 +00:00
|
|
|
self.lower_tetrad()
|
2014-05-16 06:08:33 +00:00
|
|
|
|
|
|
|
|
2014-05-16 08:10:28 +00:00
|
|
|
def try_placement(self, tetrad):
|
2014-05-16 06:44:50 +00:00
|
|
|
if self.board.can_place_tetrad(tetrad):
|
|
|
|
self.tetrad = tetrad
|
2014-05-16 07:05:51 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
2014-05-16 06:44:50 +00:00
|
|
|
|
|
|
|
|
2014-05-19 02:13:56 +00:00
|
|
|
def lower_tetrad(self):
|
2014-05-19 04:33:38 +00:00
|
|
|
self.ticker = 0
|
2014-05-19 02:13:56 +00:00
|
|
|
if self.try_placement(self.tetrad.moved_down()):
|
2014-05-19 02:40:38 +00:00
|
|
|
return True
|
2014-05-19 02:13:56 +00:00
|
|
|
|
|
|
|
self.board.place_tetrad(self.tetrad)
|
2014-05-19 04:33:38 +00:00
|
|
|
|
|
|
|
cleared_lines = self.board.settle()
|
|
|
|
if cleared_lines > 0:
|
|
|
|
self.score += (self.current_level() + 1) * self.line_multipliers[cleared_lines]
|
|
|
|
self.cleared_lines += cleared_lines
|
2014-05-19 02:13:56 +00:00
|
|
|
|
2014-05-19 03:56:14 +00:00
|
|
|
self.tetrad = self.tetrad_next.centered(self.board.grid_dims[0])
|
2014-05-19 02:13:56 +00:00
|
|
|
self.tetrad_next = Tetrad.random()
|
|
|
|
|
|
|
|
if not self.try_placement(self.tetrad):
|
|
|
|
self.end_game()
|
|
|
|
|
2014-05-19 02:40:38 +00:00
|
|
|
return False
|
|
|
|
|
2014-05-19 02:13:56 +00:00
|
|
|
|
2014-05-19 02:40:38 +00:00
|
|
|
def move_left(self):
|
2014-05-19 02:23:22 +00:00
|
|
|
if self.active:
|
|
|
|
self.try_placement(self.tetrad.moved_left())
|
2014-05-16 06:08:33 +00:00
|
|
|
|
|
|
|
|
2014-05-19 02:40:38 +00:00
|
|
|
def move_right(self):
|
2014-05-19 02:23:22 +00:00
|
|
|
if self.active:
|
|
|
|
self.try_placement(self.tetrad.moved_right())
|
2014-05-16 06:08:33 +00:00
|
|
|
|
|
|
|
|
2014-05-19 02:40:38 +00:00
|
|
|
def move_down(self):
|
2014-05-19 02:23:22 +00:00
|
|
|
if self.active:
|
|
|
|
self.lower_tetrad()
|
2014-05-16 06:08:33 +00:00
|
|
|
|
|
|
|
|
2014-05-19 02:40:38 +00:00
|
|
|
def rotate(self):
|
2014-05-19 02:23:22 +00:00
|
|
|
if self.active:
|
|
|
|
self.try_placement(self.tetrad.rotated())
|
2014-05-16 06:08:33 +00:00
|
|
|
|
|
|
|
|
2014-05-19 02:40:38 +00:00
|
|
|
def drop(self):
|
|
|
|
if self.active:
|
|
|
|
while self.lower_tetrad():
|
|
|
|
pass
|
|
|
|
|
2014-05-19 04:33:38 +00:00
|
|
|
|
|
|
|
def current_level(self):
|
|
|
|
return self.cleared_lines / self.lines_per_level
|
|
|
|
|
|
|
|
|
|
|
|
def current_speed(self):
|
|
|
|
return self.initial_speed - self.current_level() * self.speed_multiplier
|
|
|
|
|
2014-05-19 02:40:38 +00:00
|
|
|
|
2014-05-16 06:08:33 +00:00
|
|
|
#
|
|
|
|
# Engine
|
|
|
|
#
|
|
|
|
|
|
|
|
class Engine:
|
2014-05-16 00:07:48 +00:00
|
|
|
def create(self, resolution):
|
|
|
|
pygame.init()
|
2014-05-16 08:10:28 +00:00
|
|
|
|
2014-05-19 04:17:04 +00:00
|
|
|
self.game = Game()
|
2014-05-16 04:58:16 +00:00
|
|
|
self.surface = pygame.display.set_mode(resolution, pygame.DOUBLEBUF)
|
2014-05-16 08:10:28 +00:00
|
|
|
self.ticks = pygame.time.get_ticks()
|
2014-05-16 00:07:48 +00:00
|
|
|
|
|
|
|
if pygame.joystick.get_count() > 0:
|
|
|
|
self.joystick = pygame.joystick.Joystick(0)
|
|
|
|
self.joystick.init()
|
|
|
|
else:
|
|
|
|
self.joystick = None
|
|
|
|
|
|
|
|
|
|
|
|
def update(self):
|
2014-05-16 08:10:28 +00:00
|
|
|
ticks = pygame.time.get_ticks()
|
|
|
|
self.game.advance(ticks - self.ticks)
|
2014-05-16 06:08:33 +00:00
|
|
|
self.game.render(self.surface)
|
2014-05-16 08:10:28 +00:00
|
|
|
self.ticks = ticks
|
2014-05-16 04:58:16 +00:00
|
|
|
|
2014-05-16 00:07:48 +00:00
|
|
|
pygame.display.flip()
|
|
|
|
pygame.time.delay(1)
|
2014-05-16 04:58:16 +00:00
|
|
|
|
2014-05-16 00:07:48 +00:00
|
|
|
event = pygame.event.poll()
|
|
|
|
return self.handle_event(event)
|
|
|
|
|
|
|
|
|
|
|
|
def destroy(self):
|
|
|
|
if self.joystick is not None:
|
|
|
|
self.joystick.quit()
|
|
|
|
|
|
|
|
pygame.quit()
|
|
|
|
|
|
|
|
|
|
|
|
def handle_event(self, event):
|
|
|
|
if event.type == pygame.QUIT:
|
|
|
|
return False
|
|
|
|
|
|
|
|
if event.type == pygame.KEYDOWN:
|
2014-05-16 04:43:20 +00:00
|
|
|
if event.key == pygame.K_LEFT:
|
2014-05-19 02:40:38 +00:00
|
|
|
self.game.move_left()
|
2014-05-16 04:43:20 +00:00
|
|
|
elif event.key == pygame.K_RIGHT:
|
2014-05-19 02:40:38 +00:00
|
|
|
self.game.move_right()
|
2014-05-16 04:43:20 +00:00
|
|
|
elif event.key == pygame.K_DOWN:
|
2014-05-19 02:40:38 +00:00
|
|
|
self.game.move_down()
|
2014-05-16 04:43:20 +00:00
|
|
|
elif event.key == pygame.K_UP:
|
2014-05-19 02:40:38 +00:00
|
|
|
self.game.rotate()
|
|
|
|
elif event.key == pygame.K_SPACE:
|
|
|
|
self.game.drop()
|
2014-05-19 03:43:32 +00:00
|
|
|
elif event.key == pygame.K_n:
|
|
|
|
self.game.new_game()
|
2014-05-16 04:43:20 +00:00
|
|
|
elif event.key == pygame.K_ESCAPE:
|
|
|
|
return False
|
|
|
|
|
2014-05-16 00:07:48 +00:00
|
|
|
elif event.type == pygame.JOYAXISMOTION:
|
|
|
|
if event.axis == 0:
|
2014-05-19 02:40:38 +00:00
|
|
|
if event.value > 0: self.game.move_right()
|
|
|
|
elif event.value < 0: self.game.move_left()
|
2014-05-16 00:07:48 +00:00
|
|
|
elif event.axis == 1:
|
2014-05-19 02:40:38 +00:00
|
|
|
if event.value > 0: self.game.move_down()
|
|
|
|
elif event.value < 0: self.game.rotate()
|
2014-05-16 00:07:48 +00:00
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2014-05-16 06:08:33 +00:00
|
|
|
#
|
|
|
|
# Entry
|
|
|
|
#
|
2014-05-16 00:07:48 +00:00
|
|
|
|
|
|
|
def main():
|
|
|
|
engine = Engine()
|
|
|
|
engine.create((800, 600))
|
|
|
|
while engine.update():
|
|
|
|
pass
|
|
|
|
engine.destroy()
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|