Skip to content

Instantly share code, notes, and snippets.

@caulagi
Created November 4, 2014 12:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save caulagi/3bb7701a2b619a421222 to your computer and use it in GitHub Desktop.
Save caulagi/3bb7701a2b619a421222 to your computer and use it in GitHub Desktop.
# -*- 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