# tetris_shape.py
# Copyright (C) 2010, 2011 Simon Peverett
# Copyright (C) 2011 Leah Alpert
#
# This file is part of Burton-Conner Tetris Battle.
#
# Burton-Conner Tetris Battle 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.
#
# Burton-Conner Tetris Battle 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 Burton-Conner Tetris Battle. If not, see
# .
LEFT = "left"
(LEFT, RIGHT, UP, DOWN) = range(4)
direction_d = { LEFT: (-1, 0), RIGHT: (1, 0), DOWN: (0, 1) }
class Block(object):
def __init__( self, (x, y), color):
self.color = color
self.x = x
self.y = y
def coord( self ):
return (self.x, self.y)
class shape(object):
"""
Shape is the Base class for the game pieces e.g. square, T, S, Z, L,
reverse L and I. Shapes are constructed of blocks.
"""
@classmethod
def check_and_create(cls, board, coords, color ):
"""
Check if the blocks that make the shape can be placed in empty coords
before creating and returning the shape instance. Otherwise, return
None.
"""
for coord in coords:
if not board.check_block( coord ):
return None
return cls( board, coords, color)
def __init__(self, board, coords, color ):
"""
Initialise the shape base.
"""
self.board = board
self.blocks = []
for coord in coords:
self.blocks.append( Block(coord,color) )
def move( self, direction ):
"""
Move the blocks in the direction indicated by adding (dx, dy) to the
current block coordinates
"""
d_x, d_y = direction_d[direction]
for block in self.blocks:
x = block.x + d_x
y = block.y + d_y
if not self.board.check_block( (x, y) ):
return False
for block in self.blocks:
block.x += d_x
block.y += d_y
return True
def rotate(self, clockwise = True):
"""
Rotate the blocks around the 'middle' block, 90-degrees. The
middle block is always the index 0 block in the list of blocks
that make up a shape.
"""
# TO DO: Refactor for DRY
middle = self.blocks[0]
rel = []
for block in self.blocks:
rel.append( (block.x-middle.x, block.y-middle.y ) )
# to rotate 90-degrees (x,y) = (-y, x)
# First check that the there are no collisions or out of bounds moves.
for idx in xrange(len(self.blocks)):
rel_x, rel_y = rel[idx]
if clockwise:
x = middle.x+rel_y
y = middle.y-rel_x
else:
x = middle.x-rel_y
y = middle.y+rel_x
if not self.board.check_block( (x, y) ):
return False
for idx in xrange(len(self.blocks)):
rel_x, rel_y = rel[idx]
if clockwise:
x = middle.x+rel_y
y = middle.y-rel_x
else:
x = middle.x-rel_y
y = middle.y+rel_x
self.blocks[idx].x = x
self.blocks[idx].y = y
return True
class shape_limited_rotate( shape ):
"""
This is a base class for the shapes like the S, Z and I that don't fully
rotate (which would result in the shape moving *up* one block on a 180).
Instead they toggle between 90 degrees clockwise and then back 90 degrees
anti-clockwise.
"""
def __init__( self, board, coords, color ):
self.clockwise = True
super(shape_limited_rotate, self).__init__(board, coords, color)
def rotate(self, clockwise=True):
"""
Clockwise, is used to indicate if the shape should rotate clockwise
or back again anti-clockwise. It is toggled.
"""
super(shape_limited_rotate, self).rotate(clockwise=self.clockwise)
if self.clockwise:
self.clockwise=False
else:
self.clockwise=True
class square_shape( shape ):
@classmethod
def check_and_create( cls, board ):
coords = [(4,0),(5,0),(4,1),(5,1)]
return super(square_shape, cls).check_and_create(board, coords, "red")
def rotate(self, clockwise=True):
"""
Override the rotate method for the square shape to do exactly nothing!
"""
pass
class t_shape( shape ):
@classmethod
def check_and_create( cls, board ):
coords = [(4,0),(3,0),(5,0),(4,1)]
return super(t_shape, cls).check_and_create(board, coords, "yellow" )
class l_shape( shape ):
@classmethod
def check_and_create( cls, board ):
coords = [(4,0),(3,0),(5,0),(3,1)]
return super(l_shape, cls).check_and_create(board, coords, "orange")
class reverse_l_shape( shape ):
@classmethod
def check_and_create( cls, board ):
coords = [(5,0),(4,0),(6,0),(6,1)]
return super(reverse_l_shape, cls).check_and_create(
board, coords, "green")
class z_shape( shape_limited_rotate ):
@classmethod
def check_and_create( cls, board ):
coords =[(5,0),(4,0),(5,1),(6,1)]
return super(z_shape, cls).check_and_create(board, coords, "purple")
class s_shape( shape_limited_rotate ):
@classmethod
def check_and_create( cls, board ):
coords =[(5,1),(4,1),(5,0),(6,0)]
return super(s_shape, cls).check_and_create(board, coords, "magenta")
class i_shape( shape_limited_rotate ):
@classmethod
def check_and_create( cls, board ):
coords =[(4,0),(3,0),(5,0),(6,0)]
return super(i_shape, cls).check_and_create(board, coords, "blue")