Created
November 4, 2014 12:43
-
-
Save caulagi/3bb7701a2b619a421222 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
""" | |
a simple 'text-mode' version of the Tetris game | |
>>> Board(width=12, height=12).start_game() | |
""" | |
import random | |
import re | |
import sys | |
class GameOverException(Exception): | |
pass | |
class InvalidMoveException(Exception): | |
pass | |
class UserInputHandler(): | |
"""Class that prompts and reads user input""" | |
def show_prompt(self): | |
print "\n" | |
print "Please make a move:" | |
print " a: move piece left" | |
print " d: move piece right" | |
print " w: rotate piece counter clockwise" | |
print " s: rotate piece clockwise" | |
print " <space>: do nothing (move piece down)" | |
def get_user_input(self): | |
"""Prompt user for input till a valid choice is entered""" | |
ch = re.match("^(?P<answer>[aAdDwWsS\s])$", raw_input()) | |
if not ch: | |
return self.get_user_input() | |
ch = ch.groupdict()["answer"].lower() | |
return ch if ch in ('a', 'd', 'w', 's') else " " | |
class PieceGenerator(): | |
"""Class that generates a random piece""" | |
def __init__(self): | |
self._pieces = ( | |
# **** | |
((0,0), (0,1), (0,2), (0,3)), | |
# * | |
# * | |
# ** | |
((0,0), (1,0), (2,0), (2,1)), | |
# * | |
# * | |
# ** | |
((0,1), (1,1), (2,0), (2,1)), | |
# * | |
# ** | |
# * | |
((0,1), (1,0), (1,1), (2,0)), | |
# ** | |
# ** | |
((0,0), (0,1), (1,0), (1,1)), | |
) | |
def get_random_piece(self): | |
return random.choice(self._pieces) | |
def rotate_clockwise(self, piece, pos): | |
m = { | |
# **** | |
((0,0), (0,1), (0,2), (0,3)): (((0,0), (1,0), (2,0), (3,0)), pos), | |
((0,0), (1,0), (2,0), (3,0)): (((0,0), (0,1), (0,2), (0,3)), (pos[0],pos[1]-3)), | |
# * | |
# * | |
# ** | |
((0,0), (1,0), (2,0), (2,1)): (((0,0), (0,1), (0,2), (1,0)), pos), | |
((0,0), (0,1), (0,2), (1,0)): (((0,0), (0,1), (1,1), (2,1)), pos), | |
((0,0), (0,1), (1,1), (2,1)): (((1,0), (1,1), (1,2), (0,2)), pos), | |
((1,0), (1,1), (1,2), (0,2)): (((0,0), (1,0), (2,0), (2,1)), pos), | |
# * | |
# * | |
# ** | |
((0,1), (1,1), (2,0), (2,1)): (((0,0), (1,0), (1,1), (1,2)), pos), | |
((0,0), (1,0), (1,1), (1,2)): (((0,0), (1,0), (2,0), (0,1)), pos), | |
((0,0), (1,0), (2,0), (0,1)): (((0,0), (0,1), (0,2), (1,2)), pos), | |
((0,0), (0,1), (0,2), (1,2)): (((0,1), (1,1), (2,0), (2,1)), pos), | |
# * | |
# ** | |
# * | |
((0,1), (1,0), (1,1), (2,0)): (((0,0), (0,1), (1,1), (1,2)), pos), | |
((0,0), (0,1), (1,1), (1,2)): (((0,1), (1,0), (1,1), (2,0)), pos), | |
# ** | |
# ** | |
((0,0), (0,1), (1,0), (1,1)): (((0,0), (0,1), (1,0), (1,1)), pos), | |
} | |
return m[piece] | |
class Board(): | |
"""A tetris board that can play the game and draw the board""" | |
def __init__(self, **kwargs): | |
self._WIDTH = kwargs.get("width", 20) | |
self._HEIGHT = kwargs.get("height", 20) | |
self._board = self._init_board() | |
self._piece_generator = PieceGenerator() | |
self._input_handler = UserInputHandler() | |
self._add_new_piece() | |
self._draw_board() | |
def handle_move(self, move): | |
"""Update the game with a move that has been made""" | |
if not self._can_move_down(): | |
return self._add_new_piece() | |
self._blank_current_pos() | |
if move == 'a': | |
return self.move_left() | |
elif move == 'd': | |
return self.move_right() | |
elif move == 's': | |
return self.rotate_clockwise() | |
elif move == 'w': | |
return self.rotate_counter_clockwise() | |
elif move == ' ': | |
return self.move_down() | |
raise InvalidMoveException("Not a valid move") | |
def move_left(self): | |
"""Move left and then move down. If moving left is not possible, | |
just move down""" | |
m, n = self._current_pos | |
if not self._can_move_left(): | |
return self._move_piece_down() | |
self._current_pos = (m, n-1) | |
return self._move_piece_down() | |
def move_right(self): | |
"""Move right and then move down. If moving right is not possible, | |
just move down""" | |
m, n = self._current_pos | |
if not self._can_move_right(): | |
return self._move_piece_down() | |
self._current_pos = (m, n+1) | |
return self._move_piece_down() | |
def rotate_clockwise(self): | |
"""If possible, rotate the piece clockwise. Then move down""" | |
if not self._can_rotate_clockwise(): | |
return self._move_piece_down() | |
self._current_piece, self._current_pos = \ | |
self._piece_generator.rotate_clockwise(self._current_piece, self._current_pos) | |
return self._move_piece_down() | |
def rotate_counter_clockwise(self): | |
a, b = self._piece_generator.rotate_clockwise(self._current_piece, self._current_pos) | |
a, b = self._piece_generator.rotate_clockwise(a, b) | |
if not self._can_rotate_clockwise(): | |
return self._move_piece_down() | |
self._current_piece, self._current_pos = self._piece_generator.rotate_clockwise(a, b) | |
return self._move_piece_down() | |
def move_down(self): | |
"""Move the current piece down if possible or add a new piece""" | |
if not self._can_move_down(): | |
return self._add_new_piece() | |
self._blank_current_pos() | |
return self._move_piece_down() | |
def start_game(self): | |
while self.is_valid(): | |
try: | |
self._input_handler.show_prompt() | |
self.handle_move(self._input_handler.get_user_input()) | |
self._draw_board() | |
except GameOverException: | |
print "\n GAME OVER!!\n" | |
sys.exit(0) | |
def is_valid(self): | |
"""Whether the board is valid""" | |
return True | |
def _init_board(self, **kwargs): | |
_board = [] | |
for i in xrange(self._HEIGHT): | |
_board.append([]) | |
for j in xrange(self._WIDTH): | |
_board[i].append(".") | |
return _board | |
def _add_new_piece(self): | |
"""Add a random new piece to the board""" | |
y = random.choice(range(self._WIDTH)) | |
piece = self._piece_generator.get_random_piece() | |
for pos in piece: | |
try: | |
if self._board[pos[0]][pos[1] + y] == 'X': | |
raise GameOverException('Game over!') | |
except IndexError: | |
# the piece and pos combination is invalid, try again | |
return self._add_new_piece() | |
for pos in piece: | |
self._board[pos[0]][pos[1] + y] = 'X' | |
self._current_piece = piece | |
self._current_pos = (0, y) | |
def _get_random_pos(self): | |
"""Get a random x coordinate for the new piece""" | |
return random.choice(range(self._WIDTH)) | |
def _can_move_left(self): | |
m, n = self._current_pos | |
if n - 1 < 0: | |
return False | |
for (x, y) in self._current_piece: | |
if self._board[m+x-1][n+y] == 'X': | |
return False | |
return True | |
def _can_move_right(self): | |
m, n = self._current_pos | |
for (_, y) in self._current_piece: | |
if n + y + 1 >= self._WIDTH: | |
return False | |
for (x, y) in self._current_piece: | |
if self._board[m+x][n+y+1] == 'X': | |
return False | |
return True | |
def _can_move_down(self): | |
max_x = 0 | |
for (x, _) in self._current_piece[::-1]: | |
if x > max_x: | |
max_x = x | |
m, n = self._current_pos | |
try: | |
for (_, y) in self._current_piece[::-1]: | |
if self._board[m+1+max_x][n+y] == 'X': | |
return False | |
except IndexError: | |
return False | |
return True | |
def _can_rotate_clockwise(self): | |
m, n = self._current_pos | |
pieces, (_, q) = self._piece_generator.rotate_clockwise(self._current_piece, self._current_pos) | |
for (x, y) in pieces: | |
if x < 0 or y < 0 or q < 0: | |
return False | |
if (m + x + 1 >= self._WIDTH) or (n + y >= self._HEIGHT): | |
return False | |
return True | |
def _can_rotate_counter_clockwise(self): | |
pass | |
def _blank_current_pos(self): | |
"""Mark all squares of current pos as unoccupied (prior to a move)""" | |
m, n = self._current_pos | |
for (x, y) in self._current_piece[::-1]: | |
self._board[m+x][n+y] = '.' | |
def _move_piece_down(self): | |
"""Move the current piece down 1 level""" | |
m, n = self._current_pos | |
for (x, y) in self._current_piece[::-1]: | |
self._board[m+1+x][n+y] = 'X' | |
self._current_pos = (m+1, n) | |
def _draw_board(self): | |
for i in xrange(self._HEIGHT): | |
print " ".join(self._board[i]) | |
if __name__ == "__main__": | |
Board(width=12, height=12).start_game() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment