playscii/games/wildflowers/scripts/flower.py

173 lines
7.2 KiB
Python

import random
import time
from art import ART_DIR, UV_FLIPX, UV_FLIPY, UV_ROTATE180
from game_object import GameObject
from games.wildflowers.scripts.frond import Frond
from games.wildflowers.scripts.petal import Petal
from games.wildflowers.scripts.ramps import PALETTE_RAMPS
from renderable import TileRenderable
# TODO: random size range?
# (should also change camera zoom, probably frond/petal counts)
FLOWER_WIDTH, FLOWER_HEIGHT = 16, 16
class FlowerObject(GameObject):
generate_art = True
should_save = False
physics_move = False
art_width, art_height = FLOWER_WIDTH, FLOWER_HEIGHT
min_petals, max_petals = 0, 4
min_fronds, max_fronds = 0, 8
# every flower must have at least this many petals + fronds
minimum_complexity = 4
# app updates per grow update; 1 = grow every frame
ticks_per_grow = 4
# DEBUG: if True, add current time to date seed as a decimal,
# to test with highly specific values
# (note: this turns the seed from an int into a float)
seed_includes_time = False
# DEBUG: if nonzero, use this seed for testing
debug_seed = 0
debug_log = False
def __init__(self, world, obj_data=None):
GameObject.__init__(self, world, obj_data)
# set random seed based on date, a different flower each day
t = time.localtime()
year, month, day = t.tm_year, t.tm_mon, t.tm_mday
date = year * 10000 + month * 100 + day
if self.seed_includes_time:
date += t.tm_hour * 0.01 + t.tm_min * 0.0001 + t.tm_sec * 0.000001
if self.debug_seed != 0:
self.seed = self.debug_seed
else:
self.seed = date
random.seed(self.seed)
# pick a random dark BG color (will be quantized to palette)
r, g, b = random.random() / 10, random.random() / 10, random.random() / 10
# set up art with character set, size, and a random (supported) palette
self.art.set_charset_by_name("jpetscii")
palette = random.choice(list(PALETTE_RAMPS.keys()))
self.art.set_palette_by_name(palette)
# quantize bg color and set it for art and world
self.bg_index = self.art.palette.get_closest_color_index(
int(r * 255), int(g * 255), int(b * 255)
)
bg_color = self.art.palette.colors[self.bg_index]
self.world.bg_color[0] = bg_color[0] / 255.0
self.world.bg_color[1] = bg_color[1] / 255.0
self.world.bg_color[2] = bg_color[2] / 255.0
self.world.bg_color[3] = 1.0 # set here or alpha is zero?
self.art.resize(self.art_width, self.art_height)
self.app.ui.adjust_for_art_resize(self) # grid etc
self.art.clear_frame_layer(0, 0, bg_color=self.bg_index)
# petals on a layer underneath fronds?
# self.art.add_layer(z=-0.001, name='petals')
self.finished_growing = False
# some flowers can be more petal-centric or frond-centric,
# but keep a certain minimum complexity
petal_count = random.randint(self.min_petals, self.max_petals)
frond_count = random.randint(self.min_fronds, self.max_fronds)
while petal_count + frond_count < self.minimum_complexity:
petal_count = random.randint(self.min_petals, self.max_petals)
frond_count = random.randint(self.min_fronds, self.max_fronds)
self.petals = []
# petal_count = 5 # DEBUG
for i in range(petal_count):
self.petals.append(Petal(self, i))
# sort petals by radius largest to smallest,
# so big ones don't totally stomp smaller ones
self.petals.sort(key=lambda item: item.goal_radius, reverse=True)
self.fronds = []
# frond_count = 0 # DEBUG
for i in range(frond_count):
self.fronds.append(Frond(self, i))
# track # of growth updates we've had
self.grows = 0
# create an art document we can add frames to and later export
self.export_filename = "{}{}wildflower_{}".format(
self.app.documents_dir,
ART_DIR,
self.seed,
)
self.exportable_art = self.app.new_art(
self.export_filename,
self.art_width,
self.art_height,
self.art.charset.name,
self.art.palette.name,
)
# re-set art's filename to be in documents dir rather than game dir :/
self.exportable_art.set_filename(self.export_filename)
# image export process needs a renderable
r = TileRenderable(self.app, self.exportable_art)
def update(self):
GameObject.update(self)
# grow only every few ticks, so you can watch the design grow
if self.app.updates % self.ticks_per_grow != 0:
return
if not self.finished_growing:
self.update_growth()
def update_growth(self):
if self.debug_log:
print("update growth:")
grew = False
for p in self.petals:
if not p.finished_growing:
grew = True
p.grow()
# break so that each petal grows one at a time
break
for f in self.fronds:
# break if still growing petals
if grew:
break
if not f.finished_growing:
grew = True
painted = f.grow()
while not painted and not f.finished_growing:
painted = f.grow()
# break so that each frond grows one at a time
break
self.copy_new_frame()
self.grows += 1
if not grew:
self.finished_growing = True
self.exportable_art.set_active_frame(self.exportable_art.frames - 1)
if self.debug_log:
print("flower finished")
def paint_mirrored(self, layer, x, y, char, fg, bg=None):
# only paint if in top left quadrant
if x > (self.art_width / 2) - 1 or y > (self.art_height / 2) - 1:
return
# draw in top left
self.art.set_tile_at(0, layer, x, y, char, fg, bg)
# mirror in other three quadrants
top_right = (self.art_width - 1 - x, y)
bottom_left = (x, self.art_height - 1 - y)
bottom_right = (self.art_width - 1 - x, self.art_height - 1 - y)
self.art.set_tile_at(0, layer, *top_right, char, fg, bg, transform=UV_FLIPX)
self.art.set_tile_at(0, layer, *bottom_left, char, fg, bg, transform=UV_FLIPY)
self.art.set_tile_at(
0, layer, *bottom_right, char, fg, bg, transform=UV_ROTATE180
)
def copy_new_frame(self):
# add new frame to art for export
# (art starts with 1 frame, only do this after first frame written)
# base exported frame delay on what you see in live app
delay = (1 / self.app.update_rate) * self.ticks_per_grow
if self.grows > 0:
self.exportable_art.add_frame_to_end(delay, log=False)
self.exportable_art.chars[-1] = self.art.chars[0].copy()
self.exportable_art.fg_colors[-1] = self.art.fg_colors[0].copy()
self.exportable_art.bg_colors[-1] = self.art.bg_colors[0].copy()
self.exportable_art.uv_mods[-1] = self.art.uv_mods[0].copy()
self.exportable_art.uv_maps[-1] = self.art.uv_maps[0].copy()