added other random scripts and functions
This commit is contained in:
parent
ef148dbbb6
commit
3f1207a5cd
8 changed files with 1929 additions and 11 deletions
72
tetris/keys.py
Normal file
72
tetris/keys.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
global isWindows
|
||||
|
||||
isWindows = False
|
||||
try:
|
||||
from win32api import STD_INPUT_HANDLE
|
||||
from win32console import GetStdHandle, KEY_EVENT, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT
|
||||
isWindows = True
|
||||
except ImportError as e:
|
||||
import sys
|
||||
import select
|
||||
import termios
|
||||
|
||||
|
||||
class KeyPoller():
|
||||
def __enter__(self):
|
||||
global isWindows
|
||||
if isWindows:
|
||||
self.readHandle = GetStdHandle(STD_INPUT_HANDLE)
|
||||
self.readHandle.SetConsoleMode(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT)
|
||||
|
||||
self.curEventLength = 0
|
||||
self.curKeysLength = 0
|
||||
|
||||
self.capturedChars = []
|
||||
else:
|
||||
self.fd = sys.stdin.fileno()
|
||||
self.new_term = termios.tcgetattr(self.fd)
|
||||
self.old_term = termios.tcgetattr(self.fd)
|
||||
|
||||
self.new_term[3] = (self.new_term[3] & ~termios.ICANON & ~termios.ECHO)
|
||||
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.new_term)
|
||||
return self
|
||||
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
if isWindows:
|
||||
pass
|
||||
else:
|
||||
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_term)
|
||||
|
||||
def clear(self):
|
||||
self.capturedChars = []
|
||||
|
||||
def poll(self):
|
||||
if isWindows:
|
||||
if not len(self.capturedChars) == 0:
|
||||
return self.capturedChars.pop(0)
|
||||
|
||||
eventsPeek = self.readHandle.PeekConsoleInput(10000)
|
||||
|
||||
if len(eventsPeek) == 0:
|
||||
return None
|
||||
|
||||
if not len(eventsPeek) == self.curEventLength:
|
||||
for curEvent in eventsPeek[self.curEventLength:]:
|
||||
if curEvent.EventType == KEY_EVENT:
|
||||
if ord(curEvent.Char) == 0 or not curEvent.KeyDown:
|
||||
pass
|
||||
else:
|
||||
curChar = str(curEvent.Char)
|
||||
self.capturedChars.append(curChar)
|
||||
self.curEventLength = len(eventsPeek)
|
||||
|
||||
if not len(self.capturedChars) == 0:
|
||||
return self.capturedChars.pop(0)
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
dr,dw,de = select.select([sys.stdin], [], [], 0)
|
||||
if not dr == []:
|
||||
return sys.stdin.read(1)
|
||||
return None
|
260
tetris/tetris.py
Normal file
260
tetris/tetris.py
Normal file
|
@ -0,0 +1,260 @@
|
|||
import random
|
||||
import time
|
||||
from fluepdot import Fluepdot, Mode
|
||||
from dotenv import dotenv_values
|
||||
|
||||
from logging import getLogger, basicConfig
|
||||
|
||||
#from keys import KeyPoller
|
||||
# replacement for own keypoller using pynput
|
||||
from pynput import keyboard
|
||||
|
||||
|
||||
def get_on_press(tetris):
|
||||
def on_press(key):
|
||||
if key == keyboard.Key.esc:
|
||||
tetris.running = False
|
||||
return False # stop listener
|
||||
try:
|
||||
k = key.char
|
||||
except:
|
||||
k = key.name
|
||||
match k:
|
||||
case "q":
|
||||
tetris.rotate(True)
|
||||
case "e":
|
||||
tetris.rotate()
|
||||
case "a":
|
||||
tetris.shift(-1)
|
||||
case "d":
|
||||
tetris.shift(+1)
|
||||
case "space":
|
||||
tetris.drop()
|
||||
case _:
|
||||
print(f'Key pressed: {k}')
|
||||
|
||||
return on_press
|
||||
|
||||
|
||||
config = {
|
||||
"LOGLEVEL": 'INFO',
|
||||
"TERMINAL_OUT": "False",
|
||||
"DISPLAY": "True",
|
||||
**dotenv_values()
|
||||
}
|
||||
|
||||
tout = config["TERMINAL_OUT"].lower() not in ['false', '0']
|
||||
display_out = config["DISPLAY"].lower() not in ['false', '0']
|
||||
|
||||
log = getLogger("Fluedot-Tetris")
|
||||
basicConfig(level=config["LOGLEVEL"].upper())
|
||||
log.info(f'Fluepdot-Tetris loading with log level {config["LOGLEVEL"]}')
|
||||
log.info(f'Terminal output is {"enabled" if tout else "disabled"}')
|
||||
|
||||
NUMS = [[[1, 1, 1], [1], [1, 1, 1], [1, 1, 1], [1, 0, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]],
|
||||
[[1, 0, 1], [1], [0, 0, 1], [0, 0, 1], [1, 0, 1], [1, 0, 0], [1, 0, 0], [0, 0, 1], [1, 0, 1], [1, 0, 1]],
|
||||
[[1, 0, 1], [1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [0, 0, 1], [1, 1, 1], [1, 1, 1]],
|
||||
[[1, 0, 1], [1], [1, 0, 0], [0, 0, 1], [0, 0, 1], [0, 0, 1], [1, 0, 1], [0, 0, 1], [1, 0, 1], [0, 0, 1]],
|
||||
[[1, 1, 1], [1], [1, 1, 1], [1, 1, 1], [0, 0, 1], [1, 1, 1], [1, 1, 1], [0, 0, 1], [1, 1, 1], [1, 1, 1]]]
|
||||
|
||||
# Game settings
|
||||
WIDTH = 16
|
||||
HEIGHT = 45
|
||||
|
||||
# Define Tetris shapes
|
||||
# TODO: Implement rotation like TETЯIS
|
||||
SHAPES = [
|
||||
[[1, 1, 1, 1]], # I Shape
|
||||
[[1, 1], [1, 1]], # O Shape
|
||||
[[0, 1, 0], [1, 1, 1]], # T Shape
|
||||
[[1, 1, 0], [0, 1, 1]], # S Shape
|
||||
[[0, 1, 1], [1, 1, 0]], # Z Shape
|
||||
[[1, 0, 0], [1, 1, 1]], # L Shape
|
||||
[[0, 0, 1], [1, 1, 1]] # J Shape
|
||||
]
|
||||
|
||||
|
||||
def _or(a, b):
|
||||
return a | b
|
||||
|
||||
|
||||
def _and(a, b):
|
||||
return a & b
|
||||
|
||||
|
||||
def _2d_boards_apply(board1, board2, func=_or):
|
||||
rows1, cols1 = len(board1), len(board1[0]) if board1 else 0
|
||||
rows2, cols2 = len(board2), len(board2[0]) if board2 else 0
|
||||
rows = max(rows1, rows2)
|
||||
cols = max(cols1, cols2)
|
||||
result = [[0] * cols for _ in range(rows)]
|
||||
for i in range(rows):
|
||||
for j in range(cols):
|
||||
value1 = board1[i][j] if i < rows1 and j < cols1 else 0
|
||||
value2 = board2[i][j] if i < rows2 and j < cols2 else 0
|
||||
result[i][j] = func(value1, value2)
|
||||
return result
|
||||
|
||||
|
||||
class Tetris:
|
||||
def __init__(self, address):
|
||||
self.running = False
|
||||
self.fd = Fluepdot(address)
|
||||
if display_out:
|
||||
self.fd_size = self.fd.get_size()
|
||||
else:
|
||||
self.fd_size = [100, 16]
|
||||
log.debug(f"fluepdot size is {self.fd_size}")
|
||||
if display_out:
|
||||
self.fd.set_mode(Mode.DIFFERENTIAL)
|
||||
|
||||
self.level = 0
|
||||
self.board = [[0] * WIDTH for _ in range(HEIGHT)]
|
||||
self.current_piece = None
|
||||
self.current_position = (0, 0)
|
||||
self.next_piece = random.choice(SHAPES)
|
||||
self.padding = None
|
||||
self.score = 0
|
||||
|
||||
def get_score_board(self):
|
||||
sb = [[]] * 5
|
||||
num = self.score
|
||||
while num > 0:
|
||||
d = num % 10
|
||||
num //= 10
|
||||
for i in range(len(NUMS)):
|
||||
sb[i] = NUMS[i][d] + [0] + sb[i]
|
||||
return sb
|
||||
|
||||
def get_padding(self):
|
||||
if HEIGHT < self.fd_size[0]:
|
||||
pad = self.fd_size[0] - HEIGHT - 1
|
||||
padding = [[0] * WIDTH] * pad + [[1] * WIDTH]
|
||||
if pad > 4:
|
||||
padding = _2d_boards_apply(padding, self.offset_piece(1, 1, self.next_piece))
|
||||
if pad > 10:
|
||||
sb = self.get_score_board()
|
||||
if len(sb[0]) <= 16:
|
||||
padding = _2d_boards_apply(padding, self.offset_piece(WIDTH - len(sb[0]) - 1, 4, sb))
|
||||
self.padding = list(reversed(list(map(list, zip(*padding)))))
|
||||
else:
|
||||
self.padding = [[0] * WIDTH]
|
||||
|
||||
def new_piece(self):
|
||||
self.current_piece = self.next_piece
|
||||
# NES Tetris randomizer
|
||||
# (8 options, if equal to prev or reroll -> 7 options, final)
|
||||
self.next_piece = random.choice(SHAPES + [[]])
|
||||
if not self.next_piece or self.next_piece == self.current_piece:
|
||||
self.next_piece = random.choice(SHAPES)
|
||||
self.get_padding()
|
||||
self.current_position = (WIDTH // 2 - len(self.current_piece[0]) // 2, 0)
|
||||
|
||||
def offset_piece(self, x, y, piece=None):
|
||||
if not piece:
|
||||
piece = self.current_piece
|
||||
piece = [[0] * x + l for l in piece]
|
||||
return [[0] * len(piece[0])] * y + piece
|
||||
|
||||
def draw_board(self):
|
||||
piece = self.offset_piece(*self.current_position)
|
||||
board = _2d_boards_apply(piece, self.board)
|
||||
board = list(reversed(list(map(list, zip(*board)))))
|
||||
board = [p + f for p, f in zip(self.padding, board)]
|
||||
if display_out:
|
||||
self.fd.post_frame([[v == 1 for v in l] for l in board])
|
||||
if tout:
|
||||
for l in board:
|
||||
print("." + "".join(['X' if x else ' ' for x in l]) + ".")
|
||||
print('')
|
||||
|
||||
def rotate(self, reversed_=False):
|
||||
for l in self.current_piece:
|
||||
log.debug(l)
|
||||
if reversed_:
|
||||
direction = "counter clockwise"
|
||||
self.current_piece = list(reversed(list(map(list, zip(*self.current_piece)))))
|
||||
else:
|
||||
direction = "clockwise"
|
||||
self.current_piece = list(map(list, map(reversed, list(map(list, zip(*self.current_piece))))))
|
||||
log.debug(f"Rotate piece {direction}.")
|
||||
|
||||
def shift(self, direction: int = 0) -> bool:
|
||||
x, y = self.current_position
|
||||
max_x = WIDTH - len(self.current_piece[0])
|
||||
x = max(0, min(max_x, x + direction))
|
||||
log.debug(f"Shifting the piece {'left' if direction == -1 else 'right'}. {direction}")
|
||||
if not self.collide((x, y)):
|
||||
self.current_position = (x, y)
|
||||
return True
|
||||
return False
|
||||
|
||||
def drop(self):
|
||||
log.debug(f"Dropping the piece.")
|
||||
x, y = self.current_position
|
||||
oy = y
|
||||
y_offset = HEIGHT - y + 1
|
||||
while not (self.collide(pos=(x, y + y_offset)) and not self.collide(pos=(x, y + y_offset - 1))):
|
||||
log.debug(f"{y_offset=}, {y + y_offset}")
|
||||
if self.collide(pos=(x, y + y_offset)):
|
||||
y_offset //= 2
|
||||
else:
|
||||
y += y_offset // 2
|
||||
log.debug(f"Found the drop offset.")
|
||||
self.current_position = (x, y + y_offset)
|
||||
self.score += y + y_offset - oy # TODO: softdrop 1 point for each cell, harddrop 2
|
||||
self.step()
|
||||
|
||||
def step(self):
|
||||
log.debug(f"Runing a step.")
|
||||
if self.collide():
|
||||
if self.current_position[1] == 0:
|
||||
return False
|
||||
self.board = _2d_boards_apply(self.board, self.offset_piece(*self.current_position))
|
||||
self.clears()
|
||||
self.new_piece()
|
||||
else:
|
||||
self.current_position = (self.current_position[0], self.current_position[1] + 1)
|
||||
return True
|
||||
|
||||
def collide(self, pos=None):
|
||||
if not pos:
|
||||
x, y = self.current_position
|
||||
else:
|
||||
x, y = pos
|
||||
y += 1
|
||||
piece = self.offset_piece(x, y)
|
||||
if y + len(self.current_piece) > HEIGHT:
|
||||
return True
|
||||
board = _2d_boards_apply(piece, self.board, _and)
|
||||
return any(map(any, board))
|
||||
|
||||
def clears(self):
|
||||
rows = sum(all(l) for l in self.board)
|
||||
if rows > 0:
|
||||
self.score += [40, 100, 300, 1200][rows - 1] * (self.level + 1)
|
||||
self.board = [[0] * WIDTH] * rows + [l for l in self.board if not all(l)]
|
||||
|
||||
def end_game(self):
|
||||
log.info(f"Game ended. Score: {self.score}")
|
||||
if display_out:
|
||||
self.fd.set_mode(Mode.FULL)
|
||||
self.fd.post_text(f"Score: {self.score}")
|
||||
|
||||
def run(self):
|
||||
self.new_piece()
|
||||
#log.debug(self.current_piece)
|
||||
self.running = True
|
||||
listener = keyboard.Listener(on_press=get_on_press(self))
|
||||
listener.start()
|
||||
while self.running:
|
||||
self.running = self.step()
|
||||
self.draw_board()
|
||||
time.sleep(.5) # Game loop delay
|
||||
self.end_game()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
log.info(f'Connecting to fluepdot at {config["DOTS_HOST"]}')
|
||||
game = Tetris(config["DOTS_HOST"])
|
||||
game.run()
|
Loading…
Add table
Add a link
Reference in a new issue