diff --git a/.gitignore b/.gitignore index ff2d99e..1ee1291 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +testclient.py +testserver.py +errorlog.txt +error_log.txt + # From: https://github.com/github/gitignore/blob/main/Python.gitignore # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/banker.py b/banker.py index 401cd06..a80eda9 100644 --- a/banker.py +++ b/banker.py @@ -2,8 +2,6 @@ import threading import os import random -import sys -import io import style as s import screenspace as ss @@ -19,11 +17,11 @@ import select from time import sleep -bank_cash = 100000 -starting_cash = 1500 +STARTING_CASH = 1500 clients = [] port = 3131 num_players = 0 +play_monopoly = True timer = 0 @@ -45,6 +43,7 @@ def start_server() -> socket.socket: it returns the transmitter socket. Parameters: None + Returns: Transmitter socket aka the Banker's sender socket. """ global clients, port @@ -89,13 +88,14 @@ def start_server() -> socket.socket: sleep(0.5) return server_socket -def start_receiver(): +def start_receiver() -> None: """ This function handles all client-to-server requests (not the other way around). Function binds an independent receiving socket at the same IP address, one port above. For example, if the opened port was 3131, the receiver will open on 3132. Parameters: None + Returns: None """ global player_data, timer, port @@ -115,7 +115,6 @@ def start_receiver(): for reader in readers: if reader is server: player,address = reader.accept() - # Client(player, 'Player', starting_cash, []) s.print_w_dots('Player connected from: ' + address[0]) to_read.append(player) # add client to list of readable sockets else: @@ -134,37 +133,109 @@ def start_receiver(): print(f'{s.set_cursor_str(0, os.get_terminal_size().lines-3)}{s.COLORS.backBLUE}Time passed since last command: {timer}. ',flush=True,end=f'\r{s.COLORS.RESET}') timer += 1 -def unittest(test: int): +def set_unittest() -> None: """ Unit test function for the Banker module. Add here as you think of more tests. + + Parameters: None + + Returns: None """ - global num_players + global num_players, STARTING_CASH, play_monopoly + ss.set_cursor_str(0, 0) + print(f""" + Enter to skip unit testing. + - Monopoly game will not start. + - num_players = 2 + - STARTING_CASH = 1500 + - No games added to the game manager. + + Unit test -1: Create your own test. + - Set the number of players, starting cash to whatever you want. + - You may also indicate whether to start the Monopoly game or not. + + Unit test 1: + - num_players = 1 + - Starts the Monopoly game. + - STARTING_CASH = 2000 + - Tests adding games to the game manager (1 Game). + + Unit test 2: + - num_players = 2 + - Starts the Monopoly game. + - STARTING_CASH = 1500 + - Tests adding games to the game manager (4 Games). + + Unit test 3: + - num_players = 4 + - Does not start the Monopoly game. + - STARTING_CASH = 100 + - No games added to the game manager. + + Unit test 4 {s.COLORS.LIGHTBLUE}(Useful for locally testing modules without Monopoly){s.COLORS.RESET}: + - num_players = 1 + - Does not start the Monopoly game. + - STARTING_CASH = 100 + - No games added to the game manager. + + Any other number will skip unit tests. + - Monopoly game will not start. + - num_players = 2 + - STARTING_CASH = 1500 + - No games added to the game manager. + """) + + test = ss.get_valid_int("Enter a test number: ", allowed=[' ']) + if test == "": + play_monopoly = False + STARTING_CASH = 1500 + num_players = 2 + print("Skipping unit tests.") + return + if test == -1: + play_monopoly = ss.get_valid_int("Enter 1 to start Monopoly, 0 to skip: ", 0, 1) == 1 + num_players = ss.get_valid_int("Enter the number of players: ") + STARTING_CASH = ss.get_valid_int("Enter the starting cash: ") + return + match test: case 1: - print("Running unittest1") + play_monopoly = True + num_players = 1 + STARTING_CASH = 2000 + gm.add_game(gm.Game('Fake Game', [Client(None, "Null", 0, [])] * 4, 'board', 'other_data')) + case 2: + play_monopoly = True num_players = 2 + STARTING_CASH = 1500 gm.add_game(gm.Game('Battleship', [Client(None, "Null", 0, [])] * 4, 'board', 'other_data')) gm.add_game(gm.Game('Battleship', [Client(None, None, 0, [])] * 2, 'board', 'other_data')) gm.add_game(gm.Game('Battleship', [Client(None, "Name", 0, [])] * 3, 'board', 'other_data')) gm.add_game(gm.Game('TicTacToe', [Client(None, "nada", 0, [])] * 2, 'board', None)) - case 2: - gm.add_game(gm.Game('Fake Game', [Client(None, "Null", 0, [])] * 4, 'board', 'other_data')) + case 3: + play_monopoly = False + num_players = 4 + STARTING_CASH = 100 + case 4: + play_monopoly = False num_players = 1 + STARTING_CASH = 100 case _: + play_monopoly = False print("Invalid test number.") - print("Running unittest2") - unittest(2) + print("Skipping unit tests.") + return -def handle_data(data: bytes, client: socket.socket) -> str: +def handle_data(data: bytes, client: socket.socket) -> None: """ Handles all data received from player sockets. Parameters: - data (str) Data received from player sockets. + data (str): Data received from player sockets. Returns: - str representing the response to the player's data. + None """ global timer current_client = get_client_by_socket(client) @@ -172,178 +243,209 @@ def handle_data(data: bytes, client: socket.socket) -> str: print(f"Received data from {current_client.name}: {data}") - # current_client.data_queue.put(decoded_data) - if decoded_data == 'request_board': net.send_message(client, mply.get_gameboard()) s.print_w_dots(f'Gameboard sent to player {client}') + + elif decoded_data.startswith('request_info'): + pass elif decoded_data.startswith('mply'): monopoly_game(current_client, decoded_data) elif decoded_data == 'ships': - if gm.game_exists('Battleship') == False: - s.print_w_dots('No active games to join.') - number_of_players = 1 - s.print_w_dots(f'Creating new Battleship with {number_of_players} players.') - battleship_game_object = battleship.start_game() - battleship_game = gm.Game('Battleship', [-1] * number_of_players, battleship_game_object.board, battleship_game_object) - gm.add_game(battleship_game) - s.print_w_dots('Game created.') - battleship_game_object.player_names.append(current_client.name) - s.print_w_dots(f'{current_client.name} joined game.') - net.send_message(client, battleship_game_object.generate_water_and_coords()) - - elif gm.player_in_game('Battleship', current_client.name) == True: - if len(gm.get_game_by_name('Battleship')) >= 1: - print(f'{current_client.name} is already playing at least one game, need to select a specific game to rejoin.') - net.send_message(client, gm.display_games(name='Battleship', player_name=current_client.name)) - - else: # should only appear if player is in multiple games - net.send_message(client, gm.display_games()) - - # battleship_board = + battleship_game.popup("Players: " + str(gm.get_game_by_id(0).other_data.player_names)) - # print(battleship_board) - # print("Current size of Battleship board (if over 10^10, broken): ", len(battleship_board)) - # net.send_message(client, battleship_board) - # s.print_w_dots(f'Gameboard sent to player {client}') + handle_battleship(decoded_data, current_client) + elif decoded_data.startswith('ttt'): - ttt_game = None - ttt_location_info = s.set_cursor_str(random.randint(0, 100), random.randint(0, 40)) + random.choice([s.COLORS.dispBLUE, s.COLORS.dispGREEN, s.COLORS.dispRED]) # just throw the information somewhere on the screen - print(ttt_location_info + "TicTacToe data requested!") - if decoded_data.split(',')[1] == 'getgamestate': - # Joining a game logic - # Game does not exist + handle_ttt(decoded_data, current_client) + + elif decoded_data == 'bal': + net.send_message(client, f'Your balance is: {current_client.money}') + timer = 0 + +def handle_battleship(cmds: str, current_client: Client) -> None: + """ + Handles the logic for a client to join or create a Battleship game. + + Parameters: + cmds (str): The command string sent by the client. + current_client (Client): The client object representing the current player. + Returns: + None + """ + if gm.game_exists('Battleship') == False: + s.print_w_dots('No active games to join.') + number_of_players = 1 + s.print_w_dots(f'Creating new Battleship with {number_of_players} players.') + battleship_game_object = battleship.start_game() + battleship_game = gm.Game('Battleship', [-1] * number_of_players, battleship_game_object.board, battleship_game_object) + gm.add_game(battleship_game) + s.print_w_dots('Game created.') + battleship_game_object.player_names.append(current_client.name) + s.print_w_dots(f'{current_client.name} joined game.') + net.send_message(current_client.socket, battleship_game_object.generate_water_and_coords()) + + elif gm.player_in_game('Battleship', current_client.name) == True: + if len(gm.get_game_by_name('Battleship')) >= 1: + print(f'{current_client.name} is already playing at least one game, need to select a specific game to rejoin.') + net.send_message(current_client.socket, gm.display_games(name='Battleship', player_name=current_client.name)) + + else: # should only appear if player is in multiple games + net.send_message(current_client.socket, gm.display_games()) + +def handle_ttt(cmds: str, current_client: Client) -> None: + """ + Handles all TicTacToe requests from the player. + + Parameters: + cmds (str): The command string from the player. + current_client (Client): The client object associated with the player. + + Returns: + None + """ + ttt_game = None + ttt_location_info = s.set_cursor_str(random.randint(0, 100), random.randint(0, 40)) + random.choice([s.COLORS.dispBLUE, s.COLORS.dispGREEN, s.COLORS.dispRED]) # just throw the information somewhere on the screen + print(ttt_location_info + "TicTacToe data requested!") + if cmds.split(',')[1] == 'getgamestate': + # Joining a game logic + # Game does not exist + if gm.player_in_game('TicTacToe', current_client.name) == True: + if len(gm.get_game_by_name('TicTacToe')) >= 1: + print(f"{ttt_location_info}Player is already playing at least one game, need to select a specific game to rejoin.") + net.send_message(current_client.socket, "\nPlease select a game to join.\n" + gm.display_games(name='TicTacToe', player_name=current_client.name)) + # Player is not in any games + else: + print(f"{ttt_location_info}TTT: Player is not in any games. Can create a game.") + # Ask player first, then create a game if they want to play. + net.send_message(current_client.socket, "\nYou are not part of any games.\nWould you like to create a new TicTacToe game?\nEnter -1 to create, or 0 to ignore.") + return + + if cmds.split(',')[1] == 'joingame': + # Player knows game exists, and is trying to (re)join a game + game_id = int(cmds.split(',')[2]) + + if game_id == -1: # Create a new game. + # Don't let a player create a new game if they're already in one.. This might be adjusted later TODO debug if gm.player_in_game('TicTacToe', current_client.name) == True: if len(gm.get_game_by_name('TicTacToe')) >= 1: - print(f"{ttt_location_info}Player is already playing at least one game, need to select a specific game to rejoin.") - net.send_message(client, "\nPlease select a game to join.\n" + gm.display_games(name='TicTacToe', player_name=current_client.name)) - # Player is not in any games + print(f"{ttt_location_info}Player input -1 when already playing another game, need to select a specific game to rejoin.") + net.send_message(current_client.socket, "\nYou're playing a game already! Rejoin from the game list.\n" + gm.display_games(name='TicTacToe', player_name=current_client.name)) + return else: - print(f"{ttt_location_info}TTT: Player is not in any games. Can create a game.") - # Should not automatically create a game if the player is not in any games.. - # Ask player first, then create a game if they want to play. - net.send_message(client, "\nYou are not part of any games.\nWould you like to create a new TicTacToe game?\nEnter -1 to create, or 0 to ignore.") - return + opponent = int(cmds.split(',')[3]) + # Check if valid opponent inputted + if len(clients) <= opponent or clients[opponent] == None or clients[opponent] == current_client: + net.send_message(current_client.socket, "\nInvalid opponent. Please select another player.") + return - if decoded_data.split(',')[1] == 'joingame': - # Player knows game exists, and is trying to (re)join a game - game_id = int(decoded_data.split(',')[2]) - - if game_id == -1: # Create a new game. - # Don't let a player create a new game if they're already in one.. This might be adjusted later TODO debug - if gm.player_in_game('TicTacToe', current_client.name) == True: - if len(gm.get_game_by_name('TicTacToe')) >= 1: - print(f"{ttt_location_info}Player input -1 when already playing another game, need to select a specific game to rejoin.") - net.send_message(client, "\nYou're playing a game already! Rejoin from the game list.\n" + gm.display_games(name='TicTacToe', player_name=current_client.name)) - return - else: - opponent = int(decoded_data.split(',')[3]) - # Check if valid opponent inputted - if len(clients) <= opponent or clients[opponent] == None or clients[opponent] == current_client: - net.send_message(client, "\nInvalid opponent. Please select another player.") - return - - s.print_w_dots('Creating new TicTacToe game.') - ttt_game = tictactoe.TicTacToe() - gm.add_game(gm.Game('TicTacToe', [None] * 2, ttt_game.board, ttt_game)) - game_id = len(gm.games)-1 - gm.get_game_by_id(len(gm.games)-1).players[0] = current_client # Should be able to safely assume that the last game in the list is the one we just created. - gm.get_game_by_id(len(gm.games)-1).players[1] = clients[opponent] # Second player - net.send_notif(clients[opponent].socket, f'{current_client.name} is attacking you in TicTacToe!') - s.print_w_dots('Game created.') - s.print_w_dots(f'{current_client.name} joined game with id {len(gm.games)-1}.') - - queried_game = gm.get_game_by_id(game_id) - if queried_game: # Game requested by ID exists - if queried_game.name == 'TicTacToe' and len(queried_game.players) < queried_game.MAXPLAYERS: - # Game is a TicTacToe game and has space for another player - # Note that this means a player can accidentally join a game they're not supposed to - # if they know the game ID. This is a security flaw. TODO fix this - gm.add_player_to_game(game_id, current_client.name) - s.print_w_dots(f'Player {current_client.name} joined game.') - - ttt_game = gm.get_game_by_id(game_id) - - elif queried_game.name == 'TicTacToe' and current_client.name in [player.name for player in queried_game.players]: - # Player is already in the game. Let them rejoin and continue playing with the same game object. - s.print_w_dots(f'\n\n\nTTT: Player rejoined game with ID {game_id}.') - ttt_game = queried_game - - elif queried_game.name != 'TicTacToe': - # Player tried to join a game that isn't TicTacToe - s.print_w_dots('\n\n\nTTT: Incorrect game name.') - net.send_message(client, "\nIncorrect game name. Please select another game.") - elif len(queried_game.players) >= queried_game.MAXPLAYERS: - # Game is full - s.print_w_dots('\n\n\nTTT: Game full.') - net.send_message(client, "\nGame is full. Please select another game.") - else: # Edge case handling. Not strictly necessary or helpful, so remove in the future if it's not needed. - s.print_w_dots('\n\n\nTTT: Something else went wrong. Game not found.') - else: - s.print_w_dots('\n\n\nTTT: Game not found.') + s.print_w_dots('Creating new TicTacToe game.') + ttt_game = tictactoe.TicTacToe() + gm.add_game(gm.Game('TicTacToe', [None] * 2, ttt_game.board, ttt_game)) + game_id = len(gm.games)-1 + gm.get_game_by_id(len(gm.games)-1).players[0] = current_client # Should be able to safely assume that the last game in the list is the one we just created. + gm.get_game_by_id(len(gm.games)-1).players[1] = clients[opponent] # Second player + net.send_notif(clients[opponent].socket, f'{current_client.name} is attacking you in TicTacToe!') + s.print_w_dots('Game created.') + s.print_w_dots(f'{current_client.name} joined game with id {len(gm.games)-1}.') + + queried_game = gm.get_game_by_id(game_id) + if queried_game: # Game requested by ID exists + if queried_game.name == 'TicTacToe' and len(queried_game.players) < queried_game.MAXPLAYERS: + # Game is a TicTacToe game and has space for another player + # Note that this means a player can accidentally join a game they're not supposed to + # if they know the game ID. This is a security flaw. TODO fix this + gm.add_player_to_game(game_id, current_client.name) + s.print_w_dots(f'Player {current_client.name} joined game.') + + ttt_game = gm.get_game_by_id(game_id) + + elif queried_game.name == 'TicTacToe' and current_client.name in [player.name for player in queried_game.players]: + # Player is already in the game. Let them rejoin and continue playing with the same game object. + s.print_w_dots(f'\n\n\nTTT: Player rejoined game with ID {game_id}.') + ttt_game = queried_game + + elif queried_game.name != 'TicTacToe': + # Player tried to join a game that isn't TicTacToe + s.print_w_dots('\n\n\nTTT: Incorrect game name.') + net.send_message(current_client.socket, "\nIncorrect game name. Please select another game.") + elif len(queried_game.players) >= queried_game.MAXPLAYERS: + # Game is full + s.print_w_dots('\n\n\nTTT: Game full.') + net.send_message(current_client.socket, "\nGame is full. Please select another game.") + else: # Edge case handling. Not strictly necessary or helpful, so remove in the future if it's not needed. + s.print_w_dots('\n\n\nTTT: Something else went wrong. Game not found.') else: - pass + s.print_w_dots('\n\n\nTTT: Game not found.') + else: + pass + + # We should have a game object by now. If we don't, something went wrong. + if cmds.split(',')[1] == 'move': + if ttt_game == None: # We know the player is validly in a game, so we can get the game object + ttt_game = gm.get_game_by_id(int(cmds.split(',')[2])) + + if type(ttt_game) == gm.Game: + ttt_game_object = ttt_game.other_data # Get the actual *specific* game object from the Game object (in this case, the TicTacToe object) + # Now check for moves + if cmds.split(',')[1] == 'move': + if (ttt_game_object.current_player == 'O' and current_client.name == ttt_game.players[0].name) \ + or (ttt_game_object.current_player == 'X' and current_client.name == ttt_game.players[1].name): + net.send_message(current_client.socket, "It's not your turn!") + return "It's not your turn!" + ttt_game_object.place(int(cmds.split(',')[3].split('.')[0]), int(cmds.split(',')[3].split('.')[1])) + if ttt_game_object.check_winner(): + net.send_message(current_client.socket, "You win!") + gm.remove_game(ttt_game.id) + return "You win!" + elif ttt_game_object.is_full(): + net.send_message(current_client.socket, "It's a tie!") + gm.remove_game(ttt_game.id) + return "It's a tie!" + else: + ttt_game_object.current_player = 'O' if ttt_game_object.current_player == 'X' else 'X' + if current_client.name == ttt_game.players[0].name: + net.send_notif(ttt_game.players[1].socket, f'TTT: {current_client.name} has made a move!') + elif current_client.name == ttt_game.players[1].name: + net.send_notif(ttt_game.players[0].socket, f'TTT: {current_client.name} has made a move!') - # We should have a game object by now. If we don't, something went wrong. - if decoded_data.split(',')[1] == 'move': - if ttt_game == None: # We know the player is validly in a game, so we can get the game object - ttt_game = gm.get_game_by_id(int(decoded_data.split(',')[2])) - - if type(ttt_game) == gm.Game: - ttt_game_object = ttt_game.other_data # Get the actual *specific* game object from the Game object (in this case, the TicTacToe object) - # Now check for moves - if decoded_data.split(',')[1] == 'move': - if (ttt_game_object.current_player == 'O' and current_client.name == ttt_game.players[0].name) \ - or (ttt_game_object.current_player == 'X' and current_client.name == ttt_game.players[1].name): - net.send_message(client, "It's not your turn!") - return "It's not your turn!" - ttt_game_object.place(int(decoded_data.split(',')[3].split('.')[0]), int(decoded_data.split(',')[3].split('.')[1])) - if ttt_game_object.check_winner(): - net.send_message(client, "You win!") - gm.remove_game(ttt_game.id) - return "You win!" - elif ttt_game_object.is_full(): - net.send_message(client, "It's a tie!") - gm.remove_game(ttt_game.id) - return "It's a tie!" - else: - ttt_game_object.current_player = 'O' if ttt_game_object.current_player == 'X' else 'X' - if current_client.name == ttt_game.players[0].name: - net.send_notif(ttt_game.players[1].socket, f'TTT: {current_client.name} has made a move!') - elif current_client.name == ttt_game.players[1].name: - net.send_notif(ttt_game.players[0].socket, f'TTT: {current_client.name} has made a move!') - - # Send the board to the player - # net.send_message(client, tictactoe.construct_board(ttt_game.board)) - if ttt_game != None: - net.send_message(client, tictactoe.construct_board(ttt_game.board)) - - timer = 0 + # Send the board to the player + if ttt_game != None: + net.send_message(current_client.socket, tictactoe.construct_board(ttt_game.board)) -def handshake(client_socket: socket.socket, handshakes: list): +def handshake(client_socket: socket.socket, handshakes: list) -> None: """ - As players connect, they attempt to handshake the server, this function handles that and - only starts the game once 4 handshakes have been successfully resolved. + As players connect, they attempt to handshake the server, this function handles that. + Player's name is also validated here. If an invalid (or empty) name is input, a default name is assigned. Parameters: - client_socket (socket.socket) Server sender socket which players connect to at game - initialization. - handshakes (list) Boolean list of successful handshakes. By default, all values are false. + client_socket (socket.socket) Server sender socket which players connect to at game initialization. + handshakes (list) Boolean list of successful handshakes. By default, all values are false. + + Returns: + None """ global clients # Attempt handshake net.send_message(client_socket, "Welcome to the game!") message = net.receive_message(client_socket) - if message == "Connected!": + if message.startswith("Connected!"): handshakes[len(clients)-1] = True - clients.append(Client(client_socket, f'Player {len(clients)+1}', 2000, [])) - clients[len(clients)-1].name = input(f"{s.COLORS.backGREEN}{s.set_cursor_str(70,25)}What is this player's name?{s.COLORS.RESET}\n") - # If the player doesn't enter a name, assign a default name. - # Also blacklist other illegal names here that would break the game. - if clients[len(clients)-1].name == "" or clients[len(clients)-1].name == "PAD ME PLEASE!": - clients[len(clients)-1].name = f"Player {len(clients)}" + def validate_name(n: str) -> str: + """ + Validates the name of the player. + If the player doesn't enter a name, assign a default name. + Also blacklist other illegal names here that would break the game. + """ + if n == "" or n == "PAD ME PLEASE!": + return f"Player {len(clients)+1}" + return n + + name = validate_name(message.split(',')[1]) + + clients.append(Client(client_socket, name, 2000, [])) + else: handshakes[len(clients)-1] = False @@ -352,10 +454,11 @@ def get_client_by_socket(socket: socket.socket) -> Client: Returns the client object associated with the given socket. Parameters: - socket (socket.socket) The socket of the client. + socket (socket.socket): The socket of the client. Returns: - Client object associated with the given socket. + obj (Client): + Client object associated with the given socket. """ for client in clients: # Only checking the IP address for now. This will not work if two clients are on the same IP address. @@ -371,12 +474,12 @@ def set_gamerules() -> None: Configure all gamerule variables according to Banker user input. Repeats until successful. Parameters: None + Returns: None """ - global bank_cash, starting_cash, num_players + global STARTING_CASH, num_players try: - bank_cash = ss.get_valid_int("Enter the amount of money the bank starts with: ") - starting_cash = ss.get_valid_int("Enter the amount of money each player starts with: ") + STARTING_CASH = ss.get_valid_int("Enter the amount of money each player starts with: ") num_players = ss.get_valid_int("Enter the number of players: ") except: print("Failed to set gamerules. Try again.") @@ -384,7 +487,22 @@ def set_gamerules() -> None: set_gamerules() def monopoly_controller() -> None: + """ + Controls the flow of the Monopoly game. + + This function initializes the Monopoly game, waits for players to connect, + and then enters a loop to manage turns. It sends the game board to the + current player and prompts them to roll the dice. + + This function does nothing if a Monopoly game is not set to play during Banker setup. + + Returns: + None + """ print("About to start Monopoly game.") + if not play_monopoly: + print("No players in the game. Not attempting to run Monopoly.") + return sleep(5) # Temporary sleep to give all players time to connect to the receiver TODO remove this and implement a better way to check all are connected to rcvr net.send_monopoly(clients[mply.turn].socket, mply.get_gameboard() + ss.set_cursor_str(0, 38) + "Welcome to Monopoly! It's your turn. Type roll to roll the dice.") print("Sent gameboard to player 1.") @@ -398,14 +516,13 @@ def monopoly_controller() -> None: clients[mply.turn].can_roll = True print(f"Sent gameboard to player {mply.turn}.") - def monopoly_game(client: Client = None, cmd: str = None) -> None: """ Description: This is the main game loop for Monopoly. It will be called from the main function in its own thread. Notes: - Monopoly command looks like this: "mply,,,," + Monopoly command looks like this: "mply,(action),(specific data),(even more specific data),(etc)" player_roll all happens on the player side, so the player can handle all of that. All data during player_roll will be sent to banker like the following: @@ -415,8 +532,6 @@ def monopoly_game(client: Client = None, cmd: str = None) -> None: Now for player_choice, banker and player will do a bit more back and forth. Most of the game logic can be handled on the player side, but banker will have to preface the messages with cash, properties, etc. - - TODO fill out this docstring with more information. """ dice = (0, -1) if mply.players[mply.turn].name == client.name: # Check if the client who sent data is the current player @@ -465,11 +580,10 @@ def monopoly_game(client: Client = None, cmd: str = None) -> None: os.system("cls") print("Welcome to Terminal Monopoly, Banker!") - test = ss.get_valid_int("Enter a test number: ") - unittest(test) + set_unittest() # set_gamerules() start_server() - game = mply.start_game(starting_cash, num_players) + game = mply.start_game(STARTING_CASH, num_players) print(game) threading.Thread(target=monopoly_controller, daemon=True).start() start_receiver() diff --git a/board.py b/board.py index 82cf2e1..b0b0daf 100644 --- a/board.py +++ b/board.py @@ -1,6 +1,6 @@ from properties import Property from style import COLORS -from player_class import Player +from player_class import MonopolyPlayer class Board: """ @@ -52,7 +52,7 @@ def __init__(self, num_players) -> None: 39: Property(0, "Boardwalk", -1, (29,72), COLORS.BLUE, 400, 200, 50, 200, 600, 1400, 1700, 2000, 200), } - def update_location(self, player:Player, roll: int, new = None) -> None: + def update_location(self, player:MonopolyPlayer, roll: int, new = None) -> None: """ Update location with player\n @location: int\n @@ -78,7 +78,7 @@ def update_location(self, player:Player, roll: int, new = None) -> None: self.locations[new].players.append(player.order) player.location = new - def current_location(self, player:Player) -> int: + def current_location(self, player:MonopolyPlayer) -> int: """ Return current location\n @player: Player object\n diff --git a/cards.py b/cards.py index 5298c24..b980984 100644 --- a/cards.py +++ b/cards.py @@ -1,7 +1,7 @@ import style as s import random from board import Board -from player_class import Player +from player_class import MonopolyPlayer class Cards: """ @@ -13,7 +13,7 @@ def __init__(self) -> None: self.community_chest = s.get_graphics().get('community chest text').split("\n") random.shuffle(self.chance) random.shuffle(self.community_chest) - def draw_chance(self, p: Player, board: Board, players) -> str: + def draw_chance(self, p: MonopolyPlayer, board: Board, players) -> str: """ Draw chance card\n """ @@ -78,7 +78,7 @@ def draw_chance(self, p: Player, board: Board, players) -> str: case 16: p.receive(150) return self.chance[-1] - def draw_community_chest(self, p: Player, board: Board, players) -> str: + def draw_community_chest(self, p: MonopolyPlayer, board: Board, players) -> str: """ Draw community chest card\n """ diff --git a/modules.py b/modules.py index 953b34c..b8fe2dd 100644 --- a/modules.py +++ b/modules.py @@ -7,8 +7,35 @@ import keyboard import time -def calculator() -> str: - """A simple calculator module that can perform basic arithmetic operations.""" +calculator_history_queue = [] +calculator_history_current_capacity = 15 + +def calculator(active_terminal) -> str: + # Helper function that contructs terminal printing. + def calculator_terminal_response(footer_option: int) -> str: + calculator_header = "\nCALCULATOR TERMINAL\nHistory:\n" + footer_options = ["Awaiting an equation...\nPress 'e' to exit the calculator terminal.", + s.COLORS.BLUE+"Type 'calc' to begin the calculator!", + s.COLORS.RED+"Equation either malformed or undefined! Try again!\nPress 'e' to exit the calculator terminal"+s.COLORS.RESET] + response = calculator_header + for i in range(len(calculator_history_queue)-1, -1, -1): + response += calculator_history_queue[i][0] + response += '\n' + footer_options[footer_option] + + return response + + #Helper function to update calculator history + def update_history(equation: str) -> None: + global calculator_history_current_capacity + + numLines = (len(equation)//75) + 1 + while(numLines > calculator_history_current_capacity): + calculator_history_current_capacity += calculator_history_queue[0][1] + calculator_history_queue.pop(0) + + calculator_history_current_capacity -= numLines + calculator_history_queue.append((equation, numLines)) + #Uses recursion to calculate. def calculate(equation: str) -> float: for i in range(0, len(equation)-1): @@ -53,43 +80,51 @@ def calculate(equation: str) -> float: return float(equation) - response = '\nCALCULATOR TERMINAL\n' - digit_result = 0 - print("\r", end='') - equation = input(s.COLORS.GREEN) - if(equation == "e"): - return equation - - #Trims unnecessary spaces and pads operators with spaces - equation = equation.replace(" ", "") - for op in ['+', '-', '*', '/', '%', '^']: - equation = equation.replace(op, " " + op + " ") - - #Removes spaces from negative number - if(len(equation) > 1 and equation[1] == '-'): - equation = "-" + equation[3:] - - try: - digit_result = calculate(equation) - except: - return "error" - - responseEQ = f'{equation} = {digit_result}' - - #There are 75 columns for each terminal, making any string longer than 75 characters overflow. - numOverflowingChar = len(responseEQ) - 75 - lineNumber = 0 - wrappedResponse = "" - while(numOverflowingChar > 0): - wrappedResponse += responseEQ[(75*lineNumber):(75*(lineNumber + 1))] + '\n' - lineNumber = lineNumber + 1 - numOverflowingChar = numOverflowingChar - 75 - - wrappedResponse += responseEQ[(75*lineNumber):(75*(lineNumber + 1)) + numOverflowingChar] + '\n' - #response += wrappedResponse + # Initial comment in active terminal + ss.update_quadrant(active_terminal, calculator_terminal_response(0), padding=True) + # All other work is done on the work line (bottom of the screen) + while True: + + response = '\nCALCULATOR TERMINAL\n' + digit_result = 0 + print("\r", end='') + equation = input(s.COLORS.GREEN) + print(s.COLORS.RESET, end="") + if(equation == "e"): + ss.update_quadrant(active_terminal, calculator_terminal_response(1), padding=True) + break + + #Trims unnecessary spaces and pads operators with spaces + equation = equation.replace(" ", "") + for op in ['+', '-', '*', '/', '%', '^']: + equation = equation.replace(op, " " + op + " ") + #Removes spaces from negative number + if(len(equation) > 1 and equation[1] == '-'): + equation = "-" + equation[3:] + + try: + digit_result = calculate(equation) + responseEQ = f'{equation} = {digit_result}' + + #There are 75 columns for each terminal, making any string longer than 75 characters overflow. + numOverflowingChar = len(responseEQ) - 75 + lineNumber = 0 + wrappedResponse = "" + while(numOverflowingChar > 0): + wrappedResponse += responseEQ[(75*lineNumber):(75*(lineNumber + 1))] + '\n' + lineNumber = lineNumber + 1 + numOverflowingChar = numOverflowingChar - 75 + + wrappedResponse += responseEQ[(75*lineNumber):(75*(lineNumber + 1)) + numOverflowingChar] + '\n' + #response += wrappedResponse + + player_equation = wrappedResponse - print(s.COLORS.RESET, end='') - return wrappedResponse + print(s.COLORS.RESET, end='') + update_history(player_equation) + ss.update_quadrant(active_terminal, calculator_terminal_response(0)) + except: + ss.update_quadrant(active_terminal, calculator_terminal_response(2), padding=True) def list_properties() -> str: """ diff --git a/monopoly.py b/monopoly.py index bf31168..dcaf4e4 100644 --- a/monopoly.py +++ b/monopoly.py @@ -7,7 +7,7 @@ from properties import Property from cards import Cards from board import Board -from player_class import Player +from player_class import MonopolyPlayer import screenspace as ss import style as s @@ -125,7 +125,7 @@ def update_history(message: str): history.pop(0) refresh_h_and_s() -def update_status(p: Player, update: str, status: list = status, mode: str = "normal", property_id: str = ""): +def update_status(p: MonopolyPlayer, update: str, status: list = status, mode: str = "normal", property_id: str = ""): """ Update the status\n """ @@ -145,7 +145,7 @@ def update_status(p: Player, update: str, status: list = status, mode: str = "no location = board.locations[int(propertyid)] if location.owner > -1: # if the location is owned color = COLORS.playerColors[location.owner] - status.append(f"Current owner: " + color + f"Player{location.owner}" + COLORS.RESET) + status.append(f"Current owner: " + color + f"{players[location.owner]}" + COLORS.RESET) status.append(f"Houses: {location.houses}") if(location.rent != 0): # if location could be owned and is not a utility or railroad status.append(f"{location.color}=== {location.name} ===") @@ -216,7 +216,7 @@ def buy_logic(mode: str = "normal", pinput: str = ""): else: update_history(f"{players[turn].name} did not buy {board.locations[CL].name}") -def housing_logic(p: Player, mode: str = "normal", propertyid: str = "", num_houses: int = -1): +def housing_logic(p: MonopolyPlayer, mode: str = "normal", propertyid: str = "", num_houses: int = -1): update_status(p, "properties") if mode == "normal": propertyid = input(ss.set_cursor_str(0, 39) + "What property do you want to build on? Enter property # or 'e' to exit.") @@ -344,7 +344,7 @@ def player_roll(num_rolls, act: int = 0, mode: str = "normal") -> str: input("\033[36;0HRoll dice?") dice = roll() bottom_screen_wipe() - update_history(f"Player {turn} rolled {dice[0]} and {dice[1]}") + update_history(f"{players[turn]} rolled {dice[0]} and {dice[1]}") if dice[0] == dice[1]: if num_rolls == 1: @@ -354,15 +354,15 @@ def player_roll(num_rolls, act: int = 0, mode: str = "normal") -> str: update_history(f"{players[turn]} rolled doubles!(X2) Roll again.") elif num_rolls == 3: - update_history(f"Player {turn} rolled doubles three times\n in a row!") - update_history(f"Player {turn} is going to jail!") + update_history(f"{players[turn]} rolled doubles three times\n in a row!") + update_history(f"{players[turn]} is going to jail!") players[turn].jail = True board.update_location(players[turn], -1) refresh_board() #if player rolled their third double they will be in jail and their location doesn't update if players[turn].jail == False: if (players[turn].location + dice[0] + dice[1]) > 39: # checks if player passed go - update_history(f"Player {players[turn].order} passed Go and received $200") + update_history(f"{players[turn]} passed Go and received $200") board.update_location(players[turn], dice[0] + dice[1]) update_history(f"{players[turn].name} landed on {board.locations[players[turn].location].name}") refresh_board() @@ -382,14 +382,14 @@ def player_roll(num_rolls, act: int = 0, mode: str = "normal") -> str: new_loc = players[turn].location update_history(f"{players[turn].name} drew a Community Chest card! {card}") if old_loc > new_loc and new_loc != 10 and new_loc != players[turn].location - 3: #check if chance card made player pass go - update_history(f"Player {players[turn].order} passed Go and received $200") + update_history(f"{players[turn]} passed Go and received $200") case -4: #chance old_loc = players[turn].location card = decks.draw_chance(players[turn], board, players) new_loc = players[turn].location update_history(f"{players[turn].name} drew a Chance card! {card}") if old_loc > new_loc and new_loc != 10 and new_loc != players[turn].location - 3: #check if chance card made player pass go - update_history(f"Player {players[turn].order} passed Go and received $200") + update_history(f"{players[turn]} passed Go and received $200") if (board.locations[players[turn].location].owner != -4): done_moving_around = False # only case where loop is needed case -5: #income tax @@ -451,7 +451,7 @@ def process_roll(num_rolls: int, dice: tuple) -> str: TODO add more detail here """ bottom_screen_wipe() - update_history(f"Player {turn} rolled {dice[0]} and {dice[1]}") + update_history(f"{players[turn]} rolled {dice[0]} and {dice[1]}") if dice[0] == dice[1]: if num_rolls == 1: @@ -461,15 +461,15 @@ def process_roll(num_rolls: int, dice: tuple) -> str: update_history(f"{players[turn]} rolled doubles!(X2) Roll again.") elif num_rolls == 3: - update_history(f"Player {turn} rolled doubles three times\n in a row!") - update_history(f"Player {turn} is going to jail!") + update_history(f"{players[turn]} rolled doubles three times\n in a row!") + update_history(f"{players[turn]} is going to jail!") players[turn].jail = True board.update_location(players[turn], -1) refresh_board() #if player rolled their third double they will be in jail and their location doesn't update if players[turn].jail == False: if (players[turn].location + dice[0] + dice[1]) > 39: # checks if player passed go - update_history(f"Player {players[turn].order} passed Go and received $200") + update_history(f"{players[turn]} passed Go and received $200") board.update_location(players[turn], dice[0] + dice[1]) update_history(f"{players[turn].name} landed on {board.locations[players[turn].location].name}") refresh_board() @@ -499,14 +499,14 @@ def evaluate_board_location(num_rolls: int, dice: tuple) -> str: new_loc = players[turn].location update_history(f"{players[turn].name} drew a Community Chest card! {card}") if old_loc > new_loc and new_loc != 10 and new_loc != players[turn].location - 3: #check if chance card made player pass go - update_history(f"Player {players[turn].order} passed Go and received $200") + update_history(f"{players[turn]} passed Go and received $200") case -4: #chance old_loc = players[turn].location card = decks.draw_chance(players[turn], board, players) new_loc = players[turn].location update_history(f"{players[turn].name} drew a Chance card! {card}") if old_loc > new_loc and new_loc != 10 and new_loc != players[turn].location - 3: #check if chance card made player pass go - update_history(f"Player {players[turn].order} passed Go and received $200") + update_history(f"{players[turn]} passed Go and received $200") if (board.locations[players[turn].location].owner != -4): done_moving_around = False # only case where loop is needed case -5: #income tax @@ -565,10 +565,10 @@ def player_choice(): choice = input("\033[38;0H'e' to end turn, p to manage properties, ?") update_history(f"{players[turn]} ended their turn.") else: - update_history(f"Player {turn} is in debt. Resolve debts before ending turn.") + update_history(f"{players[turn]} is in debt. Resolve debts before ending turn.") option = input("\033[38;0HResolve debts before ending turn.").lower().strip() if(option == "b"): # Declare bankruptcy - update_history(f"Player {turn} declared bankruptcy.") + update_history(f"{players[turn]} declared bankruptcy.") players[turn].order = -1 elif(option == "m"): # Mortgage properties pass @@ -597,7 +597,7 @@ def start_game(cash: int, num_p: int) -> str: decks = Cards() players = [] for i in range(num_players): - players.append(Player(CASH, i)) + players.append(MonopolyPlayer(CASH, i)) add_to_output(COLORS.WHITE + "\033[0;0H") add_to_output(gameboard) @@ -618,7 +618,7 @@ def game_loop(): # CASH = input("Starting cash?") # num_players = int(input("Number players?")) for i in range(num_players): - players.append(Player(CASH, i)) + players.append(MonopolyPlayer(CASH, i)) turn = 0 @@ -644,6 +644,6 @@ def game_loop(): for index, player in enumerate(players): if player.order != -1: color = COLORS.playerColors[index] - update_history(color + f"Player {index} wins!") + update_history(color + f"{players[index]} wins!") break add_to_output("\033[40;0H") \ No newline at end of file diff --git a/networking.py b/networking.py index 7b8eec4..397ced6 100644 --- a/networking.py +++ b/networking.py @@ -10,10 +10,10 @@ def format_message(text: str) -> tuple: Formats a message to be sent over a socket connection. Parameters: - text (str) The message to be sent. + text (str) The message to be sent. Returns: - tuple (bytes, bytes) containing the header and the message with padding (body). + tuple (bytes, bytes) containing the header and the message with padding (body). """ FOOTERSIZE = (16 - ((len(text) + HEADERSIZE) % 16)) % 16 # Padding to make sure the message is a multiple of 16 bytes @@ -23,32 +23,34 @@ def format_message(text: str) -> tuple: return (bytes(header, 'utf-8'), msg + bytes((' ' * FOOTERSIZE), "utf-8")) # Return the header and the message with padding -def send_message(other: socket.socket, text: str): +def send_message(other: socket.socket, text: str) -> None: """ Sends a message to a client socket. Parameters: - client (socket.socket) The client socket to send the message to. - text (str) The message to be sent. + client (socket.socket) The client socket to send the message to. + text (str) The message to be sent. + + Returns: + None """ header, body = format_message(text) other.send(header) other.send(body) -def send_notif(other: socket.socket, text: str): +def send_notif(other: socket.socket, text: str) -> None: """ Sends a notification to a client socket. This is sent to the client's second socket, which is used for notifications only. This socket is always listening for notifications and does not send any data back. - Additionally, the player should have a queue of notifications to be - displayed in the client's interface, so they do not cover one another. - Keep track of the notifications sent to the player and display them - in the order they were received, across other parts of the client's - interface. Think: set_cursor_str. Notifications are NOT terminal-based. + Notifications are NOT activeterminal-based. Parameters: - client (socket.socket) The client socket to send the notification to. - text (str) The notification to be sent. + client (socket.socket) The client socket to send the notification to. + text (str) The notification to be sent. + + Returns: + None """ other_address, other_port = other.getpeername() @@ -65,14 +67,17 @@ def send_notif(other: socket.socket, text: str): # Close the new socket new_socket.close() -def send_monopoly(other: socket.socket, stream: str): +def send_monopoly(other: socket.socket, stream: str) -> None: """ Sends the whole Monopoly board state to a client socket. This is how the banker "throws" a player into Monopoly. Parameters: - client (socket.socket) The client socket to send the message to. - stream (str) The Monopoly board state to be sent. + client (socket.socket) The client socket to send the message to. + stream (str) The Monopoly board state to be sent. + + Returns: + None """ other_address, other_port = other.getpeername() @@ -94,10 +99,10 @@ def receive_message(other: socket.socket) -> str: Receives a message from a client socket. Parameters: - client (socket.socket) The client socket to receive the message from. + client (socket.socket) The client socket to receive the message from. Returns: - str representing the message received. + str representing the message received. """ full_msg = bytearray(b'') new_msg = True diff --git a/player.py b/player.py index 29b86f5..fb29dd7 100644 --- a/player.py +++ b/player.py @@ -15,11 +15,6 @@ sockets = (socket.socket, socket.socket) ADDRESS = "" PORT = 0 -name = "" -balance = 0 -properties = 0 -calculator_history_queue = [] -calculator_history_current_capacity = 15 def get_graphics(): """Grab text from ascii.txt and split into dictionary""" @@ -49,6 +44,8 @@ def initialize(): ADDRESS = input("Enter Host IP: ") PORT = input("Enter Host Port: ") + name = input("Enter your name: ") + s.print_w_dots("Press enter to connect to the server...", end='') input() try: @@ -61,7 +58,7 @@ def initialize(): else: initialize() try: - handshake(client_receiver) + handshake(client_receiver, name) except Exception as e: print(e) n = input(COLORS.RED+"Handshake failed. Type 'exit' to quit or press enter to try again.\n"+COLORS.RESET) @@ -86,14 +83,15 @@ def initialize(): f.write(f"Failed to connect to Banker's receiver. {e}\n") s.print_w_dots("Failed connecting. ") -def handshake(sock: socket.socket) -> str: +def handshake(sock: socket.socket, name: str) -> str: """ - Used in ensuring the client and server are connected and can send/receive messages. + Used in ensuring the client and server are connected and can send/receive messages.\n Parameters: - sock (socket.socket) Client socket to receive message on. + sock (socket.socket) Client socket to receive message on. + name (str) Player's name to send to the server. Returns: - string representing the "Welcome to the game!" confirmation message. + string representing the "Welcome to the game!" confirmation message. """ # Sockets should send and receive relatively simultaneously. # As soon as the client connects, the server should send confirmation message. @@ -101,7 +99,7 @@ def handshake(sock: socket.socket) -> str: # message = sock.recv(1024).decode('utf-8') print(message) if message == "Welcome to the game!": - net.send_message(sock, "Connected!") + net.send_message(sock, f"Connected!,{name}") # Now start notification socket. import threading notif_thread = threading.Thread(target=start_notification_listener, args=(sockets[0],)) @@ -161,84 +159,6 @@ def start_notification_listener(my_socket: socket.socket) -> None: ss.initialize_terminals() ss.update_terminal(active_terminal, active_terminal) - -def calculate() -> None: - """ - Helper method for calling calculator() in modules.py. Updates screen accordingly. - Parameters: None - Returns: None - - Four parts of response - Terminal header (i.e CALCULATOR TERMINAL) - Calculator history - Letting the user know they are able to input - Prompt that lets the user know to press "e" to exit - """ - calculator_header = "\nCALCULATOR TERMINAL\nHistory:\n" - calculator_footer1 = "Awaiting an equation...\nPress \'e\' to exit the calculator terminal" - calculator_footer2 = "Type \'calc\' to begin the calculator!" - calculator_footer3 = "Equation either malformed or undefined! Try again!\nPress \'e\' to exit the calculator terminal" - - # Helper function that contructs terminal printing. - def calculator_terminal_response(footer_option: int) -> str: - response = calculator_header - for i in range(len(calculator_history_queue)-1, -1, -1): - response += calculator_history_queue[i][0] - if footer_option == 1: - response += calculator_footer1 - elif footer_option == 2: - response += calculator_footer2 - elif footer_option == 3: - response += calculator_footer3 - - return response - - #Helper function to update calculator history - def update_history(equation: str) -> None: - global calculator_history_current_capacity - - numLines = (len(equation)//75) + 1 - while(numLines > calculator_history_current_capacity): - calculator_history_current_capacity += calculator_history_queue[0][1] - calculator_history_queue.pop(0) - - calculator_history_current_capacity -= numLines - calculator_history_queue.append((equation, numLines)) - - # Initial comment in active terminal - ss.update_quadrant(active_terminal, calculator_terminal_response(1), padding=True) - # All other work is done on the work line (bottom of the screen) - while True: - player_equation = m.calculator() - if(player_equation == "e"): - ss.update_quadrant(active_terminal, calculator_terminal_response(2), padding=True) - break - elif(player_equation == "error"): - ss.update_quadrant(active_terminal, calculator_terminal_response(3), padding=True) - else: - update_history(player_equation) - ss.update_quadrant(active_terminal, calculator_terminal_response(1)) - -def display_balance() -> None: - """ - Display player's cash, assets, etc. - - Parameters: None - Returns: None - """ - pass - -def list_properties() -> None: - """ - Temporary function to list all properties on the board by calling the property list stored in ascii.txt. - Can be reworked to add color and better formatting. - - Parameters: None - Returns: None - """ - ss.update_quadrant(active_terminal, text_dict.get('properties')) - -# Probably want to implement threading for printing and getting input. def get_input() -> None: """ Main loop for input handling while in the terminal screen. Essentially just takes input from user, @@ -280,7 +200,6 @@ def get_input() -> None: elif stdIn == 'e': net.send_message(sockets[1], 'mply,endturn') - elif screen == 'terminal': stdIn = input(COLORS.WHITE+'\r').lower().strip() if screen == 'gameboard': # If player has been "pulled" into the gameboard, don't process input @@ -300,9 +219,10 @@ def get_input() -> None: ss.initialize_terminals() # Reinitialize terminals to clear the screen. TODO restore previous terminals state ss.update_terminal(active_terminal, active_terminal) elif stdIn == "calc": - calculate() + m.calculator(active_terminal) elif stdIn == "bal": - ss.update_quadrant(active_terminal, f'Cash on hand: {balance}'.center(ss.cols), padding=True) + net.send_message(sockets[1], 'bal') + ss.update_quadrant(active_terminal, net.receive_message(sockets[1]).center(ss.cols), padding=True) elif stdIn == "list": ss.update_quadrant(active_terminal, m.list_properties(), padding=False) elif stdIn.startswith("term "): @@ -315,10 +235,11 @@ def get_input() -> None: ss.overwrite(COLORS.RESET + COLORS.RED + "Include a number between 1 and 4 (inclusive) after 'term' to set the active terminal.") elif stdIn.startswith("deed"): if(len(stdIn) > 4): - ss.update_quadrant(active_terminal, m.deed(stdIn[5:]), padding=True) + pass # ss.update_quadrant(active_terminal, m.deed(stdIn[5:]), padding=True) elif stdIn == "disable": ss.update_quadrant(active_terminal, m.disable()) elif stdIn == "kill": + print(COLORS.RED) ss.update_quadrant(active_terminal, m.kill()) elif stdIn == "exit" or stdIn.isspace() or stdIn == "": # On empty input make sure to jump up one console line diff --git a/player_class.py b/player_class.py index 35d8607..434b5d1 100644 --- a/player_class.py +++ b/player_class.py @@ -1,4 +1,4 @@ -class Player: +class MonopolyPlayer: """ Player class for Monopoly game\n Contains player data.\n @@ -69,4 +69,4 @@ def leave_jail(self) -> None: self.jail = False def __str__(self) -> str: - return f"Player {self.order}" \ No newline at end of file + return self.name \ No newline at end of file diff --git a/screenspace.py b/screenspace.py index 5fba242..c855718 100644 --- a/screenspace.py +++ b/screenspace.py @@ -18,22 +18,6 @@ rows = HEIGHT//2 cols = WIDTH//2 -def print_board(gameboard: list[str]) -> None: - """ - Used in printing the gameboard for the player. Overwrites the current screen to display the gameboard. - - Parameters: - gameboard (list[str]): A representation of the gameboard as a list of strings. - - Returns: None - """ - # clear_screen() - # Resets cursor position to top left - print("\033[1A" * (HEIGHT + 4), end='\r') - - for y in range(len(gameboard)): - print(gameboard[y]) - def notification(message: str, n: int, color: str) -> str: """ Generates a notification popup message for the player. @@ -87,14 +71,21 @@ def replace_sequence(match, x, y): # Return the new sequence return f"\033[{new_y};{new_x}H" -def update_quadrant(n: int, data: str, padding: bool = True): +def update_quadrant(n: int, data: str, padding: bool = True) -> None: """ Better quadrant update function. This exceeds others because it immediately updates a single quadrant with the new data. Previously, the screen would not update until print_screen() was called. - Furthermore, print_screen() would overwrite the entire screen, which is not ideal and slower. - - Set padding = True if you're not sure whether your module needs padding. + Furthermore, print_screen() would overwrite the entire screen, which is not ideal and slower.\n + Set padding = True if you're not sure whether your module needs padding. + + Parameters: + n (int): Number (1-4) of the terminal to change data. + data (str): The string (with newlines to separate lines) to populate the quadrant with. + padding (bool): (default True) a flag whether or not your module needs extra padding + (blank spaces) to fill in any missing lines + Returns: + None """ # If you're really desparate to add padding, for some edge case you can add it to the data string. @@ -264,19 +255,28 @@ def get_valid_int(prompt, min_val = -1000000000, max_val = 1000000000, disallowe min_val (int, optional): The minimum acceptable value (inclusive). Defaults to -1000000000. max_val (int, optional): The maximum acceptable value (inclusive). Defaults to 1000000000. disallowed (list, optional): A list of disallowed values. Defaults to an empty list. + allowed (list, optional): A list of allowed values. Defaults to an empty list. + If a space is in the whitelist, user is allowed to skip input (enter key), returning an empty string. Returns: - int: A valid integer input by the user within the specified range. + int: A valid integer input by the user within the specified range. (or an empty string if allowed) Raises: - ValueError: If the input is not a valid integer or is outside the specified range. + None: All exceptions are caught and handled by the function. """ while True: try: set_cursor(0, INPUTLINE) value = int(input(prompt)) + if value in allowed: + return value if value < min_val or value > max_val or value in disallowed: raise ValueError return value except ValueError: + try: + value # check if value is defined. If not, the input was empty and the user pressed enter. + except UnboundLocalError: + if " " in allowed: + return "" # This is the signal to skip input overwrite("Invalid input. Please enter a valid integer.") set_cursor(0, INPUTLINE) diff --git a/testclient.py b/testclient.py deleted file mode 100644 index 645a2d6..0000000 --- a/testclient.py +++ /dev/null @@ -1,72 +0,0 @@ -import socket -import threading -import time -from networking import receive_message, send_message -from style import COLORS -from screenspace import notification, set_cursor_str -import random -import os - -pad = 26 -ln = 0 -thread_list = {} - -def print_nextln(msg): - global ln - ln = ln + 1 - thr_prefix = (thread_list[threading.current_thread().name] + threading.current_thread().name + COLORS.RESET + set_cursor_str(pad, ln + 1)) - print(thr_prefix + msg) - threading.current_thread().native_id - -def main(): - thread_list[threading.current_thread().name] = COLORS.YELLOW - os.system('cls') - cols = os.get_terminal_size().columns - print("=" * int((cols/2 - 3)) + "CLIENT" + "=" * int((cols/2 - 3))) - # Create a socket - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - # Connect to the server - s.connect(("localhost", 1234)) - - # Start notification listener. - client_addr = s.getsockname() - client_ip, client_port = client_addr[0], client_addr[1] - recv_thr = threading.Thread(target=notif_listener, args=(client_ip, client_port), daemon=True) - recv_thr.start() - - # Send a message - send_message(s, "Hello, server, this is client!") - - # Receive a message - msg = receive_message(s) - print_nextln(f"Received message: {msg}") - - # Close the socket - # s.close() - - print_nextln("Sending another message.") - send_message(s, 'Another message.') - recv_thr.join() - - -def notif_listener(ip: str, port:int): - thread_list[threading.current_thread().name] = COLORS.BLUE - - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind((ip, port+1)) - s.listen(1) - print_nextln("Listening for connections...") - - server_sock, server_addr = s.accept() - server_ip, server_port = server_addr[0], server_addr[1] - - print_nextln(f"Connection received from {server_ip}:{server_port}") - while True: - msg = receive_message(server_sock) - # notif = notification(msg, 1, COLORS.RED) - if msg: break - print_nextln(COLORS.RED + msg + COLORS.RESET) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/testserver.py b/testserver.py deleted file mode 100644 index 438a532..0000000 --- a/testserver.py +++ /dev/null @@ -1,44 +0,0 @@ -import socket -import threading -import time -import networking as net -import os - -def main(): - os.system('cls') - cols = os.get_terminal_size().columns - print("=" * int((cols/2 - 3)) + "SERVER" + "=" * int((cols/2 - 3))) - # Create a socket - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - # Connect to the server - s.bind(('localhost', 1234)) - - s.listen(1) - print("Listening for connections...") - - client_socket, client_addr = s.accept() - client_ip, client_port = client_addr[0], client_addr[1] - - print(f"Connection received from {client_ip}:{client_port}") - print("Waiting for messages...") - - msg = net.receive_message(client_socket) - print(f"Received message: {msg}") - - # Send a message - net.send_message(client_socket, "Hello, client, this is server!") - - - print(f"client_socket peername: {client_socket.getpeername()}") - print(f"client_socket sockname: {client_socket.getsockname()}") - # Send a notification - net.send_notif(client_socket, "This is a notification.") - - # Close the socket - # s.close() - - print(net.receive_message(client_socket)) - -if __name__ == "__main__": - main() \ No newline at end of file