added first draft litris
This commit is contained in:
149
field.py
Normal file
149
field.py
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
from tetromino import Tetromino
|
||||||
|
|
||||||
|
class Field():
|
||||||
|
|
||||||
|
WIDTH = 20
|
||||||
|
HEIGHT = 20
|
||||||
|
|
||||||
|
def __init__(self, state=None):
|
||||||
|
if state:
|
||||||
|
self.state = state
|
||||||
|
else:
|
||||||
|
self.state = [[' ' for cols in range(Field.WIDTH)]
|
||||||
|
for rows in range(Field.HEIGHT)]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
BAR = ' ' + '-' * (Field.WIDTH * 2 + 1) + '\n ' + \
|
||||||
|
' '.join(map(str, range(Field.WIDTH))) + '\n'
|
||||||
|
return BAR + '\n'.join([
|
||||||
|
'{:2d} |'.format(i) + ' '.join(row) + '|'
|
||||||
|
for i, row in enumerate(self.state)]) + '\n' + BAR
|
||||||
|
|
||||||
|
def _test_tetromino(self, tetromino, row, column):
|
||||||
|
"""
|
||||||
|
Tests to see if a tetromino can be placed at the specified row and
|
||||||
|
column. It performs the test with the bottom left corner of the
|
||||||
|
tetromino at the specified row and column.
|
||||||
|
"""
|
||||||
|
assert column >= 0
|
||||||
|
assert column + tetromino.width() <= Field.WIDTH
|
||||||
|
assert row - tetromino.height() + 1 >= 0
|
||||||
|
assert row < Field.HEIGHT
|
||||||
|
for ti, si in list(enumerate(range(row - tetromino.height() + 1,
|
||||||
|
row + 1)))[::-1]:
|
||||||
|
for tj, sj in enumerate(range(column, column + tetromino.width())):
|
||||||
|
if tetromino[ti][tj] != ' ' and self.state[si][sj] != ' ':
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _place_tetromino(self, tetromino, row, column):
|
||||||
|
"""
|
||||||
|
Place a tetromino at the specified row and column.
|
||||||
|
The bottom left corner of the tetromino will be placed at the specified
|
||||||
|
row and column. This function does not perform checks and will overwrite
|
||||||
|
filled spaces in the field.
|
||||||
|
"""
|
||||||
|
assert column >= 0
|
||||||
|
assert column + tetromino.width() <= Field.WIDTH
|
||||||
|
assert row - tetromino.height() + 1 >= 0
|
||||||
|
assert row < Field.HEIGHT
|
||||||
|
for ti, si in list(enumerate(range(row - tetromino.height() + 1,
|
||||||
|
row + 1)))[::-1]:
|
||||||
|
for tj, sj in enumerate(range(column, column + tetromino.width())):
|
||||||
|
if tetromino[ti][tj] != ' ':
|
||||||
|
self.state[si][sj] = tetromino[ti][tj]
|
||||||
|
|
||||||
|
def _get_tetromino_drop_row(self, tetromino, column):
|
||||||
|
"""
|
||||||
|
Given a tetromino and a column, return the row that the tetromino
|
||||||
|
would end up in if it were dropped in that column.
|
||||||
|
Assumes the leftmost column of the tetromino will be aligned with the
|
||||||
|
specified column.
|
||||||
|
"""
|
||||||
|
assert isinstance(tetromino, Tetromino)
|
||||||
|
assert column >= 0
|
||||||
|
assert column + tetromino.width() <= Field.WIDTH
|
||||||
|
last_fit = -1
|
||||||
|
for row in range(tetromino.height(), Field.HEIGHT):
|
||||||
|
if self._test_tetromino(tetromino, row, column):
|
||||||
|
last_fit = row
|
||||||
|
else:
|
||||||
|
return last_fit
|
||||||
|
return last_fit
|
||||||
|
|
||||||
|
def _line_clear(self):
|
||||||
|
"""
|
||||||
|
Checks and removes all filled lines.
|
||||||
|
"""
|
||||||
|
self.state = list(filter(lambda row: row.count(' ') != 0, self.state))
|
||||||
|
while len(self.state) < Field.HEIGHT:
|
||||||
|
self.state.insert(0, [' ' for col in range(Field.WIDTH)])
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
"""
|
||||||
|
Returns a shallow copy of the field.
|
||||||
|
"""
|
||||||
|
return Field([row[:] for row in self.state])
|
||||||
|
|
||||||
|
def drop(self, tetromino, column):
|
||||||
|
"""
|
||||||
|
Drops a tetromino in the specified column.
|
||||||
|
The leftmost column of the tetromino will be aligned with the specified
|
||||||
|
column.
|
||||||
|
Returns the row it was dropped in for computations.
|
||||||
|
"""
|
||||||
|
assert isinstance(tetromino, Tetromino)
|
||||||
|
assert column >= 0
|
||||||
|
assert column + tetromino.width() <= Field.WIDTH
|
||||||
|
row = self._get_tetromino_drop_row(tetromino, column)
|
||||||
|
assert row != -1
|
||||||
|
self._place_tetromino(tetromino, row, column)
|
||||||
|
self._line_clear()
|
||||||
|
return row
|
||||||
|
|
||||||
|
def count_gaps(self):
|
||||||
|
"""
|
||||||
|
Check each column one by one to make sure there are no gaps in the
|
||||||
|
column.
|
||||||
|
"""
|
||||||
|
return sum(
|
||||||
|
["".join([row[col] for row in self.state]).lstrip().count(' ')
|
||||||
|
for col in range(Field.WIDTH)])
|
||||||
|
|
||||||
|
def height(self):
|
||||||
|
"""
|
||||||
|
Returns the height on the field of the highest placed tetromino on the
|
||||||
|
field.
|
||||||
|
"""
|
||||||
|
for i, row in enumerate(self.state):
|
||||||
|
if ''.join(row).strip():
|
||||||
|
return Field.HEIGHT - i
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
f = Field()
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1] == 'sim':
|
||||||
|
from optimizer import Optimizer
|
||||||
|
i = input()
|
||||||
|
while i != 'q':
|
||||||
|
t = Tetromino.create(i)
|
||||||
|
opt = Optimizer.get_optimal_drop(f, t)
|
||||||
|
t.rotate(opt['orientation'])
|
||||||
|
f.drop(t, opt['column'])
|
||||||
|
print(f)
|
||||||
|
i = input()
|
||||||
|
t = Tetromino.JTetromino().rotate_right()
|
||||||
|
print(t)
|
||||||
|
f.drop(t, 0)
|
||||||
|
print(f)
|
||||||
|
# f.drop(Tetromino.LTetromino(), 2)
|
||||||
|
# print(f)
|
||||||
|
# f.drop(Tetromino.JTetromino().rotate_left(), 5)
|
||||||
|
# print(f)
|
||||||
|
# t = Tetromino.LTetromino().flip()
|
||||||
|
# f.drop(t, 0)
|
||||||
|
# f.drop(Tetromino.TTetromino().flip(), 0)
|
||||||
|
# f.drop(Tetromino.JTetromino(), 4)
|
||||||
|
# print(f)
|
||||||
89
litris.py
89
litris.py
@@ -35,6 +35,10 @@ from utils import mse
|
|||||||
from game_base_class import GameBase
|
from game_base_class import GameBase
|
||||||
import random
|
import random
|
||||||
from pynput.keyboard import Key, Controller
|
from pynput.keyboard import Key, Controller
|
||||||
|
from field import Field
|
||||||
|
from tetromino import Tetromino
|
||||||
|
from optimizer import Optimizer
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
BLOCK_FULL = [[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]]
|
BLOCK_FULL = [[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]]
|
||||||
@@ -89,24 +93,14 @@ class Litris(GameBase):
|
|||||||
|
|
||||||
self.fill_data_coordinates()
|
self.fill_data_coordinates()
|
||||||
|
|
||||||
|
self.field = Field()
|
||||||
|
|
||||||
#self.sd_reset_board = cv.imread("control_elements/sodoku_reset_button.jpg", cv.IMREAD_COLOR)
|
#self.sd_reset_board = cv.imread("control_elements/sodoku_reset_button.jpg", cv.IMREAD_COLOR)
|
||||||
|
|
||||||
self.needles = {1: cv.imread("litris/blue_needle.jpg", cv.IMREAD_COLOR)
|
self.needles = {1: cv.imread("litris/blue_needle.jpg", cv.IMREAD_COLOR)
|
||||||
# 2: cv.imread("sodoku/2.jpg", cv.IMREAD_COLOR),
|
# 2: cv.imread("sodoku/2.jpg", cv.IMREAD_COLOR),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.full_stones_dic = {1: BLOCK_FULL,
|
|
||||||
2: L1_FULL,
|
|
||||||
3: LINE_FULL,
|
|
||||||
4: DOT_FULL
|
|
||||||
}
|
|
||||||
|
|
||||||
self.col_stones_dic = {1: BLOCK_COL,
|
|
||||||
2: L1_COL,
|
|
||||||
3: LINE_COL,
|
|
||||||
4: DOT_COL
|
|
||||||
}
|
|
||||||
|
|
||||||
def fill_data_coordinates(self):
|
def fill_data_coordinates(self):
|
||||||
# 610 to 1950 = 1340 - 76 / 20 = 63
|
# 610 to 1950 = 1340 - 76 / 20 = 63
|
||||||
# 40 to 1380 = 1340 - 76 / 20 = 63
|
# 40 to 1380 = 1340 - 76 / 20 = 63
|
||||||
@@ -126,14 +120,31 @@ class Litris(GameBase):
|
|||||||
|
|
||||||
#if self.check_for_button_and_execute(self.capture_window.get_screenshot(), self.sd_reset_board):
|
#if self.check_for_button_and_execute(self.capture_window.get_screenshot(), self.sd_reset_board):
|
||||||
# cv.waitKey(2000)
|
# cv.waitKey(2000)
|
||||||
|
#current_stone = self.new_stone_detection_and_identification()
|
||||||
|
#new_observation, new_screenshot = self.get_current_board_state()
|
||||||
|
#col = self.find_place_for_stone(current_stone, new_observation)
|
||||||
|
#self.move_stone(col)
|
||||||
|
#field = Field()
|
||||||
|
|
||||||
|
#current_tetromino = Tetromino.create("O")
|
||||||
|
#next_tetromino = None
|
||||||
|
#time.sleep(2)
|
||||||
|
#stone_list = ["L","S","Z","J"]
|
||||||
|
#ier = 0
|
||||||
|
|
||||||
current_stone = self.new_stone_detection_and_identification()
|
current_stone = self.new_stone_detection_and_identification()
|
||||||
new_observation, new_screenshot = self.get_current_board_state()
|
current_tetromino = Tetromino.create(self.get_letter_for_stone(current_stone))
|
||||||
col = self.find_place_for_stone(current_stone, new_observation)
|
opt = Optimizer.get_optimal_drop(self.field, current_tetromino)
|
||||||
self.move_stone(col)
|
rotation = opt['tetromino_rotation']
|
||||||
|
column = opt['tetromino_column']
|
||||||
|
current_tetromino.rotate(rotation)
|
||||||
|
offset_col = current_tetromino.get_offset_column(rotation)
|
||||||
|
self.field.drop(current_tetromino, column)
|
||||||
|
self.move_stone(column - offset_col, rotation)
|
||||||
|
|
||||||
|
#time.sleep(0.2)
|
||||||
self.observation = new_observation
|
#self.observation = new_observation
|
||||||
return new_observation
|
#return new_observation
|
||||||
|
|
||||||
def get_current_board_state(self):
|
def get_current_board_state(self):
|
||||||
# get an updated image of the game
|
# get an updated image of the game
|
||||||
@@ -203,6 +214,32 @@ class Litris(GameBase):
|
|||||||
#cv.waitKey(150)
|
#cv.waitKey(150)
|
||||||
return stone_coords
|
return stone_coords
|
||||||
|
|
||||||
|
def get_letter_for_stone(self, stone):
|
||||||
|
|
||||||
|
if np.array_equal(stone, BLOCK_FULL):
|
||||||
|
return "O"
|
||||||
|
elif np.array_equal(stone, BL3_FULL):
|
||||||
|
return "D"
|
||||||
|
elif np.array_equal(stone, L1_FULL):
|
||||||
|
return "L"
|
||||||
|
elif np.array_equal(stone, L2_FULL):
|
||||||
|
return "J"
|
||||||
|
elif np.array_equal(stone, LINE_FULL):
|
||||||
|
return "I"
|
||||||
|
elif np.array_equal(stone, DOT_FULL):
|
||||||
|
return "C"
|
||||||
|
elif np.array_equal(stone, DDOT_FULL) :
|
||||||
|
return "B"
|
||||||
|
elif np.array_equal(stone, DDDOT_FULL) :
|
||||||
|
return "A"
|
||||||
|
elif np.array_equal(stone, Z1_FULL):
|
||||||
|
return "S"
|
||||||
|
elif np.array_equal(stone, Z2_FULL):
|
||||||
|
return "Z"
|
||||||
|
elif np.array_equal(stone, T1_FULL):
|
||||||
|
return "T"
|
||||||
|
|
||||||
|
|
||||||
def find_place_for_stone(self, stone, current_board):
|
def find_place_for_stone(self, stone, current_board):
|
||||||
|
|
||||||
if np.array_equal(stone, BLOCK_FULL):
|
if np.array_equal(stone, BLOCK_FULL):
|
||||||
@@ -291,13 +328,27 @@ class Litris(GameBase):
|
|||||||
for i in range(0, 18, 1):
|
for i in range(0, 18, 1):
|
||||||
if current_board[e][i] == 1 and current_board[e][i + 1] == 0 and current_board[e][i + 2] == 1 and current_board[e - 1][i] == 0 and current_board[e - 1][i + 1] == 0 and current_board[e - 1][i + 2] == 0:
|
if current_board[e][i] == 1 and current_board[e][i + 1] == 0 and current_board[e][i + 2] == 1 and current_board[e - 1][i] == 0 and current_board[e - 1][i + 1] == 0 and current_board[e - 1][i + 2] == 0:
|
||||||
return i - T1_COL
|
return i - T1_COL
|
||||||
def move_stone(self, col_movement):
|
def move_stone(self, col_movement, rotation):
|
||||||
if col_movement is None:
|
if col_movement is None:
|
||||||
return
|
return
|
||||||
# Press and release space
|
# Press and release space
|
||||||
self.keyboard.press(Key.down)
|
self.keyboard.press(Key.down)
|
||||||
self.keyboard.release(Key.down)
|
self.keyboard.release(Key.down)
|
||||||
cv.waitKey(250)
|
cv.waitKey(250)
|
||||||
|
|
||||||
|
if rotation == 1:
|
||||||
|
self.keyboard.press('e')
|
||||||
|
self.keyboard.release('e')
|
||||||
|
elif rotation == 2:
|
||||||
|
self.keyboard.press('e')
|
||||||
|
self.keyboard.release('e')
|
||||||
|
cv.waitKey(40)
|
||||||
|
self.keyboard.press('e')
|
||||||
|
self.keyboard.release('e')
|
||||||
|
elif rotation == 3:
|
||||||
|
self.keyboard.press('q')
|
||||||
|
self.keyboard.release('q')
|
||||||
|
|
||||||
if col_movement < 0:
|
if col_movement < 0:
|
||||||
for i in range(0, col_movement, - 1):
|
for i in range(0, col_movement, - 1):
|
||||||
self.keyboard.press(Key.left)
|
self.keyboard.press(Key.left)
|
||||||
|
|||||||
86
optimizer.py
Normal file
86
optimizer.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
from field import Field
|
||||||
|
from tetromino import Tetromino
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from functools import cmp_to_key
|
||||||
|
|
||||||
|
class Optimizer():
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_optimal_drop(field, tetromino):
|
||||||
|
rotations = [
|
||||||
|
tetromino,
|
||||||
|
tetromino.copy().rotate_right(),
|
||||||
|
tetromino.copy().flip(),
|
||||||
|
tetromino.copy().rotate_left(),
|
||||||
|
]
|
||||||
|
drops = []
|
||||||
|
for rotation, tetromino_ in enumerate(rotations):
|
||||||
|
for column in range(Field.WIDTH):
|
||||||
|
try:
|
||||||
|
f = field.copy()
|
||||||
|
row = f.drop(tetromino_, column)
|
||||||
|
drops.append({
|
||||||
|
'field': f,
|
||||||
|
'field_gaps': f.count_gaps(),
|
||||||
|
'field_height': f.height(),
|
||||||
|
'tetromino_rotation': rotation,
|
||||||
|
'tetromino_column': column,
|
||||||
|
'tetromino_row': row
|
||||||
|
})
|
||||||
|
except AssertionError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# First, we pick out all the drops that will produce the least
|
||||||
|
# amount of gaps.
|
||||||
|
lowest_gaps = min([drop['field_gaps'] for drop in drops])
|
||||||
|
drops = list(filter(
|
||||||
|
lambda drop: drop['field_gaps'] == lowest_gaps, drops))
|
||||||
|
# Next we sort for the ones with the lowest field height.
|
||||||
|
lowest_height = min([drop['field_height'] for drop in drops])
|
||||||
|
drops = list(filter(
|
||||||
|
lambda drop: drop['field_height'] == lowest_height, drops))
|
||||||
|
# Finally, we sort for the ones that drop the tetromino in the lowest
|
||||||
|
# row. Since rows increase from top to bottom, we use max() instead.
|
||||||
|
lowest_row = max([drop['tetromino_row'] for drop in drops])
|
||||||
|
drops = list(filter(
|
||||||
|
lambda drop: drop['tetromino_row'] == lowest_row, drops))
|
||||||
|
assert len(drops) > 0
|
||||||
|
return drops[0]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_keystrokes(rotation, column, keymap):
|
||||||
|
keys = []
|
||||||
|
# First we orient the tetronimo
|
||||||
|
if rotation == 1:
|
||||||
|
keys.append(keymap['rotate_right'])
|
||||||
|
elif rotation == 2:
|
||||||
|
keys.append(keymap['rotate_right'])
|
||||||
|
keys.append(keymap['rotate_right'])
|
||||||
|
elif rotation == 3:
|
||||||
|
keys.append(keymap['rotate_left'])
|
||||||
|
# Then we move it all the way to the the left that we are guaranteed
|
||||||
|
# that it is at column 0. The main reason for doing this is that when
|
||||||
|
# the tetromino is rotated, the bottom-leftmost piece in the tetromino
|
||||||
|
# may not be in the 3rd column due to the way Tetris rotates the piece
|
||||||
|
# about a specific point. There are too many edge cases so instead of
|
||||||
|
# implementing tetromino rotation on the board, it's easier to just
|
||||||
|
# flush all the pieces to the left after orienting them.
|
||||||
|
for i in range(4):
|
||||||
|
keys.append(keymap['move_left'])
|
||||||
|
# Now we can move it back to the correct column. Since pyautogui's
|
||||||
|
# typewrite is instantaneous, we don't have to worry about the delay
|
||||||
|
# from moving it all the way to the left.
|
||||||
|
for i in range(column):
|
||||||
|
keys.append(keymap['move_right'])
|
||||||
|
keys.append(keymap['drop'])
|
||||||
|
return keys
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
f = Field()
|
||||||
|
f.drop(Tetromino.TTetromino(), 3)
|
||||||
|
opt = Optimizer.get_optimal_drop(
|
||||||
|
f['tetromino_rotation'], f['tetromino_column'], Tetromino.ITetromino())
|
||||||
|
print(opt['field'])
|
||||||
202
tetromino.py
Normal file
202
tetromino.py
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
class Tetromino():
|
||||||
|
|
||||||
|
TYPES = ['i','a', 'b', 'c', 'o', 'd', 't', 's', 'z', 'j', 'l']
|
||||||
|
|
||||||
|
def __init__(self, state, letter):
|
||||||
|
# assert that there are rows
|
||||||
|
assert len(state) > 0
|
||||||
|
# assert rows and columns form a rectangle
|
||||||
|
assert len({len(row) for row in state}) == 1
|
||||||
|
self.state = state
|
||||||
|
self.letter = letter
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ITetromino(): #ok
|
||||||
|
return Tetromino(
|
||||||
|
[
|
||||||
|
['I', 'I', 'I', 'I']
|
||||||
|
],
|
||||||
|
'i'
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ATetromino(): #ok
|
||||||
|
return Tetromino(
|
||||||
|
[
|
||||||
|
['I', 'I', 'I']
|
||||||
|
],
|
||||||
|
'a'
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def BTetromino(): #ok
|
||||||
|
return Tetromino(
|
||||||
|
[
|
||||||
|
['I', 'I',]
|
||||||
|
],
|
||||||
|
'b'
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def CTetromino(): #ok
|
||||||
|
return Tetromino(
|
||||||
|
[
|
||||||
|
['I']
|
||||||
|
],
|
||||||
|
'c'
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def OTetromino(): #ok
|
||||||
|
return Tetromino(
|
||||||
|
[
|
||||||
|
['O', 'O'],
|
||||||
|
['O', 'O']
|
||||||
|
],
|
||||||
|
'o'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def DTetromino(): #ok
|
||||||
|
return Tetromino(
|
||||||
|
[
|
||||||
|
['O', 'O'],
|
||||||
|
[' ', 'O']
|
||||||
|
],
|
||||||
|
'd'
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def TTetromino(): #ok
|
||||||
|
return Tetromino(
|
||||||
|
[
|
||||||
|
['T', 'T', 'T'],
|
||||||
|
[' ', 'T', ' ']
|
||||||
|
],
|
||||||
|
't'
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def STetromino(): #ok
|
||||||
|
return Tetromino(
|
||||||
|
[
|
||||||
|
[' ', 'S', 'S'],
|
||||||
|
['S', 'S', ' ']
|
||||||
|
],
|
||||||
|
's'
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ZTetromino(): #ok
|
||||||
|
return Tetromino(
|
||||||
|
[
|
||||||
|
['Z', 'Z', ' '],
|
||||||
|
[' ', 'Z', 'Z']
|
||||||
|
],
|
||||||
|
'z'
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def JTetromino(): #ok
|
||||||
|
return Tetromino(
|
||||||
|
[
|
||||||
|
['J', 'J', 'J'],
|
||||||
|
[' ', ' ', 'J']
|
||||||
|
],
|
||||||
|
'j'
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def LTetromino():
|
||||||
|
return Tetromino(
|
||||||
|
[
|
||||||
|
[' ', ' ', 'L'],
|
||||||
|
['L', 'L', 'L']
|
||||||
|
],
|
||||||
|
'l'
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create(letter):
|
||||||
|
assert letter.lower() in Tetromino.TYPES
|
||||||
|
return getattr(Tetromino, '{}Tetromino'.format(letter.upper()))()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "\n".join(["".join(x) for x in self.state])
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.state[key]
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return Tetromino([row[:] for row in self.state], self.letter)
|
||||||
|
|
||||||
|
def width(self):
|
||||||
|
return len(self.state[0])
|
||||||
|
|
||||||
|
def height(self):
|
||||||
|
return len(self.state)
|
||||||
|
|
||||||
|
def rotate(self, change):
|
||||||
|
while change < 0:
|
||||||
|
change += 4
|
||||||
|
change = (change % 4)
|
||||||
|
assert 0 <= change and change <= 3
|
||||||
|
if change == 0:
|
||||||
|
return None
|
||||||
|
elif change == 1:
|
||||||
|
self.rotate_right()
|
||||||
|
elif change == 2:
|
||||||
|
self.flip()
|
||||||
|
elif change == 3:
|
||||||
|
self.rotate_left()
|
||||||
|
else:
|
||||||
|
raise Exception('This should never happen!')
|
||||||
|
|
||||||
|
def rotate_right(self):
|
||||||
|
self.state = list(zip(*self.state[::-1]))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def rotate_left(self):
|
||||||
|
self.state = list(reversed(list(zip(*self.state))))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def flip(self):
|
||||||
|
self.state = [row[::-1] for row in self.state[::-1]]
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_offset_column(self, rotation):
|
||||||
|
offset_map= {
|
||||||
|
'i': {0: 10, 1: 9, 2: 10, 3: 9},
|
||||||
|
'a': {0: 9, 1: 9, 2: 9, 3: 9},
|
||||||
|
'b': {0: 9, 1: 8, 2: 10, 3: 8},
|
||||||
|
'c': {0: 8, 1: 8, 2: 8, 3: 8},
|
||||||
|
'o': {0: 9, 1: 9, 2: 9, 3: 9},
|
||||||
|
'd': {0: 9, 1: 9, 2: 9, 3: 9},
|
||||||
|
't': {0: 9, 1: 9, 2: 10, 3: 9},
|
||||||
|
's': {0: 9, 1: 9, 2: 9, 3: 9},
|
||||||
|
'z': {0: 9, 1: 9, 2: 9, 3: 9},
|
||||||
|
'j': {0: 9, 1: 8, 2: 8, 3: 8},
|
||||||
|
'l': {0: 9, 1: 8, 2: 8, 3: 8}
|
||||||
|
}
|
||||||
|
return offset_map.get(self.letter)[rotation]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
t = Tetromino.LTetromino()
|
||||||
|
print(t)
|
||||||
|
print()
|
||||||
|
t.rotate_right()
|
||||||
|
print(t)
|
||||||
|
print()
|
||||||
|
t.rotate_right()
|
||||||
|
print(t)
|
||||||
|
print()
|
||||||
|
t.rotate_left()
|
||||||
|
print(t)
|
||||||
|
print(t.height())
|
||||||
|
print(t.width())
|
||||||
|
t.flip()
|
||||||
|
print(t)
|
||||||
Reference in New Issue
Block a user