diff --git a/block.py b/block.py new file mode 100644 index 0000000..7055dfc --- /dev/null +++ b/block.py @@ -0,0 +1,10 @@ +class Block: + """A Block represents a single square on the Tetris board. + Blocks are part of a Piece while they are 'alive'. + """ + character = "X" + color = "blue" + alive = True + + def __init__(self, position): + self.position = position diff --git a/play_game.py b/cursor/play_game.py similarity index 100% rename from play_game.py rename to cursor/play_game.py diff --git a/play_game2.py b/cursor/play_game2.py similarity index 100% rename from play_game2.py rename to cursor/play_game2.py diff --git a/test_game.py b/cursor/test_game.py similarity index 100% rename from test_game.py rename to cursor/test_game.py diff --git a/tetris.py b/cursor/tetris.py similarity index 100% rename from tetris.py rename to cursor/tetris.py diff --git a/game.py b/game.py new file mode 100644 index 0000000..32cab79 --- /dev/null +++ b/game.py @@ -0,0 +1,7 @@ +from retro.game import Game +from manager import Manager + +agents = [Manager()] +state = {'level': 1} +game = Game(agents, state, board_size=(20, 20), debug=True) +game.play() diff --git a/manager.py b/manager.py new file mode 100644 index 0000000..6629dfd --- /dev/null +++ b/manager.py @@ -0,0 +1,24 @@ +from piece import Piece, PIECE_DEFINITIONS +from random import choice +from retro.errors import AgentNotFoundByName + +class Manager: + """The Manager takes care of stuff that isn't anyone else's responsibility: + - Create a Piece whenever none exists. + - Clear full rows of Blocks (and move other Blocks down). + - End the game when the Blocks pile up all the way. + """ + display = False + + def play_turn(self, game): + try: + game.get_agent_by_name("piece") + except AgentNotFoundByName: + self.create_piece(game) + + def create_piece(self, game): + width, height = game.board_size + piece = Piece((width//2, 2), game, choice(PIECE_DEFINITIONS)) + game.add_agent(piece) + + diff --git a/piece.py b/piece.py new file mode 100644 index 0000000..beddd0a --- /dev/null +++ b/piece.py @@ -0,0 +1,100 @@ +from block import Block + +PIECE_DEFINITIONS = [ + [(-1, 0), (0, 0), (1, 0), (2, 0)], + [(0, 0), (1, 0), (0, 1), (1, 1)], +] + +class Piece: + """A Piece is a group of blocks which are 'alive': + They fall and the player can rotate or move them. + A Piece is created with a position, the game, and a list of block_offsets, + each of which represents the location of one of the Piece's + Blocks relative to the Piece position. + """ + name = "piece" + display = False + + def __init__(self, position, game, block_offsets): + self.position = position + self.blocks = {} + for offset in block_offsets: + self.create_block(game, offset) + + def handle_keystroke(self, keystroke, game): + x, y = self.position + if keystroke.name == "KEY_LEFT": + new_position = (x - 1, y) + if self.can_move_to(new_position, game): + self.move_to(new_position) + elif keystroke.name == "KEY_RIGHT": + new_position = (x + 1, y) + if self.can_move_to(new_position, game): + self.move_to(new_position) + + def play_turn(self, game): + if self.should_fall(game): + self.fall(game) + + def should_fall(self, game): + """Determines whether the piece should fall. + Currently, the Piece falls every third turn. + In the future, the Piece should fall slowly at first, and + then should fall faster at higher levels. + """ + return game.turn_number % 3 == 0 + + def fall(self, game): + x, y = self.position + falling_position = (x, y + 1) + if self.can_move_to(falling_position, game): + self.move_to(falling_position) + else: + self.destroy(game) + + def can_move_to(self, new_position, game): + """Checks whether the Piece can move to a new position. + For every one of the Piece's Blocks, finds where that block + would be after the move, and checks whether there are any dead agents + already there (live agents would be Blocks which are part of this Piece, + not a problem since they'll be moving too). + """ + x, y = new_position + agents_by_position = game.get_agents_by_position() + for offset in self.blocks.keys(): + ox, oy = offset + new_block_position = (x+ox, y+oy) + if not game.on_board(new_block_position): + return False + for agent in agents_by_position[new_block_position]: + if not agent.alive: + return False + return True + + def move_to(self, position): + """Move to position and updates positions of Blocks. + """ + x, y = position + self.position = position + for offset, block in self.blocks.items(): + ox, oy = offset + block.position = (x + ox, y + oy) + + def create_block(self, game, offset): + x, y = self.position + ox, oy = offset + block = Block((x + ox, y + oy)) + self.blocks[offset] = block + game.add_agent(block) + + def destroy(self, game): + """Causes the Piece to destroy itself. + All the Blocks are set to dead. + """ + for block in self.blocks.values(): + block.alive = False + game.remove_agent(self) + + + + diff --git a/planning.jpg b/planning.jpg new file mode 100644 index 0000000..43bd614 Binary files /dev/null and b/planning.jpg differ diff --git a/poetry.lock b/poetry.lock index 3fece54..83da853 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "ansicon" @@ -48,14 +48,14 @@ ansicon = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "retro-games" -version = "1.1.0" +version = "1.1.3" description = "A simple framework for Terminal-based games" optional = false python-versions = "<4.0,>=3.10" groups = ["main"] files = [ - {file = "retro_games-1.1.0-py3-none-any.whl", hash = "sha256:c621117e4dd528b1e4870d897d00c4365566ab3ba965177e3996ed3c889dd9f8"}, - {file = "retro_games-1.1.0.tar.gz", hash = "sha256:2167b574f42fe1e739b7c9ec75e98a9b76df42e2166376b85559291b3dc58f82"}, + {file = "retro_games-1.1.3-py3-none-any.whl", hash = "sha256:4bdd27241b5cb3ee72e69a042d301ff58df2a2ade7e3c29400a538fa54e30148"}, + {file = "retro_games-1.1.3.tar.gz", hash = "sha256:4f91ff725e551820aa4e30c12c0264e2da41967ed34252122b7136bc2a8ed311"}, ] [package.dependencies]