267 lines
8.4 KiB
Python
267 lines
8.4 KiB
Python
import curses
|
|
from _curses import error as CursesException
|
|
from random import choice
|
|
|
|
import numpy as np
|
|
|
|
from afutc.pieces import PIECES
|
|
|
|
WIDTH = 20
|
|
HEIGHT = 25
|
|
|
|
|
|
def catch(func, default=..., args=[], kwargs={}):
|
|
try:
|
|
return func(*args, **kwargs)
|
|
except Exception as e:
|
|
if default == ...:
|
|
return e
|
|
else:
|
|
return default
|
|
|
|
|
|
class Afutc:
|
|
running = True
|
|
pause = False
|
|
pivot = [0, 0]
|
|
|
|
def __init__(self, stdscr):
|
|
self.stdscr = stdscr
|
|
self.print = stdscr.addstr
|
|
self.input = stdscr.getkey
|
|
self.board = np.zeros(shape=(HEIGHT, WIDTH))
|
|
self.current_move = np.zeros(shape=(HEIGHT, WIDTH))
|
|
self.ghost = np.zeros(shape=(HEIGHT, WIDTH))
|
|
self.board_center = WIDTH // 2
|
|
self.current_piece = None
|
|
self.next_piece = None
|
|
self.score = 0
|
|
self.char = "▣"
|
|
self.debug = ""
|
|
|
|
def is_valid(self, board):
|
|
for field in (board + self.board).flat:
|
|
if field == 2:
|
|
return False
|
|
return True
|
|
|
|
def get_size(self):
|
|
return self.stdscr.getmaxyx()
|
|
|
|
def draw_board(self):
|
|
_, height = self.get_size()
|
|
height_start = (height // 2) - (WIDTH // 2)
|
|
try:
|
|
print = lambda x: self.print((" " * height_start) + x + "\n")
|
|
row = str(self.score)
|
|
row = f"╣ {row} ╠"
|
|
row = row.center(self.board.shape[1], "═")
|
|
print("ANOTHER F TETRIS CLONE")
|
|
print(f"╔{row}╗")
|
|
for i, row in enumerate(self.board + self.current_move):
|
|
row_str = ""
|
|
for j, field in enumerate(row):
|
|
if self.ghost[i, j]:
|
|
row_str += "▢"
|
|
elif field:
|
|
row_str += self.char
|
|
else:
|
|
row_str += " "
|
|
|
|
row_str = f"║{row_str}║"
|
|
|
|
piece = self.next_piece
|
|
piece_height, piece_width = piece.shape
|
|
|
|
if i == 0:
|
|
row_str += " " + " NEXT".center(piece_width + 4)
|
|
if i == 1:
|
|
row_str += f" ╔{'═' * (piece_width + 2)}╗"
|
|
if i >= 2 and (i - 2) < piece_height:
|
|
row = "".join(self.char if value else " "
|
|
for value in piece[i - 2])
|
|
row_str += f" ║ {row} ║"
|
|
if i == (piece_height + 2):
|
|
row_str += f" ╚{'═' * (piece_width + 2)}╝"
|
|
|
|
print(row_str)
|
|
|
|
row = "═" * (self.board.shape[1])
|
|
print(f"╚{row}╝")
|
|
# print(str(self.pivot) + "\t" + self.debug)
|
|
except CursesException:
|
|
self.pause()
|
|
|
|
def recalculate_ghost(self):
|
|
pivot = self.pivot[:]
|
|
current_piece = self.current_piece.copy()
|
|
self.ghost.fill(0.)
|
|
count = 0
|
|
while True:
|
|
result = self.fuse_matrix(
|
|
self.ghost, current_piece,
|
|
(pivot[1], HEIGHT - current_piece.shape[0] - count))
|
|
if self.is_valid(result):
|
|
self.ghost = result.copy()
|
|
break
|
|
else:
|
|
count += 1
|
|
|
|
def new_random_piece(self):
|
|
# Clear rows completed
|
|
count = 0
|
|
for i, row in enumerate(self.board):
|
|
if all(row):
|
|
count += 1
|
|
upper = self.board[:i]
|
|
down = self.board[i + 1:]
|
|
new_row = np.zeros(shape=(1, WIDTH))
|
|
self.board = np.concatenate((new_row, upper, down))
|
|
self.score += (10 * count) + (5 if count > 3 else 0)
|
|
|
|
# Generate a new piece
|
|
self.current_move.fill(0.)
|
|
if not isinstance(self.current_piece, np.ndarray):
|
|
self.current_piece = np.array(choice(PIECES))
|
|
else:
|
|
self.current_piece = self.next_piece.copy()
|
|
new_piece = self.current_piece.copy()
|
|
|
|
self.next_piece = np.array(choice(PIECES))
|
|
|
|
self.current_piece = new_piece.copy()
|
|
position = self.board_center - (new_piece.shape[1] // 2)
|
|
for i, row in enumerate(new_piece):
|
|
for j, value in enumerate(row):
|
|
self.current_move[i, position + j] = value
|
|
if not self.is_valid(self.current_move):
|
|
self.running = False
|
|
self.pivot = [0, position]
|
|
catch(self.recalculate_ghost)
|
|
|
|
def move(self, direction):
|
|
if direction == "down":
|
|
if self.pivot[0] == HEIGHT - self.current_piece.shape[0]:
|
|
self.board = self.board + self.current_move
|
|
self.new_random_piece()
|
|
return
|
|
temp_board = self.current_move.copy()
|
|
temp_board = temp_board[:-1, :]
|
|
new_row = np.zeros(shape=(1, WIDTH))
|
|
temp_board = np.concatenate((new_row, temp_board))
|
|
if not self.is_valid(temp_board):
|
|
self.board = self.board + self.current_move
|
|
self.new_random_piece()
|
|
else:
|
|
self.current_move = temp_board
|
|
self.pivot[0] += 1
|
|
elif direction == "left" and self.pivot[1] > 0:
|
|
temp_board = self.current_move.copy()
|
|
temp_board = temp_board[:, 1:]
|
|
new_column = np.zeros(shape=(HEIGHT, 1))
|
|
temp_board = np.concatenate((temp_board, new_column), axis=1)
|
|
if not self.is_valid(temp_board):
|
|
return
|
|
self.current_move = temp_board
|
|
self.pivot[1] -= 1
|
|
self.recalculate_ghost()
|
|
elif direction == "right" and self.pivot[1] < (
|
|
WIDTH - self.current_piece.shape[1]):
|
|
temp_board = self.current_move.copy()
|
|
temp_board = temp_board[:, :-1]
|
|
new_column = np.zeros(shape=(HEIGHT, 1))
|
|
temp_board = np.concatenate((new_column, temp_board), axis=1)
|
|
if not self.is_valid(temp_board):
|
|
return
|
|
self.current_move = temp_board
|
|
self.pivot[1] += 1
|
|
self.recalculate_ghost()
|
|
|
|
def rotate(self):
|
|
temp_board = self.current_move.copy()
|
|
temp_board.fill(0.)
|
|
temp_piece = self.current_piece.copy()
|
|
pivot = self.pivot[:]
|
|
|
|
# Magic to rotate a matrix !!!!!!
|
|
temp_piece = np.array(list(zip(*reversed(temp_piece))))
|
|
|
|
try:
|
|
for i, row in enumerate(temp_piece):
|
|
for j, value in enumerate(row):
|
|
temp_board[i + pivot[0], j + pivot[1]] = value
|
|
except IndexError:
|
|
if pivot[1] > 0:
|
|
self.pivot[1] -= 1
|
|
self.rotate()
|
|
return
|
|
|
|
if self.is_valid(temp_board):
|
|
self.current_move = temp_board.copy()
|
|
self.current_piece = temp_piece.copy()
|
|
self.recalculate_ghost()
|
|
|
|
def start(self):
|
|
scr = self.stdscr
|
|
input = self.input
|
|
self.new_random_piece()
|
|
|
|
while self.running:
|
|
scr.timeout(1000 - self.score)
|
|
scr.refresh()
|
|
char = catch(input, "")
|
|
scr.clear()
|
|
|
|
if not char:
|
|
self.move("down")
|
|
elif char == "q":
|
|
self.running = False
|
|
elif char == "C": # Right
|
|
self.move("right")
|
|
elif char == "D": # Left
|
|
self.move("left")
|
|
elif char == "B": # Down
|
|
self.move("down")
|
|
elif char == "A": # Rotate
|
|
self.rotate()
|
|
elif char == " ": # Rotate
|
|
self.pause()
|
|
|
|
self.draw_board()
|
|
|
|
def pause(self):
|
|
self.stdscr.refresh()
|
|
self.stdscr.clear()
|
|
self.print("PAUSE\n")
|
|
self.print("Press <SPACE>")
|
|
while True:
|
|
char = catch(self.input, "")
|
|
if char == " ":
|
|
break
|
|
elif char == "q":
|
|
self.running = False
|
|
break
|
|
|
|
@classmethod
|
|
def fuse_matrix(self, mat1, mat2, xypos=(0, 0)):
|
|
mat1 = mat1.copy()
|
|
mat2 = mat2.copy()
|
|
x, y = xypos
|
|
ysize, xsize = mat2.shape
|
|
xmax, ymax = (x + xsize), (y + ysize)
|
|
mat1[y:ymax, x:xmax] += mat2
|
|
return mat1
|
|
|
|
|
|
def start():
|
|
stdscr = curses.initscr()
|
|
curses.noecho()
|
|
curses.curs_set(False)
|
|
try:
|
|
game = Afutc(stdscr)
|
|
game.start()
|
|
except BaseException as e:
|
|
curses.endwin()
|
|
raise e
|
|
curses.endwin()
|