Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sync server to master #121

Merged
merged 4 commits into from
Oct 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions game/common/map/game_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,10 @@ def __help_populate(self, vector_list: list[Vector], game_object_list: list[Game

temp_tile: GameObject = self.game_map[vector.y][vector.x]

while hasattr(temp_tile.occupied_by, 'occupied_by'):
while temp_tile.occupied_by is not None and hasattr(temp_tile.occupied_by, 'occupied_by'):
temp_tile = temp_tile.occupied_by

if temp_tile is None:
if temp_tile.occupied_by is not None:
raise ValueError("Last item on the given tile doesn't have the 'occupied_by' attribute.")

temp_tile.occupied_by = game_object
Expand All @@ -263,13 +263,12 @@ def __help_populate(self, vector_list: list[Vector], game_object_list: list[Game
# stack remaining game_objects on last vector
temp_tile: GameObject = self.game_map[last_vec.y][last_vec.x]

while hasattr(temp_tile.occupied_by, 'occupied_by'):
while temp_tile.occupied_by is not None and hasattr(temp_tile.occupied_by, 'occupied_by'):
temp_tile = temp_tile.occupied_by

for game_object in remaining_objects:
if temp_tile is None:
if not hasattr(temp_tile, 'occupied_by') or temp_tile.occupied_by is not None:
raise ValueError("Last item on the given tile doesn't have the 'occupied_by' attribute.")

temp_tile.occupied_by = game_object
temp_tile = temp_tile.occupied_by

Expand Down
2 changes: 1 addition & 1 deletion game/common/map/tile.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Tile(Occupiable):
inherit from this class.
"""
def __init__(self, occupied_by: GameObject = None):
super().__init__()
super().__init__(occupied_by)
self.object_type: ObjectType = ObjectType.TILE

def from_json(self, data: dict) -> Self:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,7 @@ def setUp(self) -> None:
Vector(3, 1), Vector(3, 2)): [Station(None), Station(None), Station(None),
Station(None), Station(None), Station(None),
Station(None), Station(None)]}

self.occ_station = OccupiableStation()
self.game_board = GameBoard(0, Vector(4, 4), self.locations, True)
self.wall = Wall()
self.game_board = GameBoard(0, Vector(4, 4), self.locations, False)
# test movements up, down, left and right by starting with default 3,3 then know if it changes from there \/
self.avatar = Avatar(Vector(2, 2), 1)
self.client = Player(None, None, [], self.avatar)
Expand Down
5 changes: 0 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
tqdm~=4.65.0
pygame~=2.3.0
numpy~=1.24.2
Pillow~=9.5.0
opencv-python~=4.8.0.76
Sphinx~=7.0.1
Myst-Parser~=2.0.0
furo~=2023.7.26
parsec~=3.15
opencv-python~=4.8.0.76
Sphinx~=7.0.1
Myst-Parser~=2.0.0
furo~=2023.7.26
SQLAlchemy~=2.0.2
pydantic~=2.3.0
fastapi[all]~=0.103.1
2 changes: 0 additions & 2 deletions visualizer/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from game.utils.vector import Vector
from visualizer.utils.text import Text
from visualizer.utils.button import Button, ButtonColors
from visualizer.utils.sidebars import Sidebars
from visualizer.bytesprites.bytesprite import ByteSprite
from visualizer.templates.menu_templates import Basic, MenuTemplate
from visualizer.templates.playback_template import PlaybackTemplate, PlaybackButtons
Expand Down Expand Up @@ -90,7 +89,6 @@ def populate_bytesprite_factories(self) -> dict[int: Callable[[pygame.Surface],
def render(self) -> None:
# self.button.render()
# any logic for rendering text, buttons, and other visuals
# to access sidebars do sidebars.[whichever sidebar you are doing]
text = Text(self.screen, f'{self.turn_number} / {self.turn_max}', 48)
text.rect.center = Vector.add_vectors(Vector(*self.screen.get_rect().midtop), Vector(0, 50)).as_tuple()
text.render()
Expand Down
123 changes: 103 additions & 20 deletions visualizer/bytesprites/bytesprite.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,97 @@


class ByteSprite(pyg.sprite.Sprite):
"""
`ByteSprite Class Notes:`

PyGame Notes
------------
Here are listed definitions of the PyGame objects that are used in this file:
PyGame.Rect:
"An object for storing rectangular coordinates." This is used to help position things on the screen.

PyGame.Surface:
"An object for representing images." This is used mostly for getting the screen and individual images in
a spritesheet.


Class Variables
---------------
Active Sheet:
The active_sheet is the list of images (sprites) that is currently being used. In other words, it's a strip
of sprites that will be used.

Spritesheets:
This is a 2D array of sprites. For example, refer to the ``ExampleSpritesheet.png`` file. The entirety of the
4x4 would be a spritesheet. One row of it would be used as an active sheet.

Object Type:
This is an int that represents the enum value of the Object the sprite represents. For example, the
``ExampleSpritesheet.png`` shows the Avatar. The Avatar object_type's enum value is found in the JSON logs and
is the number 4. This would change if the order of the ObjectType enum changes, so be mindful of that and
refer to the JSON logs for the exact values.

Rect:
The rect is an object used for rectangular objects. You can offset the top left corner of the Rect by
passing parameters.

Example:

On the left, the Rect's offset is depicted as being at (0, 0), meaning there is no offset. A Rect object
has parameters that will determine the offset by passing in (x, y). The 'x' is the offset from the left
side, and the 'y' is the offset from the top. Therefore, passing in an (x, y) of (3, 2) in a Rect object
will move the object 3 units to the right, and 2 units down.

In the visual below, the left side shows a Rect at (0, 0) (i.e., no offset). The image on the right depicts
the Rect object further to the right, showing its offset from the left corner of the screen.

Rect Example:
::
----------------------- -----------------------
|------ | | ------ |
|| | | | | | |
|______ | --------> | ______ |
| | | |
| | | |
| | | |
----------------------- -----------------------

Screen:
The screen is also a PyGame.Screen object, so it simply represents an image of the screen itself.

Image:
The image is an individual sprite in a spritesheet.

Frame Index:
The frame index is an int that is used to determine which sprite to use from the active_sheet. For example,
say the active_sheet is the first row in the ``ExampleSpritesheet.png``. If the frame_index is 1, the first
image will be used where the head is centered. If the frame_index is 3, the sprite will now have the head of
the Avatar in a different position than in frame_index 1.

Config:
This is an object reference to the ``config.py`` file. It's used to access the fixed values that are only
accessed in the configurations of the file.

Update Function:
The update function is a method that is assigned during instantiation of the ByteSprite. That function is
used to update what the active_sheet is depending on what is implemented in ByteSprite classes.

Examine the ``exampleBS.py`` file. In that implementation of the update method, it selects the active_sheet
based on a chain of if statements. Next, in the ``create_bytesprite`` method, the implemented ``update``
method is passed into the returned ByteSprite object.

Now, in the ByteSprite object's update method, it will set the active_sheet to be based on what is returned
from the BytespriteFactory's method.

To recap, first, a ByteSprite's update function depends on the BytespriteFactory's implementation. Then, the
BytespriteFactory's implementation will return which sprite_sheet is supposed to be used. Finally, the
ByteSprite's update function will take what is returned from the BytespriteFactory's method and assign
the active_sheet to be what is returned. The two work in tandem.
"""

active_sheet: list[pyg.Surface] # The current spritesheet being used.
spritesheets: list[list[pyg.Surface]]
object_type: int
layer: int
rect: pyg.Rect
screen: pyg.Surface
image: pyg.Surface
Expand All @@ -23,7 +110,7 @@ class ByteSprite(pyg.sprite.Sprite):
# make sure that all inherited classes constructors only take screen as a parameter
def __init__(self, screen: pyg.Surface, filename: str, num_of_states: int, object_type: int,
update_function: Callable[[dict, int, Vector, list[list[pyg.Surface]]], list[pyg.Surface]],
colorkey: pyg.Color | None = None, layer: int = 0, top_left: Vector = Vector(0, 0)):
colorkey: pyg.Color | None = None, top_left: Vector = Vector(0, 0)):
# Add implementation here for selecting the sprite sheet to use
super().__init__()
self.spritesheet_parser: SpriteSheet = SpriteSheet(filename)
Expand All @@ -43,7 +130,6 @@ def __init__(self, screen: pyg.Surface, filename: str, num_of_states: int, objec
self.active_sheet: list[pyg.Surface] = self.spritesheets[0]
self.object_type: int = object_type
self.screen: pyg.Surface = screen
self.layer: int = layer

@property
def active_sheet(self) -> list[pyg.Surface]:
Expand All @@ -56,11 +142,6 @@ def spritesheets(self) -> list[list[pyg.Surface]]:
@property
def object_type(self) -> int:
return self.__object_type

@property
def layer(self) -> int:
return self.__layer

@property
def rect(self) -> pyg.Rect:
return self.__rect
Expand Down Expand Up @@ -99,15 +180,6 @@ def object_type(self, object_type: int) -> None:
raise ValueError(f'{self.__class__.__name__}.object_type can\'t be negative.')
self.__object_type = object_type

@layer.setter
def layer(self, layer: int) -> None:
if layer is None or not isinstance(layer, int):
raise ValueError(f'{self.__class__.__name__}.layer must be an int.')

if layer < 0:
raise ValueError(f'{self.__class__.__name__}.layer can\'t be negative.')
self.__layer = layer

@rect.setter
def rect(self, rect: pyg.Rect) -> None:
if rect is None or not isinstance(rect, pyg.Rect):
Expand All @@ -128,19 +200,30 @@ def update_function(self, update_function: Callable[[dict, int, Vector, list[lis

# Inherit this method to implement sprite logic
def update(self, data: dict, layer: int, pos: Vector) -> None:
"""
This method will start an animation based on the currently set active_sheet. Then, it will reassign the
active_sheet based on what the BytespriteFactory's update method will return. Lastly, the
``set_image_and_render`` method is then called to then display the new sprites in the active_sheet.
:param data:
:param layer:
:param pos:
:return: None
"""

self.__frame_index = 0 # Starts the new spritesheet at the beginning
self.rect.topleft = (
pos.x * self.__config.TILE_SIZE * self.__config.SCALE + self.__config.GAME_BOARD_MARGIN_LEFT,
pos.y * self.__config.TILE_SIZE * self.__config.SCALE + self.__config.GAME_BOARD_MARGIN_TOP)

self.update_function(data, layer, pos, self.spritesheets)
self.active_sheet = self.update_function(data, layer, pos, self.spritesheets)
self.set_image_and_render()

# Call this method at the end of the implemented logic and for each frame
def set_image_and_render(self):
"""
This method will take a single image from the current active_sheet and then display it on the screen.
:return:
"""
self.image = self.active_sheet[self.__frame_index]
self.__frame_index = (self.__frame_index + 1) % self.__config.NUMBER_OF_FRAMES_PER_TURN
self.screen.blit(self.image, self.rect)


15 changes: 15 additions & 0 deletions visualizer/bytesprites/bytesprite_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,23 @@
class ByteSpriteFactory:
@staticmethod
def update(data: dict, layer: int, pos: Vector, spritesheets: list[list[pyg.Surface]]) -> list[pyg.Surface]:
"""
This is a method that **must** be implemented in every ByteSpriteFactory class. Look at the example files
to see how this *could* be implemented. Implementation may vary.
:param data:
:param layer:
:param pos:
:param spritesheets:
:return: list[pyg.Surface]
"""
...

@staticmethod
def create_bytesprite(screen: pyg.Surface) -> ByteSprite:
"""
This is a method that **must** be implemented in every ByteSpriteFactory class. Look at the example files
to see how this can be implemented.
:param screen:
:return:
"""
...
20 changes: 20 additions & 0 deletions visualizer/bytesprites/exampleBS.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,23 @@


class AvatarBytespriteFactoryExample(ByteSpriteFactory):
"""
`Avatar Bytesprite Factory Example Notes`:

This is a factory class that will produce Bytesprite objects of the Avatar.
"""
@staticmethod
def update(data: dict, layer: int, pos: Vector, spritesheets: list[list[pyg.Surface]]) -> list[pyg.Surface]:
"""
This method will select which spritesheet to select from the ``ExampleSpritesheet.png`` file. For example,
the first if statement will return the second row of sprites in the image if conditions are met.
:param data:
:param layer:
:param pos:
:param spritesheets:
:return: list[pyg.Surface]
"""

# Logic for selecting active animation
if data['inventory'][data['held_index']] is not None:
return spritesheets[1]
Expand All @@ -23,5 +38,10 @@ def update(data: dict, layer: int, pos: Vector, spritesheets: list[list[pyg.Surf

@staticmethod
def create_bytesprite(screen: pyg.Surface) -> ByteSprite:
"""
This file will return a new ByteSprite object that is to be displayed on the screen.
:param screen: ByteSprite
:return:
"""
return ByteSprite(screen, os.path.join(os.getcwd(), 'visualizer/spritesheets/ExampleSpritesheet.png'), 4,
4, AvatarBytespriteFactoryExample.update, pyg.Color("#FBBBAD"))
27 changes: 27 additions & 0 deletions visualizer/bytesprites/exampleTileBS.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,41 @@


class TileBytespriteFactoryExample(ByteSpriteFactory):
"""
This class is used to demonstrate an example of the Tile Bytesprite. It demonstrates how any class inheriting
from ByteSpriteFactory must implement the `update()` and `create_bytesprite()` static methods. These methods may
have unique implementations based on how the sprites are meant to look and interact with other objects in the game.
"""
@staticmethod
def update(data: dict, layer: int, pos: Vector, spritesheets: list[list[pyg.Surface]]) -> list[pyg.Surface]:
"""
This implementation of the update method is different from the exampleWallBS.py file. In this method, the
data dictionary is used. The `data` is a dict representing a Tile object in JSON notation.

For this unique implementation, an if statement is used to check if something is occupying the Tile object.
If true, the second spritesheet is used. If false, the first spritesheet is used.

Examining the ExampleTileSS.png, it is apparent that the first spritesheet shows a Tile with an animation with
only the pink color. However, the second spritesheet (the one used if something occupies that tile) has a unique
animation that is blue instead.
:param data:
:param layer:
:param pos:
:param spritesheets:
:return:
"""
if data['occupied_by'] is not None:
return spritesheets[1]
else:
return spritesheets[0]

@staticmethod
def create_bytesprite(screen: pyg.Surface) -> ByteSprite:
"""
This method takes a screen from Pygame.Surface. That screen is then passed in as a parameter into the
returned Bytesprite object.
:param screen:
:return:
"""
return ByteSprite(screen, os.path.join(os.getcwd(), 'visualizer/spritesheets/ExampleTileSS.png'), 2,
7, TileBytespriteFactoryExample.update)
21 changes: 21 additions & 0 deletions visualizer/bytesprites/exampleWallBS.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,32 @@


class WallBytespriteFactoryExample(ByteSpriteFactory):
"""
This class is used to demonstrate an example of the Wall Bytesprite. It demonstrates how any class inheriting
from ByteSpriteFactory must implement the `update()` and `create_bytesprite()` static methods. These methods may
have unique implementations based on how the sprites are meant to look and interact with other objects in the game.
"""
@staticmethod
def update(data: dict, layer: int, pos: Vector, spritesheets: list[list[pyg.Surface]]) -> list[pyg.Surface]:
"""
This method implementation simply returns the first spritesheet in the list of given spritesheets. Examining the
`ExampleWallSS.png` file, it is clear that there is only one spritesheet, so that is all this method needs to
do.
:param data:
:param layer:
:param pos:
:param spritesheets:
:return:
"""
return spritesheets[0]

@staticmethod
def create_bytesprite(screen: pyg.Surface) -> ByteSprite:
"""
This method takes a screen from Pygame.Surface. That screen is then passed in as a parameter into the
returned Bytesprite object.
:param screen:
:return: a ByteSprite object
"""
return ByteSprite(screen, os.path.join(os.getcwd(), 'visualizer/spritesheets/ExampleWallSS.png'), 1,
8, WallBytespriteFactoryExample.update)
Loading