Skip to content

Commit

Permalink
Master controller (#14)
Browse files Browse the repository at this point in the history
* Update master_controller.py

* Update master_controller.py

* Update master_controller.py

* Update master_controller.py

* added type hints, dont need game stats its specific for 1.7

* changes stuff :)

* added max number of actions per turn

* Update player.py
  • Loading branch information
JuliaCaesar authored Apr 15, 2023
1 parent a3afa91 commit d8ec4d6
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 46 deletions.
2 changes: 1 addition & 1 deletion game/common/map/game_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def __init__(self, seed: int | None = None, map_size: Vector = Vector(),
self.walled: bool = walled

# game_map is initially going to be None. Since generation is slow, call generate_map() as needed
self.game_map: list[list[GameObject]] | None = None
self.game_map: list[list[Tile]] | None = None

@property
def seed(self) -> int:
Expand Down
31 changes: 16 additions & 15 deletions game/common/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@


class Player(GameObject):
def __init__(self, code: object | None = None, team_name: str | None = None, action: ActionType | None = None,
def __init__(self, code: object | None = None, team_name: str | None = None, actions: list[ActionType] = [],
avatar: Avatar | None = None):
super().__init__()
self.object_type: ObjectType = ObjectType.PLAYER
Expand All @@ -14,21 +14,23 @@ def __init__(self, code: object | None = None, team_name: str | None = None, act
self.team_name: str | None = team_name
self.code: object = code
# self.action: Action = action
self.action: ActionType | None = action
self.actions: list[ActionType] | list = actions
self.avatar: Avatar | None = avatar

@property
def action(self) -> ActionType | None: # change to Action if you want to use the action object
return self.__action
def actions(self) -> list[ActionType] | list: # change to Action if you want to use the action object
return self.__actions

@action.setter
def action(self, action: ActionType | None) -> None: # showing it returns nothing(like void in java)
@actions.setter
def actions(self, actions: list[ActionType] | list) -> None: # showing it returns nothing(like void in java)
# if it's (not none = and) if its (none = or)
if action is not None and not isinstance(action, ActionType): # since it can be either none or action type we need it to be and not or
# ^change to Action if you want to use the action object
raise ValueError(f'{self.__class__.__name__}.action must be ActionType or None')
# going across all action types and making it a boolean, if any are true this will be true\/
if actions is None or not isinstance(actions, list) \
or (len(actions) > 0
and any(map(lambda action_type: not isinstance(action_type, ActionType), actions))):
raise ValueError(f'{self.__class__.__name__}.action must be an empty list or a list of action types')
# ^if it's not either throw an error
self.__action = action
self.__actions = actions

@property
def functional(self) -> bool:
Expand Down Expand Up @@ -76,22 +78,21 @@ def to_json(self):
data['functional'] = self.functional
# data['error'] = self.error # .to_json() if self.error is not None else None
data['team_name'] = self.team_name
data['action'] = self.actiontype.to_json() if self.actiontype is not None else None
data['actions'] = self.actions
data['avatar'] = self.avatar.to_json() if self.avatar is not None else None

return data

def from_json(self, data):
super().from_json(data)

self.functional = data['functional']
# self.error = data['error'] # .from_json(data['action']) if data['action'] is not None else None
self.team_name = data['team_name']

action: ActionType | None = data['action']
self.actions: list[ActionType] | list = data['actions']
avatar: Avatar | None = data['avatar']
if action is None and avatar is None:
self.action = None
if avatar is None:
self.avatar = None
return self
# match case for action
Expand Down
2 changes: 2 additions & 0 deletions game/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

MAX_SECONDS_PER_TURN = 0.1 # max number of basic operations clients have for their turns

MAX_NUMBER_OF_ACTIONS_PER_TURN = 2 # max number of actions per turn is currently set to 2

MIN_CLIENTS_START = None # minimum number of clients required to start running the game; should be None when SET_NUMBER_OF_CLIENTS is used
MAX_CLIENTS_START = None # maximum number of clients required to start running the game; should be None when SET_NUMBER_OF_CLIENTS is used
SET_NUMBER_OF_CLIENTS_START = 2 # required number of clients to start running the game; should be None when MIN_CLIENTS or MAX_CLIENTS are used
Expand Down
6 changes: 4 additions & 2 deletions game/controllers/controller.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from game.common.enums import DebugLevel
from game.common.enums import DebugLevel, ActionType
from game.config import Debug
from game.common.player import Player
from game.common.map.game_board import GameBoard


class Controller:
Expand All @@ -8,7 +10,7 @@ def __init__(self):
self.debug_level = DebugLevel.controller
self.debug = False

def handle_actions(self, client, world):
def handle_actions(self, action: ActionType, client: Player, world: GameBoard):
return

def print(self, *args):
Expand Down
4 changes: 2 additions & 2 deletions game/controllers/interact_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ class InteractController(Controller):
def __init__(self):
super().__init__()

def handle_actions(self, client: Player, world: GameBoard):
def handle_actions(self, action: ActionType, client: Player, world: GameBoard):
# match interaction type with x and y
vector: Vector
match client.action:
match action:
case ActionType.INTERACT_UP:
vector = Vector(x=0, y=-1)
case ActionType.INTERACT_DOWN:
Expand Down
72 changes: 52 additions & 20 deletions game/controllers/master_controller.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
from copy import deepcopy
import random

from game.common.action import Action
from game.common.avatar import Avatar
from game.common.enums import *
from game.common.player import Player
import game.config as config
import game.config as config # this is for turns
from game.utils.thread import CommunicationThread

from game.controllers.movement_controller import MovementController
from game.controllers.controller import Controller
from game.controllers.interact_controller import InteractController
from game.common.map.game_board import GameBoard
from game.config import MAX_NUMBER_OF_ACTIONS_PER_TURN


class MasterController(Controller):
def __init__(self):
super().__init__()
self.game_over = False

self.turn = None
self.current_world_data = None
self.game_over: bool = False
# self.event_timer = GameStats.event_timer # anything related to events are commented it out until made
# self.event_times: tuple[int, int] | None = None
self.turn: int = 1
self.current_world_data: dict = None
self.movement_controller: MovementController = MovementController()
self.interact_controller: InteractController = InteractController()

# Receives all clients for the purpose of giving them the objects they will control
def give_clients_objects(self, clients):
pass
def give_clients_objects(self, clients: list[Player], world: dict):
# starting_positions = [[3, 3], [3, 9]] # would be done in generate game
for index, client in enumerate(clients):
client.avatar: Avatar = Avatar(position=world[index])

# Generator function. Given a key:value pair where the key is the identifier for the current world and the value is
# the state of the world, returns the key that will give the appropriate world information
Expand All @@ -34,35 +44,57 @@ def game_loop_logic(self, start=1):
self.turn += 1

# Receives world data from the generated game log and is responsible for interpreting it
def interpret_current_turn_data(self, clients, world, turn):
def interpret_current_turn_data(self, clients: list[Player], world: dict, turn):
self.current_world_data = world
if turn == 1:
random.seed(world["game_board"].seed)
# self.event_times = random.randrange(162, 172), random.randrange(329, 339)

# Receive a specific client and send them what they get per turn. Also obfuscates necessary objects.
def client_turn_arguments(self, client, turn):
actions = Action()
client.action = actions
def client_turn_arguments(self, client: Player, turn):
# turn_action: Action = Action()
# client.action: Action = turn_action
# ^if you want to use action as an object instead of an enum

turn_actions: list[ActionType] | list = []
client.actions = turn_actions

# Create deep copies of all objects sent to the player
current_world = deepcopy(self.current_world_data["game_board"]) # what is current world and copy avatar
copy_avatar = deepcopy(client.avatar)
# Obfuscate data in objects that that player should not be able to see

args = (self.turn, actions, self.current_world_data)
# Currently world data isn't obfuscated at all
args = (self.turn, turn_actions, current_world, copy_avatar)
return args

# Perform the main logic that happens per turn
def turn_logic(self, clients, turn):
pass
def turn_logic(self, clients: list[Player], turn):
for client in clients:
for i in range(MAX_NUMBER_OF_ACTIONS_PER_TURN):
self.movement_controller.handle_actions(client.actions[i], client, self.current_world_data["game_board"])
self.interact_controller.handle_actions(client.actions[i], client, self.current_world_data["game_board"])
# checks event logic at the end of round
# self.handle_events(clients)

# comment out for now, nothing is in place for event types yet
# def handle_events(self, clients):
# If it is time to run an event, master controller picks an event to run
# if self.turn == self.event_times[0] or self.turn == self.event_times[1]:
# self.current_world_data["game_map"].generate_event(EventType.example, EventType.example)
# event type.example is just a placeholder for now

# Return serialized version of game
def create_turn_log(self, clients, turn):
def create_turn_log(self, clients: list[Player], turn):
data = dict()

data['tick'] = turn
data['clients'] = [client.to_json() for client in clients]
# Add things that should be thrown into the turn logs here
data['temp'] = None
data['game_board'] = self.current_world_data["game_board"].to_json()

return data

# Gather necessary data together in results file
def return_final_results(self, clients, turn):
def return_final_results(self, clients: list[Player], turn):
data = dict()

data['players'] = list()
Expand Down
4 changes: 2 additions & 2 deletions game/controllers/movement_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ class MovementController(Controller):
def __init__(self):
super().__init__()

def handle_actions(self, client: Player, world: GameBoard):
def handle_actions(self, action: ActionType, client: Player, world: GameBoard):
avatar_pos: Vector = Vector(client.avatar.position.x, client.avatar.position.y)

pos_mod: Vector
match client.action:
match action:
case ActionType.MOVE_UP:
pos_mod = Vector(x=0, y=-1)
case ActionType.MOVE_DOWN:
Expand Down
12 changes: 8 additions & 4 deletions game/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import sys
import traceback

from game.common.map.game_board import GameBoard
from game.common.player import Player
from game.config import *
from game.controllers.master_controller import MasterController
Expand Down Expand Up @@ -36,8 +37,8 @@ def loop(self):
if self.quiet_mode:
f = open(os.devnull, 'w')
sys.stdout = f
self.boot()
self.load()
self.boot()
for self.current_world_key in tqdm(
self.master_controller.game_loop_logic(),
bar_format=TQDM_BAR_FORMAT,
Expand Down Expand Up @@ -146,9 +147,9 @@ def boot(self):
self.clients.sort(key=lambda clnt: clnt.team_name, reverse=True)
# Finally, request master controller to establish clients with basic objects
if SET_NUMBER_OF_CLIENTS_START == 1:
self.master_controller.give_clients_objects(self.clients[0])
self.master_controller.give_clients_objects(self.clients[0], self.world)
else:
self.master_controller.give_clients_objects(self.clients)
self.master_controller.give_clients_objects(self.clients, self.world)

# Loads in the world
def load(self):
Expand All @@ -167,8 +168,10 @@ def load(self):
world = None
with open(GAME_MAP_FILE) as json_file:
world = json.load(json_file)
world['game_board'] = GameBoard().from_json(world['game_board'])
self.world = world


# Sits on top of all actions that need to happen before the player takes their turn
def pre_tick(self):
# Increment the tick
Expand Down Expand Up @@ -260,7 +263,8 @@ def post_tick(self):
else:
data = self.master_controller.create_turn_log(self.clients, self.tick_number)

self.game_logs[self.tick_number] = data
with open(os.path.join(LOGS_DIR, f"turn_{self.tick_number:04d}.json"), 'w+') as f:
json.dump(data, f)

# Perform a game over check
if self.master_controller.game_over:
Expand Down

0 comments on commit d8ec4d6

Please sign in to comment.