Created
July 8, 2024 15:13
-
-
Save wjkennedy/3c193a0d3268d5e14701749bf158975e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- PICO-8 Game: Jira Sprint Manager | |
-- Constants | |
local SPRINT_CAPACITY = 20 | |
local MAX_TASKS = 15 | |
local BUCKET_SPEED = 2 | |
local FALL_SPEED = 1 | |
local BUG_PAUSE_TIME = 60 -- Time to pause when a bug is found | |
local GAME_OVER_TIME = 120 -- Time to show "Game Over" screen (2 seconds at 30 fps) | |
local DROP_INTERVAL = 30 -- Frames between drops | |
-- Colors | |
local JIRA_BLUE = 12 | |
local WHITE = 7 | |
local RED = 8 | |
local HIGH_PRIORITY = 9 -- Yellow | |
local MEDIUM_PRIORITY = 10 -- Orange | |
local LOW_PRIORITY = 11 -- Green | |
-- Game State | |
backlog = {} | |
falling_tasks = {} | |
bucket = {x = 64, y = 120, width = 16, height = 8, sprite_index = 0} | |
burndown = 0 | |
capacity = SPRINT_CAPACITY | |
game_over = false | |
sprint_number = 1 | |
bug_found = false | |
bug_timer = 0 | |
game_over_timer = 0 | |
drop_timer = 0 | |
wip_admonished = false | |
-- Splash Screen State | |
show_splash = true | |
splash_timer = 0 | |
-- Task Generation | |
function generate_task() | |
local priority = flr(rnd(3)) + 1 -- Random priority between 1 and 3 | |
local priority_color | |
if priority == 1 then | |
priority_color = LOW_PRIORITY | |
elseif priority == 2 then | |
priority_color = MEDIUM_PRIORITY | |
else | |
priority_color = HIGH_PRIORITY | |
end | |
local task = { | |
title = "Task "..tostr(#backlog+1), | |
story_points = flr(rnd(8)) + 1, -- Random story points between 1 and 8 | |
bug_probability = 0.5, -- 50% bug probability | |
x = rnd(120), -- Random x position | |
y = -8, -- Start above the screen | |
priority = priority, | |
priority_color = priority_color | |
} | |
return task | |
end | |
-- Initialize Backlog | |
function init_backlog() | |
for i=1,MAX_TASKS do | |
add(backlog, generate_task()) | |
end | |
end | |
-- Add falling task | |
function add_falling_task() | |
if #backlog > 0 then | |
local task = deli(backlog, 1) | |
add(falling_tasks, task) | |
end | |
end | |
-- Move bucket | |
function move_bucket() | |
if btn(0) then | |
bucket.x -= BUCKET_SPEED | |
end | |
if btn(1) then | |
bucket.x += BUCKET_SPEED | |
end | |
-- Keep bucket within screen bounds | |
bucket.x = mid(0, bucket.x, 128 - bucket.width) | |
end | |
-- Find index of a task in a list | |
function indexof(list, item) | |
for i=1,#list do | |
if list[i] == item then | |
return i | |
end | |
end | |
return nil | |
end | |
-- Update falling tasks | |
function update_falling_tasks() | |
drop_timer += 1 | |
if drop_timer > DROP_INTERVAL then | |
add_falling_task() | |
drop_timer = 0 | |
end | |
for task in all(falling_tasks) do | |
task.y += FALL_SPEED | |
-- Check if task is caught by the bucket | |
if task.y >= bucket.y and task.y <= bucket.y + bucket.height and task.x >= bucket.x and task.x <= bucket.x + bucket.width then | |
-- Calculate actual story points (considering bugs) | |
local actual_points = task.story_points | |
if rnd(1) < task.bug_probability then | |
actual_points *= 2 -- Double points if it has bugs | |
bug_found = true | |
bug_timer = BUG_PAUSE_TIME | |
sfx(2) -- Buzzer sound | |
else | |
sfx(0) -- Happy fanfare for catching a card | |
end | |
burndown += actual_points | |
capacity -= task.story_points | |
deli(falling_tasks, indexof(falling_tasks, task)) | |
elseif task.y > 128 then | |
-- Remove task if it falls off the screen | |
deli(falling_tasks, indexof(falling_tasks, task)) | |
end | |
end | |
end | |
-- Draw Splash Screen | |
function draw_splash() | |
cls() | |
local base_y = 64 | |
local t = splash_timer / 30 | |
local text = "SCRUMASTER" | |
local text_length = #text * 8 | |
local start_x = (128 - text_length) / 2 | |
for i = 1, #text do | |
local char = sub(text, i, i) | |
local y = base_y + 10 * sin(t + i / 6.5) | |
print(char, start_x + (i - 1) * 8, y, JIRA_BLUE) | |
end | |
print("Press X to Start", 30, 90, WHITE) | |
end | |
-- Draw UI | |
function draw_ui() | |
cls() | |
-- Draw backlog | |
print("Backlog", 2, 2, JIRA_BLUE) | |
for i=1,#backlog do | |
local task = backlog[i] | |
print(task.title.." ("..task.story_points.." SP)", 2, 10+i*8, JIRA_BLUE) | |
end | |
-- Draw burndown and capacity | |
print("Burndown: "..burndown, 2, 100, WHITE) | |
print("Capacity: "..capacity, 2, 110, WHITE) | |
print("Sprint: "..sprint_number, 2, 120, WHITE) | |
-- Draw bucket | |
spr(bucket.sprite_index, bucket.x, bucket.y) | |
-- Draw falling tasks | |
for task in all(falling_tasks) do | |
rectfill(task.x, task.y, task.x + 8, task.y + 8, JIRA_BLUE) | |
print(task.story_points.." SP", task.x, task.y + 2, WHITE) | |
pset(task.x + 4, task.y + 4, task.priority_color) -- Draw priority indicator | |
end | |
-- Draw bug message if a bug is found | |
if bug_found then | |
print("Bug Found!", 50, 30, RED) | |
end | |
end | |
-- Draw Game Over Message | |
function draw_game_over() | |
print("Game Over!", 50, 64, WHITE) | |
if wip_admonished then | |
print("Limit your WIP!", 40, 80, RED) | |
end | |
end | |
-- Main Update Loop | |
function _update() | |
if show_splash then | |
splash_timer += 1 | |
if splash_timer == 1 then | |
music(0) -- Start the intro music | |
end | |
if btnp(5) then -- 'X' to start the game | |
music(-1) -- Stop the intro music | |
show_splash = false | |
init_backlog() | |
end | |
else | |
if game_over then | |
game_over_timer += 1 | |
if game_over_timer > GAME_OVER_TIME then | |
show_splash = true | |
game_over = false | |
game_over_timer = 0 | |
wip_admonished = false | |
sprint_number = 1 | |
burndown = 0 | |
capacity = SPRINT_CAPACITY | |
backlog = {} | |
falling_tasks = {} | |
end | |
else | |
if bug_found then | |
bug_timer -= 1 | |
if bug_timer <= 0 then | |
bug_found = false | |
end | |
else | |
move_bucket() | |
update_falling_tasks() | |
if #falling_tasks < 3 and #backlog > 0 then | |
add_falling_task() | |
end | |
if capacity <= 0 then | |
game_over = true | |
elseif #backlog == 0 and #falling_tasks == 0 then | |
if sprint_number == 1 and burndown < SPRINT_CAPACITY then | |
wip_admonished = true | |
end | |
sprint_number += 1 | |
capacity = SPRINT_CAPACITY | |
for task in all(falling_tasks) do | |
add(backlog, task) | |
end | |
falling_tasks = {} | |
init_backlog() | |
burndown = 0 | |
sfx(1) -- Happy fanfare for completing a sprint | |
end | |
end | |
end | |
end | |
end | |
-- Main Draw Loop | |
function _draw() | |
if show_splash then | |
draw_splash() | |
else | |
draw_ui() | |
if game_over then | |
draw_game_over() | |
end | |
end | |
end | |
-- Sound Effects | |
-- SFX 0: Happy fanfare for catching a card | |
-- SFX 1: Happy fanfare for completing a sprint | |
-- SFX 2: Buzzer sound for finding a bug | |
-- Music | |
-- Music 0: Intro music | |
-- Initialize Game | |
init_backlog() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment