import collections import numpy as np import tetrominos FIELD_WIDTH = 10 FIELD_HEIGHT = 20 COMMAND_NONE = 0 COMMAND_LEFT = 1 COMMAND_RIGHT = 2 COMMAND_ROTATE = 3 def tetromino_to_field_at(tetromino, row, col): col_offset = col + tetromino.col_offset row_offset = row + tetromino.row_offset if col_offset < 0 or row_offset < 0: return None if col_offset + tetromino.data.shape[1] > FIELD_WIDTH: return None if row_offset + tetromino.data.shape[0] > FIELD_HEIGHT: return None array = tetromino.data left_zeros = np.zeros(shape=(array.shape[0], col_offset), dtype=np.byte, order="C") right_zeros = np.zeros(shape=(array.shape[0], FIELD_WIDTH-col_offset-array.shape[1]), dtype=np.byte, order="C") array = np.concatenate((left_zeros, array, right_zeros), axis=1) top_zeros = np.zeros(shape=(row_offset,FIELD_WIDTH), dtype=np.byte, order="C") bottom_zeros = np.zeros(shape=(FIELD_HEIGHT - row_offset - array.shape[0], FIELD_WIDTH), dtype=np.byte, order="C") array = np.concatenate((top_zeros, array, bottom_zeros), axis=0) return array GameStateData = collections.namedtuple('GameStateData', ['grid', 'current_piece', 'next_piece', 'rotation', 'row', 'col', 'score']) class GameState: def __init__(self): self.data = None self.next_piece_func = None def is_valid(self): tetromino_array = tetromino_to_field_at( tetrominos.SHAPES[self.data.current_piece][self.data.rotation], self.data.row, self.data.col) if tetromino_array is None: return False total_field = np.add(self.data.grid, tetromino_array) if np.any(np.vectorize(lambda x: x > 1)(total_field)): return False return True def get_field(self): tetromino_array = tetromino_to_field_at( tetrominos.SHAPES[self.data.current_piece][self.data.rotation], self.data.row, self.data.col) total_field = np.add(self.data.grid, tetromino_array) total_field = np.add(total_field, tetromino_array) return total_field def shift(self, col_offset): next_game_state = GameState() next_game_state.next_piece_func = self.next_piece_func next_game_state.data = GameStateData( grid=self.data.grid, current_piece=self.data.current_piece, next_piece=self.data.next_piece, rotation=self.data.rotation, row=self.data.row, col=self.data.col + col_offset, score=self.data.score) if next_game_state.is_valid(): return next_game_state return None def rotate(self): next_game_state = GameState() next_game_state.next_piece_func = self.next_piece_func next_game_state.data = GameStateData( grid=self.data.grid, current_piece=self.data.current_piece, next_piece=self.data.next_piece, rotation=(self.data.rotation + 1) % 4, row=self.data.row, col=self.data.col, score=self.data.score) if next_game_state.is_valid(): return next_game_state return None def land(self): next_game_state = GameState() next_game_state.next_piece_func = self.next_piece_func next_game_state.data = GameStateData( grid=self.data.grid, current_piece=self.data.current_piece, next_piece=self.data.next_piece, rotation=self.data.rotation, row=self.data.row, col=self.data.col, score=self.data.score) try_game_state = GameState() drop_count = 0 while True: try_game_state.data = GameStateData( grid=next_game_state.data.grid, current_piece=next_game_state.data.current_piece, next_piece=next_game_state.data.next_piece, rotation=next_game_state.data.rotation, row=next_game_state.data.row + 1, col=next_game_state.data.col, score=next_game_state.data.score) if try_game_state.is_valid(): next_game_state.data = try_game_state.data drop_count += 1 else: break next_game_state.data = GameStateData( grid=next_game_state.data.grid, current_piece=next_game_state.data.current_piece, next_piece=next_game_state.data.next_piece, rotation=next_game_state.data.rotation, row=next_game_state.data.row, col=next_game_state.data.col, score=next_game_state.data.score + drop_count*2) return next_game_state def _advance_down(self): next_game_state = GameState() next_game_state.next_piece_func = self.next_piece_func next_game_state.data = GameStateData( grid=self.data.grid, current_piece=self.data.current_piece, next_piece=self.data.next_piece, rotation=self.data.rotation, row=self.data.row + 1, col=self.data.col, score=self.data.score) if next_game_state.is_valid(): return next_game_state return None def _advance_piece(self): tetromino_array = tetromino_to_field_at( tetrominos.SHAPES[self.data.current_piece][self.data.rotation], self.data.row, self.data.col) next_grid = np.add(self.data.grid, tetromino_array) next_game_state = GameState() next_game_state.next_piece_func = self.next_piece_func next_game_state.data = GameStateData( grid=next_grid, current_piece=self.data.next_piece, next_piece=self.next_piece_func(), rotation=0, row=0, col=0, score=self.data.score) return next_game_state def _advance_purge_lines(self): next_grid_row = FIELD_HEIGHT-1 next_grid = np.zeros(shape=(FIELD_HEIGHT,FIELD_WIDTH), dtype=np.byte, order="C") lines_purged = 0 for row in range(FIELD_HEIGHT-1, -1,-1): if np.all(self.data.grid[row,:]): lines_purged += 1 else: next_grid[next_grid_row,:] = self.data.grid[row,:] next_grid_row -= 1 lines_scores = [0, 40, 100, 300, 1200] next_score = self.data.score + lines_scores[lines_purged] next_game_state = GameState() next_game_state.next_piece_func = self.next_piece_func next_game_state.data = GameStateData( grid=next_grid, current_piece=self.data.current_piece, next_piece=self.data.next_piece, rotation=self.data.rotation, row=self.data.row, col=self.data.col, score=next_score) return next_game_state def advance(self): next_game_state = self._advance_down() is_game_running = True if next_game_state is None: next_game_state = self._advance_piece() next_game_state = next_game_state._advance_purge_lines() if not next_game_state.is_valid(): is_game_running = False if not is_game_running: return self, False return next_game_state, True def create_initial_game_state(next_piece_func): game_state = GameState() game_state.next_piece_func = next_piece_func game_state.data = GameStateData( grid=np.zeros(shape=(FIELD_HEIGHT,FIELD_WIDTH), dtype=np.byte, order="C"), current_piece=next_piece_func(), next_piece=next_piece_func(), rotation=0, row=0, col=0, score=0) return game_state