Skip to content

Commit

Permalink
Changed bytesprite architecture and implemented new config (#104)
Browse files Browse the repository at this point in the history
* Added visualizer config option for held items and refactored list comprehension

* Changed bytesprite creation model to be more explicit and clear
  • Loading branch information
JeanAEckelberg authored Oct 4, 2023
1 parent d348bca commit d55ed2f
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 94 deletions.
17 changes: 9 additions & 8 deletions visualizer/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import pygame
from game.config import *
from typing import Callable, Any
from visualizer.bytesprites.exampleTileBS import ExampleTileBS
from visualizer.bytesprites.exampleWallBS import ExampleWallBS
from visualizer.bytesprites.exampleBS import ExampleBS
from visualizer.bytesprites.exampleTileBS import TileBytespriteFactoryExample
from visualizer.bytesprites.exampleWallBS import WallBytespriteFactoryExample
from visualizer.bytesprites.exampleBS import AvatarBytespriteFactoryExample
from game.utils.vector import Vector
from visualizer.utils.text import Text
from visualizer.utils.button import Button, ButtonColors
Expand Down Expand Up @@ -79,12 +79,13 @@ def continue_animation(self) -> None:
def recalc_animation(self, turn_log: dict) -> None:
self.turn_number = turn_log['tick']

def populate_bytesprites(self) -> pygame.sprite.Group:
def populate_bytesprite_factories(self) -> dict[int: Callable[[pygame.Surface], ByteSprite]]:
# Instantiate all bytesprites for each object ands add them here
self.populate_bytesprite.add(ExampleTileBS(self.screen))
self.populate_bytesprite.add(ExampleWallBS(self.screen))
self.populate_bytesprite.add(ExampleBS(self.screen))
return self.populate_bytesprite.copy()
return {
4: AvatarBytespriteFactoryExample().create_bytesprite,
7: TileBytespriteFactoryExample().create_bytesprite,
8: WallBytespriteFactoryExample().create_bytesprite,
}

def render(self) -> None:
# self.button.render()
Expand Down
27 changes: 23 additions & 4 deletions visualizer/bytesprites/bytesprite.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import pygame
from __future__ import annotations
from typing import Callable

import pygame as pyg

from visualizer.config import Config
Expand All @@ -16,10 +18,12 @@ class ByteSprite(pyg.sprite.Sprite):
image: pyg.Surface
__frame_index: int # Selects the sprite from the spritesheet to be used. Used for animation
__config: Config = Config()
__update_function: Callable[[dict, int, Vector, list[list[pyg.Surface]]], list[pyg.Surface]]

# 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, colorkey: pyg.Color | None = None,
object_type: int = 0, layer: int = 0, top_left: Vector = Vector(0, 0)):
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)):
# Add implementation here for selecting the sprite sheet to use
super().__init__()
self.spritesheet_parser: SpriteSheet = SpriteSheet(filename)
Expand All @@ -34,7 +38,7 @@ def __init__(self, screen: pyg.Surface, filename: str, num_of_states: int, color
[pyg.transform.scale(frame, self.rect.size) for frame in
sheet] for sheet in self.spritesheets]


self.update_function = update_function

self.active_sheet: list[pyg.Surface] = self.spritesheets[0]
self.object_type: int = object_type
Expand Down Expand Up @@ -65,6 +69,10 @@ def rect(self) -> pyg.Rect:
def screen(self) -> pyg.Surface:
return self.__screen

@property
def update_function(self) -> Callable[[dict, int, Vector, list[list[pyg.Surface]]], list[pyg.Surface]]:
return self.__update_function

@active_sheet.setter
def active_sheet(self, sheet: list[pyg.Surface]) -> None:
if sheet is None or not isinstance(sheet, list) and \
Expand Down Expand Up @@ -112,6 +120,12 @@ def screen(self, screen: pyg.Surface) -> None:
raise ValueError(f'{self.__class__.__name__}.screen must be a pyg.Screen object.')
self.__screen = screen

@update_function.setter
def update_function(self, update_function: Callable[[dict, int, Vector, list[list[pyg.Surface]]], list[pyg.Surface]]) -> None:
if update_function is None or not isinstance(update_function, Callable):
raise ValueError(f'{self.__class__.__name__}.update_function must be a Callable object.')
self.__update_function = update_function

# Inherit this method to implement sprite logic
def update(self, data: dict, layer: int, pos: Vector) -> None:

Expand All @@ -120,8 +134,13 @@ def update(self, data: dict, layer: int, pos: Vector) -> None:
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.set_image_and_render()

# Call this method at the end of the implemented logic and for each frame
def set_image_and_render(self):
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)


14 changes: 14 additions & 0 deletions visualizer/bytesprites/bytesprite_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import pygame as pyg

from game.utils.vector import Vector
from visualizer.bytesprites.bytesprite import ByteSprite


class ByteSpriteFactory:
@staticmethod
def update(data: dict, layer: int, pos: Vector, spritesheets: list[list[pyg.Surface]]) -> list[pyg.Surface]:
...

@staticmethod
def create_bytesprite(screen: pyg.Surface) -> ByteSprite:
...
25 changes: 12 additions & 13 deletions visualizer/bytesprites/exampleBS.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,23 @@

from visualizer.bytesprites.bytesprite import ByteSprite
from game.utils.vector import Vector
from visualizer.bytesprites.bytesprite_factory import ByteSpriteFactory


class ExampleBS(ByteSprite):
def __init__(self, screen: pyg.Surface):
super().__init__(screen, os.path.join(os.getcwd(), 'visualizer/spritesheets/ExampleSpritesheet.png'), 4,
pyg.Color("#FBBBAD"), 4)

def update(self, data: dict, layer: int, pos: Vector) -> None:
super().update(data, layer, pos)

class AvatarBytespriteFactoryExample(ByteSpriteFactory):
@staticmethod
def update(data: dict, layer: int, pos: Vector, spritesheets: list[list[pyg.Surface]]) -> list[pyg.Surface]:
# Logic for selecting active animation
if data['inventory'][data['held_index']] is not None:
self.active_sheet = self.spritesheets[1]
return spritesheets[1]
elif random.randint(1, 6) == 6:
self.active_sheet = self.spritesheets[2]
return spritesheets[2]
elif random.randint(1, 4) == 4:
self.active_sheet = self.spritesheets[3]
return spritesheets[3]
else:
self.active_sheet = self.spritesheets[0]
return spritesheets[0]

self.set_image_and_render()
@staticmethod
def create_bytesprite(screen: pyg.Surface) -> ByteSprite:
return ByteSprite(screen, os.path.join(os.getcwd(), 'visualizer/spritesheets/ExampleSpritesheet.png'), 4,
4, AvatarBytespriteFactoryExample.update, pyg.Color("#FBBBAD"))
19 changes: 10 additions & 9 deletions visualizer/bytesprites/exampleTileBS.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@

from visualizer.bytesprites.bytesprite import ByteSprite
from game.utils.vector import Vector
from visualizer.bytesprites.bytesprite_factory import ByteSpriteFactory


class ExampleTileBS(ByteSprite):
def __init__(self, screen: pyg.Surface):
super().__init__(screen, os.path.join(os.getcwd(), 'visualizer/spritesheets/ExampleTileSS.png'), 2, None, 7)

def update(self, data: dict, layer: int, pos: Vector) -> None:
super().update(data, layer, pos)
class TileBytespriteFactoryExample(ByteSpriteFactory):
@staticmethod
def update(data: dict, layer: int, pos: Vector, spritesheets: list[list[pyg.Surface]]) -> list[pyg.Surface]:
if data['occupied_by'] is not None:
self.active_sheet = self.spritesheets[1]
return spritesheets[1]
else:
self.active_sheet = self.spritesheets[0]
return spritesheets[0]

self.set_image_and_render()
@staticmethod
def create_bytesprite(screen: pyg.Surface) -> ByteSprite:
return ByteSprite(screen, os.path.join(os.getcwd(), 'visualizer/spritesheets/ExampleTileSS.png'), 2,
7, TileBytespriteFactoryExample.update)
16 changes: 9 additions & 7 deletions visualizer/bytesprites/exampleWallBS.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

from visualizer.bytesprites.bytesprite import ByteSprite
from game.utils.vector import Vector
from visualizer.bytesprites.bytesprite_factory import ByteSpriteFactory


class ExampleWallBS(ByteSprite):
def __init__(self, screen: pyg.Surface):
super().__init__(screen, os.path.join(os.getcwd(), 'visualizer/spritesheets/ExampleWallSS.png'), 1, None, 8)
class WallBytespriteFactoryExample(ByteSpriteFactory):
@staticmethod
def update(data: dict, layer: int, pos: Vector, spritesheets: list[list[pyg.Surface]]) -> list[pyg.Surface]:
return spritesheets[0]

def update(self, data: dict, layer: int, pos: Vector) -> None:
super().update(data, layer, pos)
self.active_sheet = self.spritesheets[0]
self.set_image_and_render()
@staticmethod
def create_bytesprite(screen: pyg.Surface) -> ByteSprite:
return ByteSprite(screen, os.path.join(os.getcwd(), 'visualizer/spritesheets/ExampleWallSS.png'), 1,
8, WallBytespriteFactoryExample.update)
41 changes: 3 additions & 38 deletions visualizer/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,7 @@ class Config:
__BACKGROUND_COLOR: (int, int, int) = 0, 0, 0
__GAME_BOARD_MARGIN_LEFT: int = 440
__GAME_BOARD_MARGIN_TOP: int = 100
__SIDEBAR_TOP_DIMENSIONS: Vector = Vector(x=1366, y=80) # width, height
__SIDEBAR_BOTTOM_DIMENSIONS: Vector = Vector(x=1366, y=80)
__SIDEBAR_LEFT_DIMENSIONS: Vector = Vector(x=200, y=768)
__SIDEBAR_RIGHT_DIMENSIONS: Vector = Vector(x=200, y=768)
__SIDEBAR_TOP_PADDING: int = 5 # how much space there is around it
__SIDEBAR_BOTTOM_PADDING: int = 5
__SIDEBAR_LEFT_PADDING: int = 5
__SIDEBAR_RIGHT_PADDING: int = 5
__VISUALIZE_HELD_ITEMS: bool = True

# if you have an animation, this will be the number of frames the animation goes through for each turn
@property
Expand Down Expand Up @@ -59,36 +52,8 @@ def GAME_BOARD_MARGIN_TOP(self) -> int:
return self.__GAME_BOARD_MARGIN_TOP

@property
def SIDEBAR_TOP_DIMENSIONS(self) -> Vector:
return self.__SIDEBAR_TOP_DIMENSIONS

@property
def SIDEBAR_BOTTOM_DIMENSIONS(self) -> Vector:
return self.__SIDEBAR_BOTTOM_DIMENSIONS

@property
def SIDEBAR_LEFT_DIMENSIONS(self) -> Vector:
return self.__SIDEBAR_LEFT_DIMENSIONS

@property
def SIDEBAR_RIGHT_DIMENSIONS(self) -> Vector:
return self.__SIDEBAR_RIGHT_DIMENSIONS

@property
def SIDEBAR_TOP_PADDING(self) -> int:
return self.__SIDEBAR_TOP_PADDING

@property
def SIDEBAR_BOTTOM_PADDING(self) -> int:
return self.__SIDEBAR_BOTTOM_PADDING

@property
def SIDEBAR_LEFT_PADDING(self) -> int:
return self.__SIDEBAR_LEFT_PADDING

@property
def SIDEBAR_RIGHT_PADDING(self) -> int:
return self.__SIDEBAR_RIGHT_PADDING
def VISUALIZE_HELD_ITEMS(self) -> bool:
return self.__VISUALIZE_HELD_ITEMS



30 changes: 15 additions & 15 deletions visualizer/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def __init__(self):
self.clock: pygame.time.Clock = pygame.time.Clock()

self.tick: int = 0
self.bytesprite_templates: pygame.sprite.Group = pygame.sprite.Group()
self.bytesprite_factories: dict[int: Callable[[pygame.Surface], ByteSprite]] = {}
self.bytesprite_map: [[[ByteSprite]]] = list()

self.default_frame_rate: int = self.config.FRAME_RATE
Expand All @@ -45,11 +45,12 @@ def __init__(self):
size: tuple[int, int] = (self.config.SCREEN_SIZE.x, self.config.SCREEN_SIZE.y)
# Scale for video saving (division can be adjusted, higher division = lower quality)
self.scaled: tuple[int, int] = (size[0] // 2, size[1] // 2)
self.writer: cv2.VideoWriter = cv2.VideoWriter("out.mp4", cv2.VideoWriter_fourcc(*'H264'), self.default_frame_rate, self.scaled)
self.writer: cv2.VideoWriter = cv2.VideoWriter("out.mp4", cv2.VideoWriter_fourcc(*'H264'),
self.default_frame_rate, self.scaled)

def load(self) -> None:
self.turn_logs: dict = logs_to_dict()
self.bytesprite_templates = self.adapter.populate_bytesprites()
self.bytesprite_factories = self.adapter.populate_bytesprite_factories()

def prerender(self) -> None:
self.screen.fill(self.config.BACKGROUND_COLOR)
Expand Down Expand Up @@ -162,8 +163,9 @@ def recalc_animation(self, turn_data: dict) -> None:
# Call render logic on bytesprite
self.bytesprite_map[y][x][z].update(temp_tile, z, Vector(y=y, x=x))
# increase iteration
temp_tile = temp_tile.get('occupied_by') if temp_tile.get(
'occupied_by') is not None else temp_tile.get('held_item')
temp_tile = temp_tile.get('occupied_by') if temp_tile.get('occupied_by') is not None \
else (temp_tile.get('held_item') if self.config.VISUALIZE_HELD_ITEMS
else None)
z += 1

# clean up additional layers
Expand All @@ -181,20 +183,18 @@ def __add_needed_layers(self, x: int, y: int, z: int) -> None:

# Create bytesprite at current tile ran in recalc_animation method
def __create_bytesprite(self, x: int, y: int, z: int, temp_tile: dict | None) -> None:
if self.bytesprite_map[y][x][z] is None or self.bytesprite_map[y][x][z].object_type != temp_tile[
'object_type']:
if len(self.bytesprite_templates.sprites()) == 0:
raise ValueError(f'must provide bytesprites for visualization!')
sprite_class: ByteSprite | None = next(t for t in self.bytesprite_templates.sprites() if
isinstance(t, ByteSprite) and t.object_type == temp_tile[
'object_type'])
if self.bytesprite_map[y][x][z] is None or \
self.bytesprite_map[y][x][z].object_type != temp_tile['object_type']:
if len(self.bytesprite_factories) == 0:
raise ValueError(f'must provide bytesprite factories for visualization!')
# Check that a bytesprite template exists for current object type
if sprite_class is None:
factory_function: Callable[[pygame.Surface], ByteSprite] | None = self.bytesprite_factories.get(temp_tile['object_type'])
if factory_function is None:
raise ValueError(
f'Must provide a bytesprite for each object type! Missing object_type: {temp_tile["object_type"]}')

# Instantiate a new bytesprite on current layer
self.bytesprite_map[y][x][z] = sprite_class.__class__(self.screen)
self.bytesprite_map[y][x][z] = factory_function(self.screen)

# Additional layer clean up method ran in recalc_animation method
def __clean_up_layers(self, x: int, y: int, z: int) -> None:
Expand All @@ -205,7 +205,7 @@ def continue_animation(self) -> None:
row: list
tile: list
sprite: ByteSprite
[[[sprite.set_image_and_render() for sprite in tile] for tile in row] for row in self.bytesprite_map]
[sprite.set_image_and_render() for row in self.bytesprite_map for tile in row for sprite in tile]

def postrender(self) -> None:
self.adapter.clean_up()
Expand Down

0 comments on commit d55ed2f

Please sign in to comment.