diff --git a/README.md b/README.md index 694c8b7..b1d111d 100644 --- a/README.md +++ b/README.md @@ -7,4 +7,4 @@ I will make and train the AI myself. I might make a GUI in Pygame, depends if i cbs todo list: -3 gamemodes total. ~~player vs player on the same device~~, player vs player lan, and player vs computer +3 gamemodes total. ~~player vs player on the same device~~, ~~player vs player lan~~, and player vs computer diff --git a/client.py b/client.py deleted file mode 100644 index 9d55451..0000000 --- a/client.py +++ /dev/null @@ -1,11 +0,0 @@ -import socket - -HOST = "127.0.0.1" # The server's hostname or IP address -PORT = 65432 # The port used by the server - -with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.connect((HOST, PORT)) - s.sendall(b"Hello, world") - data = s.recv(1024) - -print(f"Received {data!r}") diff --git a/main.py b/main.py index f76c5d2..9866daf 100644 --- a/main.py +++ b/main.py @@ -1,12 +1,25 @@ import os import sys +import socket from colours import Colours +# =========================== +# | Helper functions | +# =========================== def clear(): - if sys.platform.startswith('win'): - os.system('cls') + os.system('cls' if sys.platform.startswith('win') else 'clear') + +def colourTile(tile): + if tile == 'R': + return f"{Colours.BOLD}{Colours.RED}R{Colours.END}" + elif tile == 'Y': + return f"{Colours.BOLD}{Colours.YELLOW}Y{Colours.END}" + elif tile == 'r': + return f"{Colours.BOLD}{Colours.LIGHT_GREEN}R{Colours.END}" + elif tile == 'y': + return f"{Colours.BOLD}{Colours.LIGHT_GREEN}Y{Colours.END}" else: - os.system('clear') + return "O" def printBoard(board): rows = [] @@ -16,122 +29,161 @@ def printBoard(board): row += f"{Colours.BOLD}| {Colours.END}" + colourTile(column[i]) + " " row += f"{Colours.BOLD}|{Colours.END}" rows.append(row) - - rows = rows[::-1] - - toPrint = "" - for row in rows: - toPrint += (row + "\n") + rows.reverse() print(f""" {Colours.BOLD}CONNECT FOUR ============================={Colours.END} -{toPrint[:-1]} +{'\n'.join(rows)} {Colours.BOLD}==1===2===3===4===5===6===7=={Colours.END}""") -def getIntInput(prompt): - inp = "" +def getIntInput(prompt, board=None): while True: inp = input(prompt) try: inp = int(inp) - break - except: + if not 1 <= inp <= 7: + raise ValueError + return inp + except ValueError: clear() - printBoard(board) - print("Only positive integers 1-7 allowed") - - return inp - -def colourTile(tile): - if tile == 'R': - return f"{Colours.BOLD}{Colours.RED}R{Colours.END}" - elif tile == 'Y': - return f"{Colours.BOLD}{Colours.YELLOW}Y{Colours.END}" - elif tile == 'r': # winning red - return f"{Colours.BOLD}{Colours.LIGHT_GREEN}R{Colours.END}" - elif tile == 'y': # winning yellow - return f"{Colours.BOLD}{Colours.LIGHT_GREEN}Y{Colours.END}" - else: - return "O" + if board: + printBoard(board) + print("Only integers 1-7 allowed") def checkWin(board, player): rows, cols = (6, 7) winCount = 4 - - # hoz check for row in range(rows): for col in range(cols - winCount + 1): if all(board[col + i][row] == player for i in range(winCount)): return [(col + i, row) for i in range(winCount)] - - # vert check for col in range(cols): for row in range(rows - winCount + 1): if all(board[col][row + i] == player for i in range(winCount)): return [(col, row + i) for i in range(winCount)] - - # diag / check for col in range(cols - winCount + 1): for row in range(rows - winCount + 1): if all(board[col + i][row + i] == player for i in range(winCount)): return [(col + i, row + i) for i in range(winCount)] - - # diag \ check for col in range(cols - winCount + 1): for row in range(winCount - 1, rows): if all(board[col + i][row - i] == player for i in range(winCount)): return [(col + i, row - i) for i in range(winCount)] -# This is defined as columns, not rows. So tile 0 on column 0 is the bottom left tile of the board -# you index like board[column][row] -board = [ - ['O', 'O', 'O', 'O', 'O', 'O'], - ['O', 'O', 'O', 'O', 'O', 'O'], - ['O', 'O', 'O', 'O', 'O', 'O'], - ['O', 'O', 'O', 'O', 'O', 'O'], - ['O', 'O', 'O', 'O', 'O', 'O'], - ['O', 'O', 'O', 'O', 'O', 'O'], - ['O', 'O', 'O', 'O', 'O', 'O'], -] +# =========================== +# | Player move providers | +# =========================== +def local_move_provider(player, board): + col = getIntInput(f"{colourTile(player)} where do you want to drop your tile? 1-7.\n>>> ", board) - 1 + return col -playing = True -player = 'R' +def socket_receive_move(sock): + return int(sock.recv(1024).decode()) -while playing: - clear() - printBoard(board) +def socket_send_move(sock, col): + sock.sendall(str(col).encode()) + +# =========================== +# | Main game loop | +# =========================== +def play_game(player1_get_move, player2_get_move): + board = [['O'] * 6 for _ in range(7)] + player = 'R' while True: - try: - chosenColumn = getIntInput(f"{colourTile(player)} where do you want to drop your tile? 1-7.\n>>> ") - 1 - if chosenColumn < 0: - raise IndexError - tile = board[chosenColumn].index("O") - break - except ValueError: - clear() - printBoard(board) - print(f"{Colours.BOLD}You chose a column that is full. Try again{Colours.END}") - tile = "" - except IndexError: - clear() - printBoard(board) - print(f"{Colours.BOLD}You chose a column outside of the board. Try again{Colours.END}") - tile = "" - - board[chosenColumn][tile] = player - - winPositions = checkWin(board, player) - if winPositions: - for x, y in winPositions: - board[x][y] = board[x][y].lower() - clear() printBoard(board) - print(f"{colourTile(player)} won!") - break - if player == 'R': - player = 'Y' + # Get column from correct player + if player == 'R': + col = player1_get_move(player, board) + else: + col = player2_get_move(player, board) + + try: + tile = board[col].index("O") + except ValueError: + continue # column full, skip turn (could add retry logic) + + board[col][tile] = player + + winPositions = checkWin(board, player) + if winPositions: + for x, y in winPositions: + board[x][y] = board[x][y].lower() + clear() + printBoard(board) + print(f"{colourTile(player)} won!") + input("Press ENTER to return to the menu.") + break + + player = 'Y' if player == 'R' else 'R' + +# =========================== +# | Modes | +# =========================== +def play_local_pvp(): + play_game(local_move_provider, local_move_provider) + +def play_lan_server(): + HOST, PORT = "0.0.0.0", 65432 + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind((HOST, PORT)) + s.listen() + print("Waiting for player 2...") + conn, addr = s.accept() + with conn: + print(f"Connected by {addr}") + play_game( + lambda p, b: send_and_return_local_move(p, b, conn), + lambda p, b: socket_receive_move(conn) + ) + +def play_lan_client(): + while True: + HOST, PORT = input("Enter server IP: "), 65432 + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect((HOST, PORT)) + print("Connected to server.") + play_game( + lambda p, b: socket_receive_move(s), + lambda p, b: send_and_return_local_move(p, b, s) + ) + break + except: + print("No game found on that IP. Try again.") + +def send_and_return_local_move(player, board, sock): + col = local_move_provider(player, board) + socket_send_move(sock, col) + return col + +def play_vs_computer(): + print("PvC mode coming soon!") + input("Press Enter to return to menu...") + +# =========================== +# | Menu | +# =========================== +while True: + clear() + print("How do you want to play?") + print("1. PvP (same device)") + print("2. PvP (LAN)") + print("3. PvC (vs computer)") + print("4. Quit") + choice = input("Choose 1-4: ").strip() + if choice == "1": + play_local_pvp() + elif choice == "2": + if input("Are you hosting? (y/n): ").lower() == "y": + play_lan_server() + else: + play_lan_client() + elif choice == "3": + play_vs_computer() + elif choice == "4": + break else: - player = 'R' \ No newline at end of file + input("Invalid choice. Press Enter to try again...") diff --git a/server.py b/server.py deleted file mode 100644 index f702a37..0000000 --- a/server.py +++ /dev/null @@ -1,16 +0,0 @@ -import socket - -HOST = "0.0.0.0" -PORT = 65432 - -with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind((HOST, PORT)) - s.listen() - conn, addr = s.accept() - with conn: - print(f"Connected by {addr}") - while True: - data = conn.recv(1024) - if not data: - break - conn.sendall(data) \ No newline at end of file diff --git a/test.py b/test.py deleted file mode 100644 index 62925d4..0000000 --- a/test.py +++ /dev/null @@ -1,183 +0,0 @@ -import os -import sys -import socket -from colours import Colours - -# =========================== -# | Helper functions | -# =========================== -def clear(): - os.system('cls' if sys.platform.startswith('win') else 'clear') - -def colourTile(tile): - if tile == 'R': - return f"{Colours.BOLD}{Colours.RED}R{Colours.END}" - elif tile == 'Y': - return f"{Colours.BOLD}{Colours.YELLOW}Y{Colours.END}" - elif tile == 'r': - return f"{Colours.BOLD}{Colours.LIGHT_GREEN}R{Colours.END}" - elif tile == 'y': - return f"{Colours.BOLD}{Colours.LIGHT_GREEN}Y{Colours.END}" - else: - return "O" - -def printBoard(board): - rows = [] - for i in range(6): - row = "" - for column in board: - row += f"{Colours.BOLD}| {Colours.END}" + colourTile(column[i]) + " " - row += f"{Colours.BOLD}|{Colours.END}" - rows.append(row) - rows.reverse() - - print(f""" {Colours.BOLD}CONNECT FOUR -============================={Colours.END} -{'\n'.join(rows)} -{Colours.BOLD}==1===2===3===4===5===6===7=={Colours.END}""") - -def getIntInput(prompt, board=None): - while True: - inp = input(prompt) - try: - inp = int(inp) - if not 1 <= inp <= 7: - raise ValueError - return inp - except ValueError: - clear() - if board: - printBoard(board) - print("Only integers 1-7 allowed") - -def checkWin(board, player): - rows, cols = (6, 7) - winCount = 4 - for row in range(rows): - for col in range(cols - winCount + 1): - if all(board[col + i][row] == player for i in range(winCount)): - return [(col + i, row) for i in range(winCount)] - for col in range(cols): - for row in range(rows - winCount + 1): - if all(board[col][row + i] == player for i in range(winCount)): - return [(col, row + i) for i in range(winCount)] - for col in range(cols - winCount + 1): - for row in range(rows - winCount + 1): - if all(board[col + i][row + i] == player for i in range(winCount)): - return [(col + i, row + i) for i in range(winCount)] - for col in range(cols - winCount + 1): - for row in range(winCount - 1, rows): - if all(board[col + i][row - i] == player for i in range(winCount)): - return [(col + i, row - i) for i in range(winCount)] - -# =========================== -# | Player move providers | -# =========================== -def local_move_provider(player, board): - col = getIntInput(f"{colourTile(player)} where do you want to drop your tile? 1-7.\n>>> ", board) - 1 - return col - -def socket_receive_move(sock): - return int(sock.recv(1024).decode()) - -def socket_send_move(sock, col): - sock.sendall(str(col).encode()) - -# =========================== -# | Main game loop | -# =========================== -def play_game(player1_get_move, player2_get_move): - board = [['O'] * 6 for _ in range(7)] - player = 'R' - - while True: - clear() - printBoard(board) - - # Get column from correct player - if player == 'R': - col = player1_get_move(player, board) - else: - col = player2_get_move(player, board) - - try: - tile = board[col].index("O") - except ValueError: - continue # column full, skip turn (could add retry logic) - - board[col][tile] = player - - winPositions = checkWin(board, player) - if winPositions: - for x, y in winPositions: - board[x][y] = board[x][y].lower() - clear() - printBoard(board) - print(f"{colourTile(player)} won!") - break - - player = 'Y' if player == 'R' else 'R' - -# =========================== -# | Modes | -# =========================== -def play_local_pvp(): - play_game(local_move_provider, local_move_provider) - -def play_lan_server(): - HOST, PORT = "0.0.0.0", 65432 - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind((HOST, PORT)) - s.listen() - print("Waiting for player 2...") - conn, addr = s.accept() - with conn: - print(f"Connected by {addr}") - play_game( - lambda p, b: send_and_return_local_move(p, b, conn), - lambda p, b: socket_receive_move(conn) - ) - -def play_lan_client(): - HOST, PORT = input("Enter server IP: "), 65432 - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.connect((HOST, PORT)) - print("Connected to server.") - play_game( - lambda p, b: socket_receive_move(s), - lambda p, b: send_and_return_local_move(p, b, s) - ) - -def send_and_return_local_move(player, board, sock): - col = local_move_provider(player, board) - socket_send_move(sock, col) - return col - -def play_vs_computer(): - print("PvC mode coming soon!") - input("Press Enter to return to menu...") - -# =========================== -# | Menu | -# =========================== -while True: - clear() - print("How do you want to play?") - print("1. PvP (same device)") - print("2. PvP (LAN)") - print("3. PvC (vs computer)") - print("4. Quit") - choice = input("Choose 1-4: ").strip() - if choice == "1": - play_local_pvp() - elif choice == "2": - if input("Are you hosting? (y/n): ").lower() == "y": - play_lan_server() - else: - play_lan_client() - elif choice == "3": - play_vs_computer() - elif choice == "4": - break - else: - input("Invalid choice. Press Enter to try again...")