generated from mwc/project_game
Started developing a retro version of Tetris, following the planning on the board of the classroom (see planning.jpg). Moved cursor work into cursor.
143 lines
3.1 KiB
Python
143 lines
3.1 KiB
Python
import inspect
|
|
import os
|
|
import random
|
|
import sys
|
|
import retro
|
|
import tetris
|
|
from retro.agent import ArrowKeyAgent
|
|
|
|
|
|
|
|
# Expose Game name expected by create_game helper
|
|
game = tetris.Tetris()
|
|
|
|
|
|
# --- Factory helpers and small harness ---
|
|
|
|
def create_game():
|
|
"""Create a Game instance with minimal sensible defaults."""
|
|
try:
|
|
return Game()
|
|
except TypeError:
|
|
try:
|
|
sig = inspect.signature(Game)
|
|
kwargs = {}
|
|
if 'agents' in sig.parameters:
|
|
kwargs['agents'] = []
|
|
if 'state' in sig.parameters:
|
|
kwargs['state'] = {}
|
|
return Game(**kwargs)
|
|
except Exception:
|
|
for args in (([], {}), ([], None), ([],), (None,)):
|
|
try:
|
|
return Game(*args)
|
|
except Exception:
|
|
continue
|
|
raise
|
|
|
|
|
|
def create_agent(game=None):
|
|
try:
|
|
return ArrowKeyAgent(game)
|
|
except TypeError:
|
|
return ArrowKeyAgent()
|
|
|
|
|
|
def draw_screen(game: Game):
|
|
# simple text render
|
|
game.clear()
|
|
s = game.draw_board()
|
|
game.refresh()
|
|
# store/return the string for tests
|
|
return s
|
|
|
|
|
|
def main():
|
|
argv = sys.argv[1:]
|
|
if len(argv) >= 1 and argv[0] == 'play':
|
|
# Try to run a terminal player. Use curses if available.
|
|
try:
|
|
play_in_terminal(create_game())
|
|
except Exception as e:
|
|
print('Terminal play failed:', e)
|
|
print('Run without args to run smoke tests instead.')
|
|
return
|
|
|
|
game = create_game()
|
|
agent = create_agent(game)
|
|
|
|
while not game.is_over():
|
|
action = agent.get_action()
|
|
game.step(action)
|
|
draw_screen(game)
|
|
|
|
print('Game Over! Score:', game.get_score())
|
|
|
|
def play_in_terminal(game):
|
|
"""Simple curses-based player loop.
|
|
|
|
Controls:
|
|
- Left/Right arrows or 'a'/'d' to move
|
|
- Up arrow or 'w' to rotate
|
|
- Down arrow or 's' to soft drop
|
|
- Space for hard drop
|
|
- 'q' to quit
|
|
"""
|
|
|
|
keymap = {
|
|
# map keys to the Testris.action integer codes:
|
|
# 0 noop, 1 left, 2 right, 3 rotate, 4 soft drop
|
|
curses.KEY_LEFT: 1,
|
|
curses.KEY_RIGHT: 2,
|
|
curses.KEY_UP: 3,
|
|
curses.KEY_DOWN: 4,
|
|
ord('a'): 1,
|
|
ord('d'): 2,
|
|
ord('w'): 3,
|
|
ord('s'): 4,
|
|
# space will perform a hard drop (special handling)
|
|
ord(' '): 'hard_drop',
|
|
ord('q'): 'quit',
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- Simple smoke tests ---
|
|
|
|
def test_game_initialization():
|
|
g = create_game()
|
|
assert g is not None
|
|
assert g.get_score() == 0
|
|
assert not g.is_over()
|
|
|
|
|
|
def test_game_step():
|
|
g = create_game()
|
|
score0 = g.get_score()
|
|
g.step(0)
|
|
assert g.get_score() >= score0
|
|
|
|
|
|
def test_draw_board_returns_str():
|
|
g = create_game()
|
|
s = g.draw_board()
|
|
assert isinstance(s, str)
|
|
|
|
|
|
def test_game_over_sequence():
|
|
g = create_game()
|
|
# run a few steps to ensure no immediate crash
|
|
for _ in range(50):
|
|
if g.is_over():
|
|
break
|
|
g.step(0)
|
|
assert g.get_score() >= 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|