School Commit Init

This commit is contained in:
2024-08-31 12:07:21 +03:00
commit 0b130ee18c
2801 changed files with 4720552 additions and 0 deletions
@@ -0,0 +1,79 @@
from src.Board.board import Board
import random
class AI:
def __init__(self) -> None:
"""
:params:
None
:returns:
None
This is the constructor for the AI class. It is called when the game class is initialized. It does not take any parameters.
"""
def get_move(self, board_state: Board) -> int:
"""
:params:
board_state: Board
:returns:
int
This function is called by the game class to get the AI's move. It should return an integer between 0 and 6. This integer represents the column the AI wants to play in.
"""
move=self.__check_imeadiate_win_or_loss(board_state,2)
if move is not None:
return move
move=self.__check_imeadiate_win_or_loss(board_state,1)
if move is not None:
return move
moves=self.__get_all_possible_moves(board_state)
return random.choice(moves)
def __get_all_possible_moves(self, board_state: Board) -> list[int]:
"""
:params:
board_state: Board
:returns:
list[int]
This function returns a list of all possible moves the AI can make. This is used to randomly select a move if the AI can't win or lose in the next move.
"""
moves=[]
for i in range(7):
if board_state.check_cell(5,i)==None:
moves.append(i)
return moves
def __check_imeadiate_win_or_loss(self, board_state: Board,player: int) -> int:
"""
:params:
board_state: Board
player: int
:returns:
int
This function checks if the AI can win or lose in the next move. If it can, it returns the column it should play in. If it can't, it returns None.
"""
for column in range(7):
for row in range(6):
if column + 3 < 7:
to_check = [board_state.get_board()[column+i][row] for i in range(4)]
if to_check.count(player) == 3 and to_check.count(None) == 1 and (row==0 or board_state.check_cell(row-1,column+to_check.index(None)) is not None):
return column + to_check.index(None)
if row + 3 < 6:
to_check = [board_state.get_board()[column][row+i] for i in range(4)]
if to_check.count(player) == 3 and to_check.count(None) == 1:
return column
if column + 3 < 7 and row + 3 < 6:
to_check = [board_state.get_board()[column+i][row+i] for i in range(4)]
if to_check.count(player) == 3 and to_check.count(None) == 1 and (row-to_check.index(None) == 0 or board_state.check_cell(row+to_check.index(None)-1,column+to_check.index(None)) is not None):
return column + to_check.index(None)
if column + 3 < 7 and row - 3 >= 0:
to_check = [board_state.get_board()[column+i][row-i] for i in range(4)]
if to_check.count(player) == 3 and to_check.count(None) == 1 and (row-to_check.index(None) == 0 or board_state.check_cell(row-to_check.index(None)-1,column+to_check.index(None)) is not None):
return column + to_check.index(None)
return None
@@ -0,0 +1,100 @@
class Board:
def __init__(self) -> None:
"""
:params:
None
:return:
None
This is the constructor for the Board class. It sets up the board and the empty spaces. It also sets up the unicode characters for the circles that will be used to represent the pieces.
"""
self.__green_circle= "\033[32m\u2b24\033[0m"
self.__red_circle= "\033[31m\u2b24\033[0m"
self.__board = [[None for _ in range(6)] for _ in range(7)]
self.__empty_spaces = 42
def __str__(self) -> str:
return """
| {5} | {11} | {17} | {23} | {29} | {35} | {41} |
+---+---+---+---+---+---+---+
| {4} | {10} | {16} | {22} | {28} | {34} | {40} |
+---+---+---+---+---+---+---+
| {3} | {9} | {15} | {21} | {27} | {33} | {39} |
+---+---+---+---+---+---+---+
| {2} | {8} | {14} | {20} | {26} | {32} | {38} |
+---+---+---+---+---+---+---+
| {1} | {7} | {13} | {19} | {25} | {31} | {37} |
+---+---+---+---+---+---+---+
| {0} | {6} | {12} | {18} | {24} | {30} | {36} |
+---+---+---+---+---+---+---+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
""".format(*[self.__green_circle if i==1 else self.__red_circle if i==2 else " " for row in self.__board for i in row])
def __repr__(self) -> str:
return str(self)
def place(self, column: int, player: int) -> int:
"""
:params:
column: int
player: int
:return:
int
This method places a piece in the board. It will raise an exception if the column is full. It returns the row where the piece was placed.
"""
for row in range(6):
if self.__board[column][row] == None:
self.__board[column][row] = player
self.__empty_spaces -= 1
return row
raise Exception("Column is full already!")
def is_full(self) -> bool:
"""
:params:
None
:return:
bool
This method returns True if the board is full, False otherwise.
"""
return self.__empty_spaces == 0
def reset(self) -> None:
"""
:params:
None
:return:
None
This method resets the board.
"""
self.__board = [[None for _ in range(6)] for _ in range(7)]
self.__empty_spaces = 42
def get_board(self) -> list[list[int]]:
"""
:params:
None
:return:
list[list[int]]
This method returns the board as a list of lists of ints. 1 represents a green piece, 2 represents a red piece and None represents an empty space.
"""
return self.__board
def check_cell(self, row: int, column: int) -> int:
"""
:params:
row: int
column: int
:return:
int
This method returns the value of the cell in the given row and column. 1 represents a green piece, 2 represents a red piece and None represents an empty space.
"""
return self.__board[column][row]
@@ -0,0 +1,203 @@
from src.Board.board import Board
from src.AI.ai import AI
import random
class Game:
def __init__(self, board: Board, ai: AI) -> None:
"""
:params:
board: Board
ai: AI
:return:
None
This is the constructor for the Game class. It takes a Board object and an AI object as parameters. It also sets the score to 0-0 and randomly chooses who goes first.
"""
self.__board = board
self.__ai = ai
self.__score = [0, 0]
self.__first_player = random.choice(["Player", "AI"])
if self.__first_player == "AI":
self.__play_ai()
def __str__(self) -> str:
return f" Connect 4 - Score : Player: {self.__score[0]} | AI: {self.__score[1]}"+str(self.__board)
def __repr__(self) -> str:
return str(self)
def play(self, column: int) -> None:
"""
:params:
column: int
:return:
None
This function is called by the main class to play a move. It takes an integer between 0 and 6 as a parameter. This integer represents the column the player wants to play in.
"""
lpo = self.__play_human(column)
if self.__check_win(lpo):
self.__score[0] += 1
return "Player"
if self.__board.is_full():
return "Draw"
lpo = self.__play_ai()
if self.__check_win(lpo):
self.__score[1] += 1
return "AI"
if self.__board.is_full():
return "Draw"
return None
def get_score(self) -> list[int]:
"""
:params:
None
:return:
list[int]
This method returns the score as a list of ints. The first int represents the player's score and the second int represents the AI's score.
"""
return self.__score
def __check_win(self, lpo: tuple[int,int]) -> bool:
"""
:params:
lpo: tuple[int,int]
:return:
bool
This method checks if the last piece played has won the game. It takes a tuple of ints as a parameter. This tuple represents the last piece played.
"""
return self.__check_column_win(lpo) or self.__check_row_win(lpo) or self.__check_diagonal1_win(lpo) or self.__check_diagonal2_win(lpo)
def __check_column_win(self, lpo: tuple[int,int]) -> bool:
"""
:params:
lpo: tuple[int,int]
:return:
bool
This method checks if the last piece played has won the game by checking the column. It takes a tuple of ints as a parameter. This tuple represents the last piece played.
"""
check = 0
for row in range(5):
if self.__board.check_cell(row, lpo[1]) == self.__board.check_cell(row + 1, lpo[1]) and self.__board.check_cell(row, lpo[1]) != None:
check += 1
else:
check = 0
if check == 3:
return True
return False
def __check_row_win(self, lpo: tuple[int,int]) -> bool:
"""
:params:
lpo: tuple[int,int]
:return:
bool
This method checks if the last piece played has won the game by checking the row. It takes a tuple of ints as a parameter. This tuple represents the last piece played.
"""
check = 0
for column in range(6):
if self.__board.check_cell(lpo[0], column) == self.__board.check_cell(lpo[0], column + 1) and self.__board.check_cell(lpo[0], column) != None:
check += 1
else:
check = 0
if check == 3:
return True
return False
def __check_diagonal1_win(self, lpo: tuple[int,int]) -> bool:
"""
:params:
lpo: tuple[int,int]
:return:
bool
This method checks if the last piece played has won the game by checking the first diagonal. It takes a tuple of ints as a parameter. This tuple represents the last piece played.
"""
check = 0
offset = -lpo[0] if lpo[0] < lpo[1] else -lpo[1]
while lpo[0]+offset < 5 and lpo[1]+offset < 6:
if self.__board.check_cell(lpo[0]+offset, lpo[1]+offset) == self.__board.check_cell(lpo[0]+offset+1, lpo[1]+offset+1) and self.__board.check_cell(lpo[0]+offset, lpo[1]+offset) != None:
check += 1
else:
check = 0
if check == 3:
return True
offset += 1
def __check_diagonal2_win(self, lpo: tuple[int,int]) -> bool:
"""
:params:
lpo: tuple[int,int]
:return:
bool
This method checks if the last piece played has won the game by checking the second diagonal. It takes a tuple of ints as a parameter. This tuple represents the last piece played.
"""
check = 0
offset = -lpo[0] if lpo[0] < 6-lpo[1] else lpo[1]-5
while lpo[0]+offset < 5 and lpo[1]-offset > 0:
if self.__board.check_cell(lpo[0]+offset, lpo[1]-offset) == self.__board.check_cell(lpo[0]+offset+1, lpo[1]-offset-1) and self.__board.check_cell(lpo[0]+offset, lpo[1]-offset) != None:
check += 1
else:
check = 0
if check == 3:
return True
offset += 1
def reset_board(self) -> None:
"""
:params:
None
:return:
None
This method resets the board and chooses a random player to start the game.
"""
self.__board.reset()
self.__first_player = random.choice(["Player", "AI"])
if self.__first_player == "AI":
self.__play_ai()
def reset_game(self) -> None:
"""
:params:
None
:return:
None
This method resets the game and the board.
"""
self.__score = [0, 0]
self.reset_board()
def __play_ai(self) -> tuple[int,int]:
"""
:params:
None
:return:
tuple[int,int]
This method plays the AI and returns the row and column of the last piece played.
"""
column = self.__ai.get_move(self.__board)
row = self.__board.place(column, 2)
return (row, column)
def __play_human(self, column: int) -> tuple[int,int]:
"""
:params:
column: int
:return:
tuple[int,int]
This method plays the human and returns the row and column of the last piece played.
"""
row = self.__board.place(column-1, 1)
return (row, column-1)
@@ -0,0 +1,38 @@
from src.Game.game import Game
class UI:
def __init__(self, game: Game) -> None:
self.game = game
def start(self) -> None:
print("\nTo play, enter a column number. To quit, enter 'quit'. To reset the board, enter 'reset'. To reset the game, enter 'reset game'.\n")
while True:
print(self.game)
command = input("Enter a column: ")
if command == "quit":
break
elif command == "reset":
self.game.reset_board()
elif command == "reset game":
self.game.reset_game()
elif command.isnumeric() and 0 < int(command) and int(command) < 8:
try:
winner = self.game.play(int(command))
if winner == "Draw":
print(self.game)
print("Draw!")
input("Press enter to continue")
self.game.reset_board()
continue
if winner is not None:
print(self.game)
print(f"{winner} won!")
input("Press enter to continue")
self.game.reset_board()
except Exception as e:
print(e)
else:
print("Invalid command")