#!/usr/bin/env python # Burton-Conner Tetris Battle -- Tetris installation controlled by DDR pads # Copyright (C) 2010, 2011 Simon Peverett # Copyright (C) 2011 Russell Cohen , # Leah Alpert # # 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 . """ Tetris Tk - A tetris clone written in Python using the Tkinter GUI library. Controls: Left/a Move left Right/d Move right Up/w Rotate / add player Down/s Move down / start game """ from time import sleep, time import random import sys from renderer import PygameGoodRenderer from renderer import PygameRenderer from renderer import LedRenderer from tetris_shape import * from ddrinput import DdrInput from ddrinput import DIRECTIONS import pygame TIME_LIMIT = 5 * 60 #seconds LINES_TO_ADVANCE = 8 #num lines needed to advance to next level LEVEL_SPEEDS = [400,300,400,250,560,520] MAXX = 10 MAXY = 18 (LEFT, RIGHT, UP, DOWN, DROP, DIE) = range(6) COLORS = ["orange", "red", "green", "blue", "purple", "yellow", "magenta"] LEVEL_COLORS = ["red", "orange", "yellow", "green", "blue", "purple"] class Board(): """ The board represents the tetris playing area. A grid of x by y blocks. Stores blocks that have landed. """ def __init__(self, max_x=10, max_y=20): # blocks are stored in dict of (x,y)->"color" self.landed = {} self.max_x = max_x self.max_y = max_y def clear(self): self.landed = {} def receive_lines(self, num_lines): #shift lines up for y in range(self.max_y-num_lines): for x in xrange(self.max_x): block_color = self.landed.pop((x,y+num_lines),None) if block_color: self.landed[(x,y)] = block_color #put in new lines for j in range(num_lines): for i in random.sample(xrange(self.max_x), random.choice([6,7])): self.landed[(i,self.max_y-1-j)] = random.choice(COLORS) def check_for_complete_row( self, blocks ): """ Look for a complete row of blocks, from the bottom up until the top row or until an empty row is reached. """ rows_deleted = 0 # Add the blocks to those in the grid that have already 'landed' for block in blocks: self.landed[ block.coord() ] = block.color empty_row = 0 # find the first empty row for y in xrange(self.max_y -1, -1, -1): row_is_empty = True for x in xrange(self.max_x): if self.landed.get((x,y), None): row_is_empty = False break; if row_is_empty: empty_row = y break # Now scan up and until a complete row is found. y = self.max_y - 1 while y > empty_row: complete_row = True for x in xrange(self.max_x): if self.landed.get((x,y), None) is None: complete_row = False break; if complete_row: rows_deleted += 1 #delete the completed row for x in xrange(self.max_x): self.landed.pop((x,y)) # move all the rows above it down for ay in xrange(y-1, empty_row, -1): for x in xrange(self.max_x): block_color = self.landed.pop((x,ay), None) if block_color: dx,dy = (0,1) self.landed[(x+dx, ay+dy)] = block_color # move the empty row index down too empty_row +=1 # y stays same as row above has moved down. else: y -= 1 # return the number of rows deleted. return rows_deleted def check_block( self, (x, y) ): """ Check if the x, y coordinate can have a block placed there. That is; if there is a 'landed' block there or it is outside the board boundary, then return False, otherwise return true. """ if x < 0 or x >= self.max_x or y < -3 or y >= self.max_y: return False elif self.landed.has_key( (x, y) ): return False else: return True #represents a player. each player has a board, other player's board, #current shape, score, etc class Player(): def __init__(self, player_id, gs, myBoard, otherBoard): self.id = player_id self.board = myBoard self.other_board = otherBoard self.score = 0 self.gs = gs self.shape = self.get_next_shape() def handle_move(self, direction): #if you can't move then you've hit something if self.shape: if direction==UP: self.shape.rotate(clockwise=False) else: if not self.shape.move( direction ): # if you're heading down then the shape has 'landed' if direction == DOWN: points = self.board.check_for_complete_row( self.shape.blocks) #del self.shape self.shape = self.get_next_shape() self.score += points if self.gs.num_players == 2: if points > 1: self.other_board.receive_lines(points-1) # If the shape returned is None, then this indicates that # that the check before creating it failed and the # game is over! if self.shape is None: self.gs.state = "ending" #you lost! if self.gs.num_players == 2: self.gs.winner = (self.id + 1) % 2 else: self.gs.winner = self.id # do we go up a level? if (self.gs.level < len(LEVEL_SPEEDS)-1 and self.score / LINES_TO_ADVANCE >= self.gs.level+1 ): self.gs.level+=1 print "level",self.gs.level self.gs.delay = LEVEL_SPEEDS[self.gs.level] # Signal that the shape has 'landed' return False return True def get_next_shape( self ): #Randomly select which tetrominoe will be used next. the_shape = self.gs.shapes[ random.randint(0,len(self.gs.shapes)-1) ] return the_shape.check_and_create(self.board) #contains variables that are shared between the players: #levels, delay time, etc class GameState(): def __init__(self): self.shapes = [square_shape, t_shape,l_shape, reverse_l_shape, z_shape, s_shape,i_shape ] self.num_players = 0 self.level = 0 #levels go 0-9 self.delay = LEVEL_SPEEDS[0] self.state = "waiting" #states: waiting (between games), playing, ending self.winner = None #winning player id #runs the overall game. initializes both player and any displays class TetrisGame(object): #one-time initialization for gui etc def __init__(self): print "initialize tetris" self.gui = [PygameGoodRenderer(), LedRenderer()] self.input = DdrInput() while True: self.init_game() #initializes each game def init_game(self): print "init next game" self.boards = [Board(MAXX,MAXY), Board(MAXX,MAXY)] self.players = [None,None] self.gameState = GameState() self.board_animation(0,"up_arrow") self.board_animation(1,"up_arrow") self.start_time = None self.input.reset() self.update_gui() self.handle_input() #this calls all other functions, such as add_player, start_game def add_player(self,num): # 0=left, 1=right print "adding player",num if self.players[num]==None: self.boards[num].clear() p = Player(num, self.gameState, self.boards[num], self.boards[(num+1)%2]) self.players[num] = p self.board_animation(num,"down_arrow") self.gameState.num_players+=1 self.update_gui() def start_game(self): print "start game" self.boards[0].clear() self.boards[1].clear() self.gameState.state = "playing" self.update_gui() self.start_time = time() self.drop_time = time() self.gravity() def handle_input(self): game_on = True t = 0 while game_on: t+=1 if (self.gameState.state=="ending") or (self.gameState.state=="playing" and time()-self.start_time > TIME_LIMIT): self.end_game() game_on = False return if self.gameState.state=="playing" and time()-self.drop_time > self.gameState.delay/1000.0: self.gravity() self.drop_time = time() if self.gameState.state != "ending": self.update_gui() ev = self.input.poll() if ev: player,direction = ev #print "Player",player,direction if direction == DIE: #Exit instruction game_on = False pygame.quit() sys.exit() if self.gameState.state=="playing": if self.players[player]!=None: #DROP is only for debugging purposes for now, to make the game end. if direction == DROP: while self.players[player].handle_move( DOWN ): pass else: self.players[player].handle_move(direction) elif self.gameState.state == "waiting": if direction==UP: self.add_player(player) elif direction==DOWN: if self.players[player]!=None: self.start_game() self.update_gui() elif t%10000==0: t=0 self.update_gui() def gravity(self): for p in self.players: if p: p.handle_move(DOWN) def update_gui(self): [gui.render_game(self.to_dict()) for gui in self.gui] #self.gui[0].render_game(self.to_gui_dict()) def end_game(self): if self.gameState.winner!=None: winner_id = self.gameState.winner print "GAME OVER: layer",winner_id,"wins" else: if self.gameState.num_players == 2: if self.players[0].score > self.players[1].score: winner_id = 0 elif self.players[1].score > self.players[0].score: winner_id = 1 else: winner_id = 2 #tie, show both as winners. elif self.players[0]!=None: winner_id = 0 else: winner_id = 1 self.animate_ending(winner_id) def board_animation(self, board_id, design, color="green"): b = self.boards[board_id] d = self.create_shapes(design) for coord in d: b.landed[coord]=color def animate_ending(self,winner_board): if winner_board == 2: self.board_animation(0,"outline") self.board_animation(1,"outline") else: self.board_animation(winner_board,"outline","yellow") self.update_gui() sleep(3) def create_shapes(self,design): #in progress..... shapes = {} y = 4 up_diags = [(1,y+4),(1,y+3),(2,y+3),(2,y+2),(3,y+2),(3,y+1), (8,y+4),(8,y+3),(7,y+3),(7,y+2),(6,y+2),(6,y+1)] down_diags = [(x0,10-y0+2*y) for (x0,y0) in up_diags] line = [(i,j) for i in [4,5] for j in range(y,y+11)] up_arrow = line[:] for xy in up_diags: up_arrow.append(xy) down_arrow = line[:] for xy in down_diags: down_arrow.append(xy) sides = [(i,j) for i in [0,9] for j in range(18)] tb = [(i,j) for i in range(10) for j in [0,17]] outline = tb + sides shapes["down_arrow"] = down_arrow shapes["up_arrow"] = up_arrow shapes["outline"] = outline shapes["test"] = [(5,5)] return shapes[design] def to_dict(self): d = {} for n in range(2): board = self.boards[n] offset = n*MAXX #blocks for (x,y) in board.landed: d[(x+offset,y)] = board.landed[(x,y)] if self.players[n]!=None: p = self.players[n] #shapes if p.shape: blocks = p.shape.blocks for b in blocks: if b.y >= 0: d[(b.x+offset*n,b.y)] = b.color #score score = p.score for i in range(10): bit = score%2 score = score>>1 coord = (MAXX-1-i + offset, MAXY+1) if bit: d[coord] = "yellow" #level level = self.gameState.level d[(level+offset,MAXY)] = LEVEL_COLORS[level] #time if self.start_time!=None: time_left = (self.start_time + TIME_LIMIT - time()) #seconds left for i in range(TIME_LIMIT/60): #0,1,2,3 (minutes) if time_left/60 >= i: seconds = time_left - 60*i # is in .5-1 secs, etc if not (.5= 0: d[(b.x+offset*n,b.y)] = b.color return d if __name__ == "__main__": print """Burton-Conner Tetris Battle Copyright (C) 2010, 2011 Simon Peverett Copyright (C) 2011 Russell Cohen, Leah Alpert This program comes with ABSOLUTELY NO WARRANTY; for details see . This is free software, and you are welcome to redistribute it under certain conditions; see for details.""" tetrisGame = TetrisGame()