generated from CogitoNTNU/README-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merged dev into main as game completed
Co-authored-by: SindreFossdal <[email protected]>
- Loading branch information
Showing
19 changed files
with
2,063 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from src.game.TetrisGameManager import TetrisGameManager | ||
from src.game.board import Board | ||
|
||
|
||
if __name__ == "__main__": | ||
board = Board() | ||
game = TetrisGameManager(board) | ||
game.startGame() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,8 @@ | ||
gitdb==4.0.11 | ||
GitPython==3.1.41 | ||
colorama==0.4.6 | ||
iniconfig==2.0.0 | ||
packaging==23.2 | ||
packaging==24.0 | ||
pluggy==1.4.0 | ||
pygame==2.5.2 | ||
pytest==8.0.2 | ||
setuptools==69.0.3 | ||
smmap==5.0.1 | ||
wheel==0.42.0 | ||
pynput==1.7.6 | ||
pytest==8.1.1 | ||
six==1.16.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
"""" | ||
This module contains the Agent class, which is the base | ||
class for all agents in the simulation. | ||
""" | ||
|
||
from abc import ABC, abstractmethod | ||
from typing import Any, Union | ||
|
||
from src.game.board import Action, Board | ||
|
||
|
||
class Agent(ABC): | ||
"""Base class for all agents in the simulation.""" | ||
|
||
@classmethod | ||
def __instancecheck__(cls, instance: Any) -> bool: | ||
return cls.__subclasscheck__(type(instance)) | ||
|
||
@classmethod | ||
def __subclasscheck__(cls, subclass: Any) -> bool: | ||
return hasattr(subclass, "result") and callable(subclass.result) | ||
|
||
@abstractmethod | ||
def result(board: Board) -> Union[Action, list[Action]]: | ||
""" | ||
Determines the next move for the agent based on the current state of the board. | ||
Args: | ||
board (Board): The current state of the board. | ||
Returns: | ||
The next move for the agent. This can be a single action or a list of actions. | ||
""" | ||
pass | ||
|
||
|
||
def play_game(agent: Agent, board: Board, actions_per_drop: int = 1) -> Board: | ||
""" | ||
Plays a game of Tetris with the given agent. | ||
Args: | ||
agent (Agent): The agent to play the game. | ||
board (Board): The initial state of the board. | ||
actions_per_drop (int, optional): The number of actions to perform per soft drop. Defaults to 1. | ||
Returns: | ||
The final state of the board after the game is over. | ||
""" | ||
while not board.isGameOver(): | ||
# Get the result of the agent's action | ||
for _ in range(actions_per_drop): | ||
result = agent.result(board) | ||
# Perform the action(s) on the board | ||
if isinstance(result, list): | ||
for action in result: | ||
board.doAction(action) | ||
else: | ||
board.doAction(result) | ||
# Advance the game by one frame | ||
board.doAction(Action.SOFT_DROP) | ||
board.printBoard() | ||
|
||
return board |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
""" This module contains the factory function for creating agents. """ | ||
|
||
from src.agents.agent import Agent | ||
from src.agents.randomAgent import RandomAgent | ||
|
||
def create_agent(agent_type: str) -> Agent: | ||
""" Create an agent of the specified type. """ | ||
|
||
match agent_type.lower(): | ||
case 'random': | ||
return RandomAgent() | ||
case _: | ||
raise ValueError(f'Unknown agent type: {agent_type}') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
""" The heuristic module contains the heuristics used by the agents. """ | ||
|
||
from src.game.board import Board | ||
|
||
|
||
def utility(gameState: Board) -> int: | ||
"""Returns the utility of the given game state.""" | ||
pass | ||
|
||
|
||
def aggregate_heights(gameState: Board) -> int: | ||
"""Returns the sum of the heights of the columns in the game state.""" | ||
checkedList = [0 for i in range(gameState.COLUMNS)] | ||
for i in range(gameState.ROWS): | ||
for j in range(gameState.COLUMNS): | ||
if gameState.board[i][j] > 0: | ||
if checkedList[j] == 0: | ||
checkedList[j] = gameState.ROWS - i | ||
return sum(checkedList) | ||
|
||
|
||
def max_height(gameState: Board) -> int: | ||
"""Returns the maximum height of the columns in the game state.""" | ||
checkedList = [0 for i in range(gameState.COLUMNS)] | ||
for i in range(gameState.ROWS): | ||
for j in range(gameState.COLUMNS): | ||
if gameState.board[i][j] > 0: | ||
if checkedList[j] == 0: | ||
checkedList[j] = gameState.ROWS - i | ||
return max(checkedList) | ||
|
||
|
||
def lines_cleaned(gameState: Board) -> int: | ||
"""Retrurns the number of lines cleared.""" | ||
sum = 0 | ||
for row in gameState.board: | ||
if all(cell == 1 for cell in row): | ||
sum += 1 | ||
return sum | ||
|
||
|
||
def bumpiness(gameState: Board) -> int: | ||
"""Returns the sum of the absolute height between all the columns""" | ||
total_bumpiness = 0 | ||
max_height = 20 | ||
columnHeightMap = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0} | ||
for column in range(gameState.COLUMNS): | ||
for row in range(gameState.ROWS): | ||
if gameState.board[row][column] > 0: | ||
if columnHeightMap[column] == 0: | ||
columnHeightMap[column] = max_height - row | ||
|
||
for key in range(gameState.COLUMNS - 1): | ||
total_bumpiness += abs(columnHeightMap[key] - columnHeightMap[key + 1]) | ||
return total_bumpiness | ||
|
||
|
||
def aggregate_height(gameState: Board) -> int: | ||
"Returns the sum of all column-heights" | ||
max_height = 20 | ||
total_height = 0 | ||
columnHeightMap = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0} | ||
for column in range(gameState.COLUMNS): | ||
for row in range(gameState.ROWS): | ||
if gameState.board[row][column] > 0: | ||
if columnHeightMap[column] == 0: | ||
columnHeightMap[column] = max_height - row | ||
|
||
for key in range(gameState.COLUMNS): | ||
total_height += columnHeightMap[key] | ||
return total_height | ||
|
||
|
||
def find_holes(gameState: Board) -> int: | ||
"""Returns number of empty cells on the board. | ||
Args: | ||
gameState (Board): the state to check | ||
Returns: | ||
The heuristic value | ||
""" | ||
holes = 0 | ||
for i in range(gameState.COLUMNS): | ||
top_block = 20 | ||
for j in range(gameState.ROWS): | ||
if (gameState.board[j][i] == 1) and (j < top_block): | ||
top_block = j | ||
if (gameState.board[j][i] == 0) and (j > top_block): | ||
holes += 1 | ||
|
||
return holes |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from src.agents.agent import Agent | ||
from src.game.board import Action, Board, get_all_actions | ||
|
||
from random import choice | ||
|
||
|
||
class RandomAgent(Agent): | ||
"""Random agent that selects a random move from the list of possible moves""" | ||
|
||
def result(self, board: Board) -> Action: | ||
# TODO: Get all possible actions | ||
|
||
# TODO: Return a random action | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
from pynput.keyboard import Key, Listener | ||
import time as t | ||
import sys | ||
|
||
from src.game.board import Action, Board | ||
|
||
baseScore = 100 | ||
|
||
""" TODO: Timer for piece drop | ||
keyboard input for piece movement | ||
keyboard input for piece rotation | ||
keyboard input for piece drop | ||
keyboard input for game start | ||
soft drop and hard drop implementation | ||
""" | ||
|
||
|
||
class TetrisGameManager: | ||
|
||
currentPiece = None | ||
nextPiece = None | ||
updateTimer = 1 | ||
streak = 1 | ||
|
||
def __init__(self, board: Board): | ||
self.board = board | ||
self.score = 0 | ||
self.currentTime = int(round(t.time() * 1000)) | ||
|
||
self.switcher = { | ||
Key.down: Action.SOFT_DROP, | ||
Key.left: Action.MOVE_LEFT, | ||
Key.right: Action.MOVE_RIGHT, | ||
Key.space: Action.HARD_DROP, | ||
Key.up: Action.ROTATE_CLOCKWISE, | ||
} | ||
|
||
def onPress(self, key): | ||
# Default action if key not found | ||
default_action = lambda: "Key not recognized" | ||
|
||
# Get the function to execute based on the key, or default action | ||
action = self.switcher.get(key, default_action) | ||
self.movePiece(action) | ||
|
||
def onRelease(self, key): | ||
pass | ||
|
||
def movePiece(self, direction: Action): | ||
self.board.doAction(direction) | ||
self.board.printBoard() | ||
|
||
def isGameOver(self): | ||
return self.board.isGameOver() | ||
|
||
def startGame(self): | ||
self.currentPiece = self.newPiece() | ||
self.nextPiece = self.newPiece() | ||
self.board.printBoard() | ||
|
||
listener = Listener(on_press=self.onPress, on_release=self.onRelease) | ||
listener.start() | ||
|
||
while not self.board.gameOver: | ||
|
||
self.checkTimer() | ||
|
||
t.sleep(0.1) # Add a small delay to reduce CPU usage | ||
|
||
# Stop the listener when the game is over | ||
print("Stopping listener") | ||
listener.stop() | ||
|
||
def newPiece(self): | ||
pass | ||
# return self.pieces.getNewPiece() | ||
|
||
def updateScore(self, linesCleared): | ||
self.score += self.streak * (baseScore**linesCleared) | ||
|
||
def checkTimer(self): | ||
checkTime = self.currentTime + 1000 / self.updateTimer | ||
newTime = int(round(t.time() * 1000)) | ||
# if (checkTime < newTime): | ||
# self.currentTime = newTime | ||
# self.movePiece("DOWN") | ||
# print("Timer checked") | ||
# self.board.printBoard() | ||
|
||
return True | ||
|
||
def stopGame(self): | ||
self.board.gameOver = True | ||
sys.exit() |
Oops, something went wrong.