4
0

save/load functionality (WIP)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2025-12-11 20:00:43 +01:00
parent 6a39128962
commit 34340d9664
9 changed files with 644 additions and 461 deletions

View File

@@ -1,4 +1,4 @@
local Config = { local DEFAULT_CONFIG = {
screen = { screen = {
width = 240, width = 240,
height = 136 height = 136
@@ -30,3 +30,44 @@ local Config = {
splash_duration = 120 splash_duration = 120
} }
} }
local Config = {
-- Copy default values initially
screen = DEFAULT_CONFIG.screen,
colors = DEFAULT_CONFIG.colors,
player = DEFAULT_CONFIG.player,
physics = DEFAULT_CONFIG.physics,
timing = DEFAULT_CONFIG.timing,
}
local CONFIG_SAVE_BANK = 7
local CONFIG_SAVE_ADDRESS_MOVE_SPEED = 0
local CONFIG_SAVE_ADDRESS_MAX_JUMPS = 1
local CONFIG_MAGIC_VALUE_ADDRESS = 2
local CONFIG_MAGIC_VALUE = 0xDE -- A magic number to check if config is saved
function Config.save()
-- Save physics settings
mset(Config.physics.move_speed * 10, CONFIG_SAVE_ADDRESS_MOVE_SPEED, CONFIG_SAVE_BANK)
mset(Config.physics.max_jumps, CONFIG_SAVE_ADDRESS_MAX_JUMPS, CONFIG_SAVE_BANK)
mset(CONFIG_MAGIC_VALUE, CONFIG_MAGIC_VALUE_ADDRESS, CONFIG_SAVE_BANK) -- Mark as saved
end
function Config.load()
-- Check if config has been saved before using a magic value
if mget(CONFIG_MAGIC_VALUE_ADDRESS, CONFIG_SAVE_BANK) == CONFIG_MAGIC_VALUE then
Config.physics.move_speed = mget(CONFIG_SAVE_ADDRESS_MOVE_SPEED, CONFIG_SAVE_BANK) / 10
Config.physics.max_jumps = mget(CONFIG_SAVE_ADDRESS_MAX_JUMPS, CONFIG_SAVE_BANK)
else
Config.restore_defaults()
end
end
function Config.restore_defaults()
Config.physics.move_speed = DEFAULT_CONFIG.physics.move_speed
Config.physics.max_jumps = DEFAULT_CONFIG.physics.max_jumps
-- Any other configurable items should be reset here
end
-- Load configuration on startup
Config.load()

View File

@@ -1,4 +1,31 @@
local Context = { local SAVE_GAME_BANK = 6
local SAVE_GAME_MAGIC_VALUE_ADDRESS = 0
local SAVE_GAME_MAGIC_VALUE = 0xCA
local SAVE_GAME_PLAYER_X_ADDRESS = 1
local SAVE_GAME_PLAYER_Y_ADDRESS = 2
local SAVE_GAME_PLAYER_VX_ADDRESS = 3
local SAVE_GAME_PLAYER_VY_ADDRESS = 4
local SAVE_GAME_PLAYER_JUMPS_ADDRESS = 5
local SAVE_GAME_CURRENT_SCREEN_ADDRESS = 6
local VX_VY_OFFSET = 128 -- Offset for negative velocities
-- Helper for deep copying tables
local function clone_table(t)
local copy = {}
for k, v in pairs(t) do
if type(v) == "table" then
copy[k] = clone_table(v)
else
copy[k] = v
end
end
return copy
end
local function create_initial_context()
return {
active_window = WINDOW_SPLASH, active_window = WINDOW_SPLASH,
inventory = {}, inventory = {},
intro = { intro = {
@@ -35,8 +62,9 @@ local Context = {
menu_items = {}, menu_items = {},
selected_menu_item = 1, selected_menu_item = 1,
selected_inventory_item = 1, selected_inventory_item = 1,
-- Screen data game_in_progress = false, -- New flag
screens = { -- Screen data (deep copy to ensure new game resets correctly)
screens = clone_table({
{ {
-- Screen 1 -- Screen 1
name = "Screen 1", name = "Screen 1",
@@ -125,7 +153,7 @@ local Context = {
{label = "I guess I am.", next_node = "you_are"} {label = "I guess I am.", next_node = "you_are"}
} }
}, },
who_are_you = { who_are_are = {
text = "I'm the Oracle. And you're right on time. Want a cookie?", text = "I'm the Oracle. And you're right on time. Want a cookie?",
options = { options = {
{label = "Sure.", next_node = "cookie"}, {label = "Sure.", next_node = "cookie"},
@@ -428,5 +456,60 @@ local Context = {
}, },
items = {} items = {}
} }
})
} }
} end
Context = create_initial_context()
local function reset_context_to_initial_state()
local initial_state = create_initial_context()
-- Clear existing keys from Context
for k in pairs(Context) do
Context[k] = nil
end
-- Copy all properties from initial_state to Context
for k, v in pairs(initial_state) do
Context[k] = v
end
end
function Context.new_game()
reset_context_to_initial_state()
Context.game_in_progress = true
MenuWindow.refresh_menu_items()
end
function Context.save_game()
if not Context.game_in_progress then return end
mset(SAVE_GAME_MAGIC_VALUE, SAVE_GAME_MAGIC_VALUE_ADDRESS, SAVE_GAME_BANK)
mset(Context.player.x * 10, SAVE_GAME_PLAYER_X_ADDRESS, SAVE_GAME_BANK)
mset(Context.player.y * 10, SAVE_GAME_PLAYER_Y_ADDRESS, SAVE_GAME_BANK)
mset( (Context.player.vx * 100) + VX_VY_OFFSET, SAVE_GAME_PLAYER_VX_ADDRESS, SAVE_GAME_BANK)
mset( (Context.player.vy * 100) + VX_VY_OFFSET, SAVE_GAME_PLAYER_VY_ADDRESS, SAVE_GAME_BANK)
mset(Context.player.jumps, SAVE_GAME_PLAYER_JUMPS_ADDRESS, SAVE_GAME_BANK)
mset(Context.current_screen, SAVE_GAME_CURRENT_SCREEN_ADDRESS, SAVE_GAME_BANK)
end
function Context.load_game()
if mget(SAVE_GAME_MAGIC_VALUE_ADDRESS, SAVE_GAME_BANK) ~= SAVE_GAME_MAGIC_VALUE then
-- No saved game found, start a new one
Context.new_game()
return
end
reset_context_to_initial_state()
Context.player.x = mget(SAVE_GAME_PLAYER_X_ADDRESS, SAVE_GAME_BANK) / 10
Context.player.y = mget(SAVE_GAME_PLAYER_Y_ADDRESS, SAVE_GAME_BANK) / 10
Context.player.vx = (mget(SAVE_GAME_PLAYER_VX_ADDRESS, SAVE_GAME_BANK) - VX_VY_OFFSET) / 100
Context.player.vy = (mget(SAVE_GAME_PLAYER_VY_ADDRESS, SAVE_GAME_BANK) - VX_VY_OFFSET) / 100
Context.player.jumps = mget(SAVE_GAME_PLAYER_JUMPS_ADDRESS, SAVE_GAME_BANK)
Context.current_screen = mget(SAVE_GAME_CURRENT_SCREEN_ADDRESS, SAVE_GAME_BANK)
Context.game_in_progress = true
MenuWindow.refresh_menu_items()
end

View File

@@ -3,5 +3,5 @@
-- desc: Life of a programmer in the Vector -- desc: Life of a programmer in the Vector
-- site: https://github.com/rastasi/mranderson -- site: https://github.com/rastasi/mranderson
-- license: MIT License -- license: MIT License
-- version: 0.11 -- version: 0.12
-- script: lua -- script: lua

View File

@@ -1,8 +1,8 @@
function Input.up() return btnp(0) end function Input.up() return btnp(0) end
function Input.down() return btnp(1) end function Input.down() return btnp(1) end
function Input.left() return btnp(2) end function Input.left() return btn(2) end
function Input.right() return btnp(3) end function Input.right() return btn(3) end
function Input.player_jump() return btnp(4) end function Input.player_jump() return btnp(4) end
function Input.menu_confirm() return btnp(4) end function Input.menu_confirm() return btnp(4) end
function Input.player_interact() return btnp(5) end -- B button function Input.player_interact() return btnp(5) end -- B button
function Input.menu_back() return btnp(5) end function Input.menu_back() return btnp(7) end

View File

@@ -35,7 +35,18 @@ local STATE_HANDLERS = {
end, end,
} }
local initialized_game = false
function init_game()
if initialized_game then return end
MenuWindow.refresh_menu_items()
initialized_game = true
end
function TIC() function TIC()
init_game()
cls(Config.colors.black) cls(Config.colors.black)
local handler = STATE_HANDLERS[Context.active_window] local handler = STATE_HANDLERS[Context.active_window]
if handler then if handler then

View File

@@ -77,3 +77,11 @@ function UI.create_numeric_stepper(label, value_getter, value_setter, min, max,
type = "numeric_stepper" type = "numeric_stepper"
} }
end end
function UI.create_action_item(label, action)
return {
label = label,
action = action,
type = "action_item"
}
end

View File

@@ -17,6 +17,14 @@ function ConfigurationWindow.init()
function(v) Config.physics.max_jumps = v end, function(v) Config.physics.max_jumps = v end,
1, 5, 1, "%d" 1, 5, 1, "%d"
), ),
UI.create_action_item(
"Save",
function() Config.save() end
),
UI.create_action_item(
"Restore Defaults",
function() Config.restore_defaults() end
),
} }
end end
@@ -32,6 +40,7 @@ function ConfigurationWindow.draw()
local current_y = y_start + (i - 1) * 12 local current_y = y_start + (i - 1) * 12
local color = Config.colors.green local color = Config.colors.green
if control.type == "numeric_stepper" then
local value = control.get() local value = control.get()
local label_text = control.label local label_text = control.label
local value_text = string.format(control.format, value) local value_text = string.format(control.format, value)
@@ -49,6 +58,17 @@ function ConfigurationWindow.draw()
print(label_text, x_start, current_y, color) print(label_text, x_start, current_y, color)
print(value_text, value_x, current_y, color) print(value_text, value_x, current_y, color)
end end
elseif control.type == "action_item" then
local label_text = control.label
if i == ConfigurationWindow.selected_control then
color = Config.colors.item
print("<", x_start -8, current_y, color)
print(label_text, x_start, current_y, color)
print(">", x_start + 8 + (#label_text * char_width) + 4, current_y, color)
else
print(label_text, x_start, current_y, color)
end
end
end end
print("Press B to go back", x_start, 120, Config.colors.light_grey) print("Press B to go back", x_start, 120, Config.colors.light_grey)
@@ -56,13 +76,10 @@ end
function ConfigurationWindow.update() function ConfigurationWindow.update()
if Input.menu_back() then if Input.menu_back() then
-- I need to find out how to switch back to the menu
-- For now, I'll assume a function GameWindow.set_state exists
GameWindow.set_state(WINDOW_MENU) GameWindow.set_state(WINDOW_MENU)
return return
end end
-- Navigate between controls
if Input.up() then if Input.up() then
ConfigurationWindow.selected_control = ConfigurationWindow.selected_control - 1 ConfigurationWindow.selected_control = ConfigurationWindow.selected_control - 1
if ConfigurationWindow.selected_control < 1 then if ConfigurationWindow.selected_control < 1 then
@@ -75,16 +92,21 @@ function ConfigurationWindow.update()
end end
end end
-- Modify control value
local control = ConfigurationWindow.controls[ConfigurationWindow.selected_control] local control = ConfigurationWindow.controls[ConfigurationWindow.selected_control]
if control then if control then
if control.type == "numeric_stepper" then
local current_value = control.get() local current_value = control.get()
if Input.left() then if btnp(2) then -- Left
local new_value = math.max(control.min, current_value - control.step) local new_value = math.max(control.min, current_value - control.step)
control.set(new_value) control.set(new_value)
elseif Input.right() then elseif btnp(3) then -- Right
local new_value = math.min(control.max, current_value + control.step) local new_value = math.min(control.max, current_value + control.step)
control.set(new_value) control.set(new_value)
end end
elseif control.type == "action_item" then
if Input.menu_confirm() then
control.action()
end
end
end end
end end

View File

@@ -26,6 +26,11 @@ function GameWindow.draw()
end end
function GameWindow.update() function GameWindow.update()
if Input.menu_back() then
Context.active_window = WINDOW_MENU
MenuWindow.refresh_menu_items()
return
end
Player.update() -- Call the encapsulated player update logic Player.update() -- Call the encapsulated player update logic
end end

View File

@@ -14,17 +14,20 @@ function MenuWindow.update()
end end
end end
function MenuWindow.play() function MenuWindow.new_game()
-- Reset player state and screen for a new game Context.new_game() -- This function will be created in Context
Context.player.x = Config.player.start_x
Context.player.y = Config.player.start_y
Context.player.vx = 0
Context.player.vy = 0
Context.player.jumps = 0
Context.current_screen = 1
GameWindow.set_state(WINDOW_GAME) GameWindow.set_state(WINDOW_GAME)
end end
function MenuWindow.load_game()
Context.load_game() -- This function will be created in Context
GameWindow.set_state(WINDOW_GAME)
end
function MenuWindow.save_game()
Context.save_game() -- This function will be created in Context
end
function MenuWindow.exit() function MenuWindow.exit()
exit() exit()
end end
@@ -34,9 +37,19 @@ function MenuWindow.configuration()
GameWindow.set_state(WINDOW_CONFIGURATION) GameWindow.set_state(WINDOW_CONFIGURATION)
end end
-- Initialize menu items after actions are defined function MenuWindow.refresh_menu_items()
Context.menu_items = { Context.menu_items = {
{label = "Play", action = MenuWindow.play}, {label = "New Game", action = MenuWindow.new_game},
{label = "Configuration", action = MenuWindow.configuration}, {label = "Load Game", action = MenuWindow.load_game},
{label = "Exit", action = MenuWindow.exit}
} }
if Context.game_in_progress then
table.insert(Context.menu_items, {label = "Save Game", action = MenuWindow.save_game})
end
table.insert(Context.menu_items, {label = "Configuration", action = MenuWindow.configuration})
table.insert(Context.menu_items, {label = "Exit", action = MenuWindow.exit})
Context.selected_menu_item = 1 -- Reset selection after refreshing
end