Skip to content

Commit 023eb68

Browse files
author
corruptedvyolet
committed
feat: allow players to control multiple avatars
1 parent 970eb1d commit 023eb68

21 files changed

Lines changed: 202 additions & 99 deletions

base_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def team_name(self):
1515
return 'Team 1'
1616

1717
# This is where your AI will decide what to do
18-
def take_turn(self, turn, actions, world, avatar):
18+
def take_turn(self, turn, actions, world, avatars, avatar):
1919
"""
2020
This is where your AI will decide what to do.
2121
:param turn: The current turn of the game.

base_client_2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def team_name(self):
1515
return 'Team 2'
1616

1717
# This is where your AI will decide what to do
18-
def take_turn(self, turn, actions, world, avatar):
18+
def take_turn(self, turn, actions, world, avatars, avatar):
1919
"""
2020
This is where your AI will decide what to do.
2121
:param turn: The current turn of the game.

game/client/user_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ def debug(self, *args):
1717
def team_name(self):
1818
return "No_Team_Name_Available"
1919

20-
def take_turn(self, turn, actions, world, avatar):
20+
def take_turn(self, turn, actions, world, avatars, selected_avatar):
2121
raise NotImplementedError("Implement this in subclass")

game/common/avatar.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,6 @@ def held_item(self) -> Item | None:
160160
self.__clean_inventory()
161161
return self.inventory[self.__held_index]
162162

163-
@property
164-
def score(self) -> int:
165-
return self.__score
166-
167163
@property
168164
def position(self) -> Vector | None:
169165
return self.__position
@@ -194,14 +190,6 @@ def held_item(self, item: Item | None) -> None:
194190
# If the item is contained in the inventory, set the held_index to that item's index
195191
self.__held_index = self.inventory.index(item)
196192

197-
@score.setter
198-
def score(self, score: int) -> None:
199-
if score is None or not isinstance(score, int):
200-
raise ValueError(
201-
f'{self.__class__.__name__}.score must be an int. It is a(n) {score.__class__.__name__} and has the value of '
202-
f'{score}')
203-
self.__score: int = score
204-
205193
@position.setter
206194
def position(self, position: Vector | None) -> None:
207195
if position is not None and not isinstance(position, Vector):

game/common/enums.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ class ActionType(Enum):
5959
PLACE_ITEM_DOWN = auto()
6060
PLACE_ITEM_LEFT = auto()
6161
PLACE_ITEM_RIGHT = auto()
62+
SELECT_DEFAULT_AVATAR = auto()
6263
"""
6364
These last 10 enums are for selecting a slot from the Avatar class' inventory.
6465
You can add/remove these as needed for the purposes of your game.
65-
"""
66+
"""

game/common/map/occupiable.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1+
from typing import Self
2+
13
from game.common.enums import ObjectType
24
from game.common.game_object import GameObject
3-
from game.common.items.item import Item
4-
from typing import Self, Type
5-
from game.common.avatar import Avatar
65

76

87
class Occupiable(GameObject):

game/common/player.py

Lines changed: 49 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
from game.common.action import Action
2-
from game.common.game_object import GameObject
1+
from game.client.user_client import UserClient
32
from game.common.avatar import Avatar
43
from game.common.enums import *
5-
from game.client.user_client import UserClient
4+
from game.common.game_object import GameObject
65

76

87
class Player(GameObject):
@@ -18,17 +17,18 @@ class Player(GameObject):
1817
"""
1918

2019
def __init__(self, code: object | None = None, team_name: str | None = None, actions: list[ActionType] = [],
21-
avatar: Avatar | None = None):
20+
avatars: dict[ObjectType, Avatar] | None = None):
2221
super().__init__()
2322
self.object_type: ObjectType = ObjectType.PLAYER
2423
self.functional: bool = True
2524
self.error: str | None = None
2625
self.file_name: str | None = None
2726
self.team_name: str | None = team_name
2827
self.code: UserClient | None = code
29-
# self.action: Action = action
3028
self.actions: list[ActionType] = actions
31-
self.avatar: Avatar | None = avatar
29+
self.avatars: dict[ObjectType, Avatar] | None = avatars
30+
self.__selected_avatar_type: ObjectType = ObjectType.AVATAR
31+
self.score: int = 0
3232

3333
@property
3434
def error(self) -> str | None:
@@ -89,15 +89,41 @@ def file_name(self, file_name: str | None) -> None:
8989
self.__file_name = file_name
9090

9191
@property
92-
def avatar(self) -> Avatar:
93-
return self.__avatar
92+
def avatars(self) -> dict[ObjectType, Avatar]:
93+
return self.__avatars
94+
95+
@avatars.setter
96+
def avatars(self, avatars: dict[ObjectType, Avatar]) -> None:
97+
if (avatars is not None and not (isinstance(avatars, dict)
98+
and len(avatars) > 0
99+
and all(map(lambda name_and_avatar: isinstance(name_and_avatar[0], ObjectType) and isinstance(name_and_avatar[1], Avatar), avatars.items())))):
100+
raise ValueError(
101+
f'{self.__class__.__name__}.avatars must be a dict with a key of ObjectType and a value of Avatar or None. It is a(n) {avatars.__class__.__name__} and has the value of {avatars}.')
102+
self.__avatars = avatars
103+
104+
def select_avatar(self, avatar_type: ObjectType) -> None:
105+
if avatar_type is None or not isinstance(avatar_type, ObjectType):
106+
raise ValueError(
107+
f'avatar_name must be a ObjectType. It is a(n) {avatar_type.__class__.__name__} and has the value of '
108+
f'{avatar_type}'
109+
)
110+
self.__selected_avatar_type = avatar_type
111+
112+
@property
113+
def avatar(self) -> Avatar | None:
114+
return self.avatars.get(self.__selected_avatar_type, None) if self.avatars is not None else None
94115

95-
@avatar.setter
96-
def avatar(self, avatar: Avatar) -> None:
97-
if avatar is not None and not isinstance(avatar, Avatar):
116+
@property
117+
def score(self) -> int:
118+
return self.__score
119+
120+
@score.setter
121+
def score(self, score: int) -> None:
122+
if score is None or not isinstance(score, int):
98123
raise ValueError(
99-
f'{self.__class__.__name__}.avatar must be Avatar or None. It is a(n) {avatar.__class__.__name__} and has the value of {avatar}.')
100-
self.__avatar = avatar
124+
f'{self.__class__.__name__}.score must be an int. It is a(n) {score.__class__.__name__} and has the value of '
125+
f'{score}')
126+
self.__score: int = score
101127

102128
@property
103129
def object_type(self) -> ObjectType:
@@ -118,7 +144,9 @@ def to_json(self):
118144
data['team_name'] = self.team_name
119145
data['file_name'] = self.file_name
120146
data['actions'] = [act.value for act in self.actions]
121-
data['avatar'] = self.avatar.to_json() if self.avatar is not None else None
147+
data['selected_avatar_type'] = self.__selected_avatar_type
148+
data['avatars'] = { k: v.to_json() if v is not None else None for k, v in self.avatars} if self.avatars is not None else None
149+
data['score'] = self.score
122150

123151
return data
124152

@@ -129,32 +157,15 @@ def from_json(self, data):
129157
self.error = data['error']
130158
self.team_name = data['team_name']
131159
self.file_name = data['file_name']
132-
133-
self.actions: list[ActionType] = [ObjectType(action) for action in data['actions']]
134-
avatar: Avatar | None = data['avatar']
135-
if avatar is None:
136-
self.avatar = None
160+
self.score = data['score']
161+
self.actions: list[ActionType] = [ActionType(action) for action in data['actions']]
162+
self.__selected_avatar_type = data['selected_avatar_type']
163+
avatars: dict[str, Avatar] = data['avatars']
164+
if avatars is None:
165+
self.avatars = None
137166
return self
138-
# match case for action
139-
# match action['object_type']:
140-
# case ObjectType.ACTION:
141-
# self.action = Action().from_json(data['action'])
142-
# case None:
143-
# self.action = None
144-
# case _: # checks if it is anything else
145-
# raise Exception(f'Could not parse action: {self.action}')
146-
147-
# match case for avatar
148-
match ObjectType(avatar['object_type']):
149-
case ObjectType.AVATAR:
150-
self.avatar = Avatar().from_json(data['avatar'])
151-
case None:
152-
self.avatar = None
153-
case _:
154-
raise Exception(f'Could not parse avatar: {self.avatar}')
167+
self.avatars = { k:Avatar().from_json(v) if v is not None else None for k, v in data['avatars']}
155168
return self
156-
# self.action = Action().from_json(data['action']) if data['action'] is not None else None
157-
# self.avatar = Avatar().from_json(data['avatar']) if data['avatar'] is not None else None
158169

159170
# to String
160171
def __str__(self):
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from game.common.enums import ActionType, ObjectType
2+
from game.common.map.game_board import GameBoard
3+
from game.common.player import Player
4+
from game.controllers.controller import Controller
5+
6+
7+
class AvatarSelectionController(Controller):
8+
def __init__(self):
9+
super().__init__()
10+
11+
def handle_actions(self, action: ActionType, client: Player, world: GameBoard) -> None:
12+
"""
13+
Given the ActionType for interacting in a direction, the Player's avatar will engage with the object.
14+
:param action:
15+
:param client:
16+
:param world:
17+
:return: None
18+
"""
19+
20+
match action:
21+
case ActionType.SELECT_DEFAULT_AVATAR:
22+
client.select_avatar(ObjectType.AVATAR)
23+
case _:
24+
return
25+
26+
return

game/controllers/controller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@ def handle_actions(self, action: ActionType, client: Player, world: GameBoard):
2727

2828
def debug(self, *args):
2929
if self.debug and Debug.level >= self.debug_level:
30-
logging.basicConfig(level=logging.DEBUGs)
30+
logging.basicConfig(level=logging.DEBUG)
3131
for arg in args:
3232
logging.debug(f'{self.__class__.__name__}: {arg}')

game/controllers/master_controller.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from game.common.enums import *
77
from game.common.player import Player
88
import game.config as config # this is for turns
9+
from game.controllers.avatar_selection_controller import AvatarSelectionController
910
from game.utils.thread import CommunicationThread
1011
from game.controllers.movement_controller import MovementController
1112
from game.controllers.controller import Controller
@@ -54,6 +55,7 @@ def __init__(self):
5455
self.current_world_data: dict = None
5556
self.movement_controller: MovementController = MovementController()
5657
self.interact_controller: InteractController = InteractController()
58+
self.avatar_selection_controller: AvatarSelectionController = AvatarSelectionController()
5759

5860
# Receives all clients for the purpose of giving them the objects they will control
5961
def give_clients_objects(self, clients: list[Player], world: dict):
@@ -62,7 +64,8 @@ def give_clients_objects(self, clients: list[Player], world: dict):
6264
avatars: list[tuple[Vector, list[Avatar]]] = gb.get_objects(ObjectType.AVATAR)
6365
for avatar, client in zip(avatars, clients):
6466
avatar[1][0].position = avatar[0]
65-
client.avatar = avatar[1][0]
67+
client.avatars = { ObjectType.AVATAR:avatar[1][0] }
68+
client.select_avatar(ObjectType.AVATAR)
6669

6770
# Generator function. Given a key:value pair where the key is the identifier for the current world and the value is
6871
# the state of the world, returns the key that will give the appropriate world information
@@ -95,17 +98,19 @@ def client_turn_arguments(self, client: Player, turn):
9598

9699
# Create deep copies of all objects sent to the player
97100
current_world = deepcopy(self.current_world_data["game_board"]) # what is current world and copy avatar
101+
copy_avatars = deepcopy(client.avatars)
98102
copy_avatar = deepcopy(client.avatar)
99103
# Obfuscate data in objects that that player should not be able to see
100104
# Currently world data isn't obfuscated at all
101-
args = (self.turn, turn_actions, current_world, copy_avatar)
105+
args = (self.turn, turn_actions, current_world, copy_avatars, copy_avatar)
102106
return args
103107

104108
# Perform the main logic that happens per turn
105109
def turn_logic(self, clients: list[Player], turn):
106110
for client in clients:
107111
for i in range(MAX_NUMBER_OF_ACTIONS_PER_TURN):
108112
try:
113+
self.avatar_selection_controller.handle_actions(client.actions[i], client, self.current_world_data["game_board"])
109114
self.movement_controller.handle_actions(client.actions[i], client, self.current_world_data["game_board"])
110115
self.interact_controller.handle_actions(client.actions[i], client, self.current_world_data["game_board"])
111116
except IndexError:

0 commit comments

Comments
 (0)