|
@@ -0,0 +1,206 @@
|
|
|
|
|
+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()
|
|
|
|
|
+ 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
|
|
|
|
|
+ else:
|
|
|
|
|
+ break
|
|
|
|
|
+ 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")
|
|
|
|
|
+ next_score = self.data.score
|
|
|
|
|
+ for row in range(FIELD_HEIGHT-1, -1,-1):
|
|
|
|
|
+ if np.all(self.data.grid[row,:]):
|
|
|
|
|
+ next_score += 1
|
|
|
|
|
+ else:
|
|
|
|
|
+ next_grid[next_grid_row,:] = self.data.grid[row,:]
|
|
|
|
|
+ next_grid_row -= 1
|
|
|
|
|
+
|
|
|
|
|
+ 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
|