# 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")