save/load functionality (WIP)

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 = {
width = 240,
height = 136
@@ -30,3 +30,44 @@ local Config = {
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,
inventory = {},
intro = {
@@ -35,8 +62,9 @@ local Context = {
menu_items = {},
selected_menu_item = 1,
selected_inventory_item = 1,
-- Screen data
screens = {
game_in_progress = false, -- New flag
-- Screen data (deep copy to ensure new game resets correctly)
screens = clone_table({
{
-- Screen 1
name = "Screen 1",
@@ -125,7 +153,7 @@ local Context = {
{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?",
options = {
{label = "Sure.", next_node = "cookie"},
@@ -428,5 +456,60 @@ local Context = {
},
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
-- site: https://github.com/rastasi/mranderson
-- license: MIT License
-- version: 0.11
-- version: 0.12
-- script: lua

View File

@@ -1,8 +1,8 @@
function Input.up() return btnp(0) end
function Input.down() return btnp(1) end
function Input.left() return btnp(2) end
function Input.right() return btnp(3) end
function Input.left() return btn(2) end
function Input.right() return btn(3) end
function Input.player_jump() return btnp(4) end
function Input.menu_confirm() return btnp(4) end
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,
}
local initialized_game = false
function init_game()
if initialized_game then return end
MenuWindow.refresh_menu_items()
initialized_game = true
end
function TIC()
init_game()
cls(Config.colors.black)
local handler = STATE_HANDLERS[Context.active_window]
if handler then

View File

@@ -77,3 +77,11 @@ function UI.create_numeric_stepper(label, value_getter, value_setter, min, max,
type = "numeric_stepper"
}
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,
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
@@ -32,6 +40,7 @@ function ConfigurationWindow.draw()
local current_y = y_start + (i - 1) * 12
local color = Config.colors.green
if control.type == "numeric_stepper" then
local value = control.get()
local label_text = control.label
local value_text = string.format(control.format, value)
@@ -49,6 +58,17 @@ function ConfigurationWindow.draw()
print(label_text, x_start, current_y, color)
print(value_text, value_x, current_y, color)
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
print("Press B to go back", x_start, 120, Config.colors.light_grey)
@@ -56,13 +76,10 @@ end
function ConfigurationWindow.update()
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)
return
end
-- Navigate between controls
if Input.up() then
ConfigurationWindow.selected_control = ConfigurationWindow.selected_control - 1
if ConfigurationWindow.selected_control < 1 then
@@ -75,16 +92,21 @@ function ConfigurationWindow.update()
end
end
-- Modify control value
local control = ConfigurationWindow.controls[ConfigurationWindow.selected_control]
if control then
if control.type == "numeric_stepper" then
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)
control.set(new_value)
elseif Input.right() then
elseif btnp(3) then -- Right
local new_value = math.min(control.max, current_value + control.step)
control.set(new_value)
end
elseif control.type == "action_item" then
if Input.menu_confirm() then
control.action()
end
end
end
end

View File

@@ -26,6 +26,11 @@ function GameWindow.draw()
end
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
end

View File

@@ -14,17 +14,20 @@ function MenuWindow.update()
end
end
function MenuWindow.play()
-- Reset player state and screen for a new game
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
function MenuWindow.new_game()
Context.new_game() -- This function will be created in Context
GameWindow.set_state(WINDOW_GAME)
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()
exit()
end
@@ -34,9 +37,19 @@ function MenuWindow.configuration()
GameWindow.set_state(WINDOW_CONFIGURATION)
end
-- Initialize menu items after actions are defined
Context.menu_items = {
{label = "Play", action = MenuWindow.play},
{label = "Configuration", action = MenuWindow.configuration},
{label = "Exit", action = MenuWindow.exit}
}
function MenuWindow.refresh_menu_items()
Context.menu_items = {
{label = "New Game", action = MenuWindow.new_game},
{label = "Load Game", action = MenuWindow.load_game},
}
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