Skip to content

Commit

Permalink
Merge pull request #57 from adamgulde/main
Browse files Browse the repository at this point in the history
Alpha v0.1.0: monopoly and tictactoe LAN implementation
  • Loading branch information
adamgulde authored Nov 4, 2024
2 parents 1a4d94c + 7d1edcf commit 4ebd74b
Show file tree
Hide file tree
Showing 15 changed files with 1,257 additions and 332 deletions.
357 changes: 287 additions & 70 deletions banker.py

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions error_log.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Failed to connect to Banker's receiver. [WinError 10061] No connection could be made because the target machine actively refused it
Failed to connect to Banker's receiver. [WinError 10061] No connection could be made because the target machine actively refused it
Failed to connect to Banker's receiver. [WinError 10061] No connection could be made because the target machine actively refused it
Failed to connect to Banker's receiver. [WinError 10061] No connection could be made because the target machine actively refused it
Failed to connect to Banker's receiver. [WinError 10061] No connection could be made because the target machine actively refused it
Failed to connect to Banker's receiver. [WinError 10061] No connection could be made because the target machine actively refused it
Failed to connect to Banker's receiver. [WinError 10061] No connection could be made because the target machine actively refused it
Failed to connect to Banker's receiver. [WinError 10061] No connection could be made because the target machine actively refused it
53 changes: 44 additions & 9 deletions gamemanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,49 @@
class Game:
def __init__(self, name: str, players: list, board: str, other_data):
self.name = name
self.id = -1
self.players = players
self.id = None
self.players = players # list of Client objects
self.board = board
self.other_data = other_data
self.gamestate = ''
self.MAXPLAYERS = 0

def __str__(self):
return f"[N:{self.name} | ID:{self.id} | P:{[player.name for player in self.players]}]"

def __repr__(self):
return self.__str__()
games = []

def add_game(g: Game) -> None:
g.id = len(games)
games.append(g)

def add_player_to_game(game_id: int, player) -> None:
"""
Adds a player to a game.
Parameters:
game_id: int - The id of the game to add the player to.
player: Client - The player to add to the game.
Raises:
ValueError - If the player is already in the game.
Returns:
None
"""
if not player in games[game_id].players:
games[game_id].players.append(player)
else: raise ValueError('Player already in game.')

def remove_game(game_id: int) -> None:
games.pop(game_id) # intentional choice to keep all other game ids the same

def is_game_full(game_id: int):
if None in games[game_id].players:
return False
return len(games[game_id].players) >= games[game_id].MAXPLAYERS

def game_exists(game_name: str) -> bool:
for game in games:
Expand All @@ -30,16 +56,26 @@ def game_exists(game_name: str) -> bool:

def player_in_game(game_name: str, player: str) -> bool:
for game in games:
if game.name == game_name:
return player in game.players
if game.name == game_name and player in [player.name for player in game.players]:
return True
return False

def get_game_by_id(game_id: int) -> Game:
"""
Returns the game object with the given id.
Made into a function to support type hints.
Parameters:
game_id: int - The id of the game to return.
Returns:
Game - The game object with the given id.
If the id is out of range, returns None.
"""
return games[game_id]
try:
return games[game_id]
except IndexError:
return None

def get_game_by_name(game_name: str) -> list:
"""
Expand Down Expand Up @@ -68,9 +104,9 @@ def display_games(id: int = -1, name:str = '', player_name: str= '', page:int =
continue
if name != '' and game.name != name:
continue
if player_name != '' and player_name not in game.players:
if player_name != '' and player_name not in [player.name for player in game.players]:
continue
game_info = f'{game.id}: {game.name}: {game.players}'
game_info = f'{game.id}: {game.name}: {[player.name for player in game.players]}'
while len(game_info) > ss.cols:
ret_val += game_info[:ss.cols] + '\n'
game_info = game_info[ss.cols:]
Expand All @@ -80,5 +116,4 @@ def display_games(id: int = -1, name:str = '', player_name: str= '', page:int =

start_index = page * ss.rows * ss.cols
end_index = start_index + ss.rows * ss.cols
return ret_val[start_index:end_index]

return ret_val[start_index:end_index]
101 changes: 97 additions & 4 deletions modules.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import screenspace as ss
import style as s
from fishing import fishing_game
from modules_directory.fishing import fishing_game
from modules_directory.tictactoe import destruct_board, construct_board
from socket import socket as Socket
import networking as net
import keyboard
import time

def calculator() -> str:
"""A simple calculator module that can perform basic arithmetic operations."""
Expand Down Expand Up @@ -90,8 +93,7 @@ def calculate(equation: str) -> float:

def list_properties() -> str:
"""
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.
Lists all properties on the board by calling the property list stored in ascii.txt.
Parameters: None
Returns: None
Expand Down Expand Up @@ -126,10 +128,101 @@ def attack():
def stocks():
pass

def ttt_handler(server: Socket, active_terminal: int):
net.send_message(server, 'ttt,getgamestate')
time.sleep(0.1)
game_data = net.receive_message(server)
game_id = None

def get_printable_board(upper_text: str, board_data: str, lower_text) -> str:
return f"{upper_text}\n{board_data}\n{lower_text}\nUse WASD to move, Enter to select, Esc to cancel."

if 'create a new' in game_data:
ss.update_quadrant(active_terminal, game_data, padding=True)
game_id = ss.get_valid_int(prompt='Enter the game id: ', min_val=-1, max_val=0)
if game_id == -1: # If creating a new game, ask who else is playing.
while True:
ss.update_quadrant(active_terminal, "1: Player 1\n2: Player 2\n3: Player 3\n4: Player 4", padding=True) # @ TODO: This is hardcoded for now, but should be dynamic
opponent = ss.get_valid_int(prompt=f"Enter the opponent's ID (1-4), not including your ID): ",
min_val=1, max_val=4)-1 # -1 for zero-indexing

net.send_message(server, f'ttt,joingame,{game_id},{opponent}')
ss.update_quadrant(active_terminal, "Attempting to join game...", padding=True)
game_data = net.receive_message(server)
if 'select a game' in game_data or (('X' in game_data and 'O' in game_data and (not '▒' in game_data)) or '▒' in game_data):
break
else:
ss.update_quadrant(active_terminal, game_data + "\nEnter to continue...", padding=True)
input()
else:
ss.update_quadrant(active_terminal, "Not creating a new game.", padding=True)

if 'select a game' in game_data:
ss.update_quadrant(active_terminal, game_data, padding=True)
game_id = ss.get_valid_int(prompt='Enter the game id: ', min_val=-1, max_val=10) # 10 is incorrect! temp for now TODO
# Send the server the game id to join. Should be validated on server side.
net.send_message(server, f'ttt,joingame,{game_id}')

# Wait for server to send back the new board
game_data = net.receive_message(server)
ss.update_quadrant(active_terminal, game_data, padding=True)

if ('X' in game_data and 'O' in game_data and (not '▒' in game_data)) or '▒' in game_data: # If the game data sent back is a board, then we can play the game
# TODO check this is going to work with player name's that have 'X' or 'O' in them, or hell, with the '▒' character
simple_board = destruct_board(game_data)
original_board = destruct_board(game_data)
x,y = 0,0
b = construct_board(simple_board)
ss.update_quadrant(active_terminal, get_printable_board("New board:", b, f"Coordinates:\n({x},{y})"))

# Only hook the keyboard after you are definitely IN a game.
ss.indicate_keyboard_hook(active_terminal) # update terminal border to show keyboard is hooked

while True:

if keyboard.read_event().event_type == keyboard.KEY_DOWN:
simple_board[y][x] = s.COLORS.RESET + original_board[y][x]
b = construct_board(simple_board)
ss.update_quadrant(active_terminal, get_printable_board("New board:", b, f"Coordinates:\n({x},{y})"))

if keyboard.is_pressed('w'):
y = max(0, min(y-1, 2))
if keyboard.is_pressed('a'):
x = max(0, min(x-1, 2))
if keyboard.is_pressed('s'):
y = max(0, min(y+1, 2))
if keyboard.is_pressed('d'):
x = max(0, min(x+1, 2))

simple_board[y][x] = s.COLORS.backYELLOW + original_board[y][x] + s.COLORS.RESET
time.sleep(0.05)
b = construct_board(simple_board)
ss.update_quadrant(active_terminal, get_printable_board("New board:", b, f"Coordinates:\n({x},{y})"))

if keyboard.is_pressed('enter'):
# Send move to server
if '▒' in simple_board[y][x]:
# At this point, the client can be sure that they have the
# correct game ID and that the move is valid. Thus, we add
# the game ID to the move string.
net.send_message(server, f'ttt,move,{game_id},{x}.{y}')
# receive new board (for display) from server
ss.update_quadrant(active_terminal, "Updated board:\n" + net.receive_message(server), padding=True)
ss.update_terminal(active_terminal, active_terminal) # reset terminal to normal
keyboard.unhook_all()
break
else:
ss.update_quadrant(active_terminal, get_printable_board("New board:", b, f"Coordinates:\n({x},{y})\nInvalid move. Try again."))

if keyboard.is_pressed('esc'):
ss.update_terminal(active_terminal, active_terminal) # reset terminal to normal
keyboard.unhook_all()
break

def battleship(server: Socket, gamestate: str) -> str:
net.send_message(server, 'battleship')

fishing_game_obj = fishing_game()
fishing_game_obj = fishing_game() # fishing is played LOCALLY, not over the network
def fishing(gamestate: str) -> tuple[str, str]:
"""
Fishing module handler for player.py. Returns tuple of [visual data, gamestate] both as strings.
Expand Down
4 changes: 4 additions & 0 deletions battleship.py → modules_directory/battleship.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
import style as s
import random
import os
# Now that this has been moved to modules_directory, it does not
# run standalone with the current imports. This is *fine* because
# it does not *need* to standalone, but debugging could get
# annoying.

class Ship():
"""
Expand Down
File renamed without changes.
68 changes: 68 additions & 0 deletions modules_directory/tictactoe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import keyboard

class TicTacToe:
def __init__(self):
self.board = [['▒' for _ in range(3)] for _ in range(3)]
self.current_player = 'X'
self.gamestate = 'X move'

def place(self, x:int,y:int) -> bool:
if self.board[y][x] == '▒':
self.board[y][x] = self.current_player
return True
return False

def get_board(self) -> str:
return '\n'.join([''.join(row) for row in self.board])

def check_winner(self):
# Check rows, columns and diagonals
for row in self.board:
if all(s == self.current_player for s in row):
return True
for col in range(3):
if all(self.board[row][col] == self.current_player for row in range(3)):
return True
if all(self.board[i][i] == self.current_player for i in range(3)) or all(self.board[i][2 - i] == self.current_player for i in range(3)):
return True
return False

def is_full(self):
return all(all(cell != '▒' for cell in row) for row in self.board)

def construct_board(b: list[list[str]]) -> str:
# Create new board state.
board_proper = ""
for row in b:
board_proper += "".join(row) + '\n'
return board_proper

def destruct_board(board: str) -> list[list[str]]:
"""
Necessary for player.py (in modules.py handler) to
interpret the board string sent by the server.
This saves a lot of headache so the client doesn't
have to send back and forth delta positions
checking if they'll be valid. It also minimizes
the amount of data saved on player's end. (Design
philosophy)
Takes in a current board string and returns a
list of lists representing the board, where x
and y values can be accessed as board[x][y].
"""
return [list(row) for row in board.split('\n')]

if __name__ == "__main__": # Driver code for
ttt = TicTacToe()
while True:
print(ttt.get_board())
if ttt.check_winner():
print(f"{ttt.current_player} wins!")
break
if ttt.is_full():
print("It's a tie!")
break
x,y = map(int, input("Enter x,y: ").split(','))
ttt.place(x,y)
ttt.print_board
Loading

0 comments on commit 4ebd74b

Please sign in to comment.