From 669204b42870bc038a9d5f52b1466aca1dd0ec7c Mon Sep 17 00:00:00 2001 From: Nicholas Arent Date: Thu, 20 Nov 2025 23:06:09 -0800 Subject: [PATCH 1/3] Add new app called City Lights --- apps/citylights/city_lights.star | 172 +++++++++++++++++++++++++++++++ apps/citylights/manifest.yaml | 6 ++ 2 files changed, 178 insertions(+) create mode 100644 apps/citylights/city_lights.star create mode 100644 apps/citylights/manifest.yaml diff --git a/apps/citylights/city_lights.star b/apps/citylights/city_lights.star new file mode 100644 index 000000000..39379f894 --- /dev/null +++ b/apps/citylights/city_lights.star @@ -0,0 +1,172 @@ +""" +Applet: City Lights +Summary: Nighttime cityscape +Description: City Lights generates a random and mesmorizing nighttime cityscape. +Author: Nicholas Arent +""" + +load("random.star", "random") +load("render.star", "render") +load("time.star", "time") + +# Constants +HEIGHT = 32 +WIDTH = 64 +BUILDINGS = 8 +BUILDING_MAX_W = 8 +BUILDING_MIN_W = 2 +BUILDING_MAX_H = 18 +BUILDING_MIN_H = 6 +WINDOW_COLOR_ON = "#E8E337" +WINDOW_COLOR_OFF = "#222222" +LIGHT_COLOR_ON = "#FF0000" +LIGHT_COLOR_OFF = "#420000ff" +STAR_COLOR = "#DDDDDD" +BACKGROUND_COLOR = "#000000" +BORDER_COLOR = "#000001" + +# 0-x, 1-y, 2-height, 3-width, 4-windows, 5-tallest +BUILDING_X, BUILDING_Y, BUILDING_H, BUILDING_W, BUILDING_WINDOWS, IS_TALLEST = range(6) + +# Animation Properties +DELAY = 200 # Delay between frames in milliseconds +DURATION_SECONDS = 15 # Total animation duration in seconds +NUM_FRAMES = DURATION_SECONDS * 1000 // DELAY # Number of frames in the animation + +tallestBuilding = 0 + +def gen_buildings(frame): + buildings = [[0, 0, 0, 0, 0, 0, 0] for _ in range(BUILDINGS)] + tallestBuildingHeight = 0 + tallestBuilding = 0 + + for b in range(BUILDINGS): + buildings[b][BUILDING_X] = b * 8 + buildings[b][BUILDING_Y] = 31 + buildings[b][BUILDING_H] = random.number(BUILDING_MIN_H, BUILDING_MAX_H) + buildings[b][BUILDING_W] = random.number(BUILDING_MIN_W, BUILDING_MAX_W) + buildings[b][BUILDING_WINDOWS] = buildings[b][BUILDING_W] * buildings[b][BUILDING_H] + buildings[b][IS_TALLEST] = False + + frame = set_windows_off(buildings[b], frame) + + if tallestBuildingHeight <= buildings[b][BUILDING_H]: + tallestBuilding = b + tallestBuildingHeight = buildings[b][BUILDING_H] + + frame = set_tallest_building(buildings[tallestBuilding], frame) + + return buildings, frame + +def set_windows_off(building, frame): + for y in range(building[BUILDING_H]): + for x in range(building[BUILDING_W]): + window_x = building[BUILDING_X] + x + window_y = building[BUILDING_Y] - y + frame[window_y][window_x] = WINDOW_COLOR_OFF + return frame + +def set_tallest_building(building, frame): + building[IS_TALLEST] = True + + if building[BUILDING_W] % 2 == 0: + building[BUILDING_W] = building[BUILDING_W] + 1 + for y in range(building[BUILDING_H]): + window_y = building[BUILDING_Y] - y + window_x = building[BUILDING_X] + building[BUILDING_W] - 1 + if window_x > 63: + window_x = 63 + frame[window_y][window_x] = WINDOW_COLOR_OFF + + light_x = building[BUILDING_X] + (building[BUILDING_W] // 2) + light_y = building[BUILDING_Y] - building[BUILDING_H] + frame[light_y][light_x] = LIGHT_COLOR_OFF + + return frame + +def stars(frame): + for _ in range(random.number(3, 5)): + star_x = random.number(0, 63) + star_y = random.number(0, 23) + + if not_star_collision(frame, star_y, star_x): + frame[star_y][star_x] = STAR_COLOR + return frame + +def not_star_collision(frame, star_y, star_x): + no_collision = not_color_collision(frame[star_y][star_x]) and not_color_collision(frame[star_y + 1][star_x]) + + if star_x - 1 >= 0: + no_collision = no_collision and not_color_collision(frame[star_y][star_x - 1]) + + if star_x + 1 < 64: + no_collision = no_collision and not_color_collision(frame[star_y][star_x + 1]) + + return no_collision + +def not_color_collision(f): + return (f != WINDOW_COLOR_ON) and (f != WINDOW_COLOR_OFF) and (f != LIGHT_COLOR_ON) and (f != LIGHT_COLOR_OFF) + +def update_view(frame, buildings, light_counter): + for b in range(BUILDINGS): + window = random.number(0, buildings[b][BUILDING_WINDOWS] - 1) + window_row = window // buildings[b][BUILDING_W] + window_col = window - (window_row * buildings[b][BUILDING_W]) + window_x = buildings[b][BUILDING_X] + window_col + window_y = buildings[b][BUILDING_Y] - window_row + if window_x > 63: + window_x = 63 + frame[window_y][window_x] = WINDOW_COLOR_ON + if (buildings[b][IS_TALLEST] == True) and (light_counter % 6 == 0): + frame = update_tallest(buildings[b], frame) + + return frame + +def update_tallest(building, frame): + light_x = building[BUILDING_X] + (building[BUILDING_W] // 2) + light_y = building[BUILDING_Y] - building[BUILDING_H] + if frame[light_y][light_x] == LIGHT_COLOR_ON: + frame[light_y][light_x] = LIGHT_COLOR_OFF + else: + frame[light_y][light_x] = LIGHT_COLOR_ON + + return frame + +def render_frame(frame): + children = [render_column(frame)] + return render.Stack(children = children) + +def render_column(frame): + rows = [] + for row in frame: + rows.append(render_row(row)) + return render.Column(children = rows) + +def render_row(row): + cells = [] + for cell in row: + cells.append(render_cell(cell)) + return render.Row(children = cells) + +def render_cell(cell): + return render.Box(width = 1, height = 1, color = cell) + +def main(): + random.seed(time.now().unix) + + frames = [] + frame = [[BACKGROUND_COLOR for _ in range(WIDTH)] for _ in range(HEIGHT)] + + buildings = [[0, 0, 0, 0, 0, 0, 0] for _ in range(BUILDINGS)] + buildings, frame = gen_buildings(frame) + + for counter in range(NUM_FRAMES): + frame = stars(frame) + frame = update_view(frame, buildings, counter) + frames.append(render_frame(frame)) + + return render.Root( + delay = DELAY, + show_full_animation = True, + child = render.Animation(children = frames), + ) diff --git a/apps/citylights/manifest.yaml b/apps/citylights/manifest.yaml new file mode 100644 index 000000000..d239fdbef --- /dev/null +++ b/apps/citylights/manifest.yaml @@ -0,0 +1,6 @@ +--- +id: city-lights +name: City Lights +summary: Nighttime cityscape +desc: City Lights generates a random and mesmorizing nighttime cityscape. +author: Nicholas Arent From 041b6c02b6cf113a1d90bcbdde0a11f332a48175 Mon Sep 17 00:00:00 2001 From: nickisnt Date: Thu, 20 Nov 2025 23:36:49 -0800 Subject: [PATCH 2/3] Update city_lights.star --- apps/citylights/city_lights.star | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/citylights/city_lights.star b/apps/citylights/city_lights.star index 39379f894..7b2bfbfeb 100644 --- a/apps/citylights/city_lights.star +++ b/apps/citylights/city_lights.star @@ -1,7 +1,7 @@ """ Applet: City Lights Summary: Nighttime cityscape -Description: City Lights generates a random and mesmorizing nighttime cityscape. +Description: City Lights generates a random and mesmerizing nighttime cityscapes. Author: Nicholas Arent """ From 3c4d337e78458e5c555b5f16745b594dbee51160 Mon Sep 17 00:00:00 2001 From: nickisnt Date: Thu, 20 Nov 2025 23:37:13 -0800 Subject: [PATCH 3/3] Update manifest.yaml --- apps/citylights/manifest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/citylights/manifest.yaml b/apps/citylights/manifest.yaml index d239fdbef..1ad878a7c 100644 --- a/apps/citylights/manifest.yaml +++ b/apps/citylights/manifest.yaml @@ -2,5 +2,5 @@ id: city-lights name: City Lights summary: Nighttime cityscape -desc: City Lights generates a random and mesmorizing nighttime cityscape. +desc: City Lights generates a random and mesmerizing nighttime cityscapes. author: Nicholas Arent