Skip to content

Commit

Permalink
Merge pull request #36 from remance/battle-loading-screen-feedback
Browse files Browse the repository at this point in the history
Improve loading speed and output
  • Loading branch information
remance committed Apr 10, 2023
2 parents 6c202c1 + 1a9743c commit 4ebd7d4
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 4 deletions.
49 changes: 45 additions & 4 deletions gamescript/battle.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import glob
import os
import sys
import time
from datetime import datetime, timedelta
from random import randint

Expand All @@ -10,12 +11,12 @@
from gamescript import camera, weather, battleui, subunit, datasprite, damagesprite, effectsprite, ai
from gamescript.common import utility

direction_list = datasprite.direction_list

from pygame.locals import *

from pathlib import Path

direction_list = datasprite.direction_list


load_image = utility.load_image
load_images = utility.load_images
csv_read = utility.csv_read
Expand All @@ -27,6 +28,24 @@
script_dir = os.path.split(os.path.abspath(__file__))[0] + "/"


# ----
# perhaps this can be in its own file?

load_timer = None


def set_start_load(what):
globals()['load_timer'] = time.time()
return "Loading {0}... ".format(what)


def set_done_load():
duration = time.time() - globals()['load_timer']
return " DONE ({0}s)\n".format(duration)

# ---


class Battle:
empty_method = utility.empty_method

Expand Down Expand Up @@ -299,8 +318,15 @@ def __init__(self, main):

def prepare_new_game(self, ruleset, ruleset_folder, team_selected, map_type, map_selected,
map_source, char_selected, map_info, camp_pos):
"""Setup stuff when start new battle"""

for message in self.inner_prepare_new_game(ruleset, ruleset_folder, team_selected, map_type, map_selected,
map_source, char_selected, map_info, camp_pos):
print(message, end="")

def inner_prepare_new_game(self, ruleset, ruleset_folder, team_selected, map_type, map_selected,
map_source, char_selected, map_info, camp_pos):
self.language = self.main.language
"""Setup stuff when start new battle"""

self.ruleset = ruleset # current ruleset used
self.ruleset_folder = ruleset_folder # the folder of rulseset used
Expand Down Expand Up @@ -328,6 +354,7 @@ def prepare_new_game(self, ruleset, ruleset_folder, team_selected, map_type, map
self.team_colour = self.main.team_colour

# Load weather schedule
yield set_start_load("weather")
try:
self.weather_event = csv_read(self.main_dir, "weather.csv",
("data", "ruleset", self.ruleset_folder, "map", map_type, self.map_selected,
Expand All @@ -340,8 +367,10 @@ def prepare_new_game(self, ruleset, ruleset_folder, team_selected, map_type, map
new_time = timedelta(hours=new_time.hour, minutes=new_time.minute, seconds=new_time.second)
self.weather_event = ((4, new_time, 0, 0),) # default weather light sunny all day
self.weather_playing = self.weather_event[0][1] # used as the reference for map starting time
yield set_done_load()

# Random music played from list
yield set_start_load("music")
if pygame.mixer:
self.SONG_END = pygame.USEREVENT + 1
self.music_list = glob.glob(os.path.join(self.main_dir, "data", "sound", "music", "*.ogg"))
Expand All @@ -367,7 +396,9 @@ def prepare_new_game(self, ruleset, ruleset_folder, team_selected, map_type, map
except: # any reading error will play random custom music instead
self.music_schedule = [self.weather_playing]
self.music_event = [] # TODO change later when has custom playlist
yield set_done_load()

yield set_start_load("map events")
try: # get new map event for event log
map_event = csv_read(self.main_dir, "eventlog_" + self.language + ".csv",
("data", "ruleset", self.ruleset_folder, "map", map_type,
Expand All @@ -391,7 +422,9 @@ def prepare_new_game(self, ruleset, ruleset_folder, team_selected, map_type, map
self.event_list.append(event)

self.time_number.start_setup(self.weather_playing)
yield set_done_load()

yield set_start_load("images")
images = load_images(self.main_dir,
subfolder=("ruleset", self.ruleset_folder, "map", map_type, self.map_selected))
self.battle_map_base.draw_image(images["base"])
Expand All @@ -402,8 +435,13 @@ def prepare_new_game(self, ruleset, ruleset_folder, team_selected, map_type, map
place_name_map = images["place_name"]
else:
place_name_map = None
yield set_done_load()

yield set_start_load("draw map")
self.battle_map.draw_image(self.battle_map_base, self.battle_map_feature, place_name_map, self.camp_pos, self)
yield set_done_load()

yield set_start_load("common setup")
self.map_corner = (
len(self.battle_map_base.map_array[0]),
len(self.battle_map_base.map_array)) # get map size that troop can move
Expand Down Expand Up @@ -434,11 +472,14 @@ def prepare_new_game(self, ruleset, ruleset_folder, team_selected, map_type, map
self.visible_subunit_list = {key: {} for key in self.all_team_subunit.keys()}

self.battle_scale_ui.change_fight_scale(self.battle_scale)
yield set_done_load()

yield set_start_load("sprites")
subunit_to_make = tuple(set([this_subunit.troop_id for this_subunit in self.subunit_updater]))
who_todo = {key: value for key, value in self.troop_data.troop_list.items() if key in subunit_to_make}
who_todo |= {key: value for key, value in self.leader_data.leader_list.items() if key in subunit_to_make}
self.subunit_animation_pool, self.status_animation_pool = self.main.create_troop_sprite_pool(who_todo)
yield set_done_load()

def run_game(self):
# Create Starting Values
Expand Down
36 changes: 36 additions & 0 deletions gamescript/common/game/create_troop_sprite_pool.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,49 @@
import os
import hashlib
import pickle
import pygame
import threading
from random import choice

import pygame
from PIL import Image

from gamescript.common.game import create_troop_sprite
from gamescript.create_troop_sprite_pool_methods import recursive_cast_surface_to_pickleable_surface
from gamescript.create_troop_sprite_pool_methods import recursive_cast_pickleable_surface_to_surface


def create_troop_sprite_pool(self, who_todo, preview=False, specific_preview=None, max_preview_size=200):

stringified_arguments = "".join(sorted(map(str, who_todo.keys())))+"p"+str(max_preview_size)
md5 = hashlib.md5(stringified_arguments.encode()).hexdigest()

cache_file_path = os.path.join(self.main_dir, 'cache_{0}.pickle'.format(md5))

if not os.path.isfile(cache_file_path):
pool = inner_create_troop_sprite_pool(self, who_todo, preview, specific_preview, max_preview_size)

assert type(pool) == tuple
assert len(pool) == 2
assert type(pool[0]) == dict
assert type(pool[1]) == dict

recursive_cast_surface_to_pickleable_surface(pool[0])
recursive_cast_surface_to_pickleable_surface(pool[1])

with open(cache_file_path, "wb") as handle:
pickle.dump(pool, handle)

with open(cache_file_path, "rb") as handle:
pool = pickle.load(handle)

recursive_cast_pickleable_surface_to_surface(pool[0])
recursive_cast_pickleable_surface_to_surface(pool[1])

return pool


def inner_create_troop_sprite_pool(self, who_todo, preview=False, specific_preview=None, max_preview_size=200):
weapon_list = self.troop_data.weapon_list
animation_sprite_pool = {}
status_animation_pool = {}
Expand Down
40 changes: 40 additions & 0 deletions gamescript/create_troop_sprite_pool_methods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import pygame

class PickleableSurface:

def __init__(self, surface):
self.surface = surface

def __getstate__(self):
return ( pygame.image.tobytes(self.surface,"RGBA"), self.surface.get_size() )



def __setstate__(self, state):
self.surface = pygame.image.frombytes(state[0],state[1],"RGBA")




def recursive_cast_surface_to_pickleable_surface(_dict):

for k,v in _dict.items():
if type(v) == dict:
recursive_cast_surface_to_pickleable_surface(v)
elif type(v) == pygame.Surface:
_dict[k] = PickleableSurface(v)
elif type(v) == tuple:
_dict[k] = tuple([ c if type(c) != pygame.Surface else PickleableSurface(c) for c in _dict[k]])


def recursive_cast_pickleable_surface_to_surface(_dict):

for k,v in _dict.items():
if type(v) == dict:
recursive_cast_pickleable_surface_to_surface(v)
if type(v) == PickleableSurface:
_dict[k] = v.surface
elif type(v) == tuple:
_dict[k] = tuple([ c if type(c) != PickleableSurface else c.surface for c in _dict[k]])


5 changes: 5 additions & 0 deletions gamescript/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,14 @@ class Game:
file_name = entry.name[:-4]
elif ".py" in entry.name:
file_name = entry.name[:-3]

if file_name.startswith("."): continue

exec(f"from gamescript.common.game import " + file_name)
exec(f"" + file_name + " = " + file_name + "." + file_name)


# Will be changed in change_game_genre function depending on selected genre
troop_sprite_size = (200, 200)
start_zoom_mode = "Follow"

Expand Down
1 change: 1 addition & 0 deletions pep8.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pep8 --max-line-length=180 --first --exclude='env' $(git diff HEAD main --name-only)
60 changes: 60 additions & 0 deletions tests/test_create_troop_sprite_pool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from unittest.mock import patch

def test_pickleable_surface():
import pickle
import pygame
#from gamescript.common.game.create_troop_sprite_pool import PickleableSurface
from gamescript.create_troop_sprite_pool_methods import PickleableSurface
import io

surface = pygame.Surface((64,)*2)
pygame.draw.circle( surface, 'red', (32,32), 32 )
ps = PickleableSurface( surface )

handle = io.BytesIO()
pickle.dump(ps,handle)


handle.seek(0)

ps2 = pickle.load(handle)

s1 = surface
s2 = ps2.surface

for x in range(64):
for y in range(64):
assert s1.get_at( (x,y) ) == s2.get_at((x,y))



@patch("gamescript.create_troop_sprite_pool_methods.PickleableSurface.__eq__",
lambda self,other : self.surface == other.surface)
def test_recursive_surface_to_pickleable_surface():
import pygame
from gamescript.create_troop_sprite_pool_methods import recursive_cast_surface_to_pickleable_surface
from gamescript.create_troop_sprite_pool_methods import PickleableSurface

surface = pygame.Surface((64,)*2)
pygame.draw.circle( surface, 'red', (32,32), 32 )



_dict = {
"a":{
"b": surface
}
}


recursive_cast_surface_to_pickleable_surface(_dict)

assert _dict == {
"a": {
"b": PickleableSurface(surface)
}
}




0 comments on commit 4ebd7d4

Please sign in to comment.