2018-06-15 15:37:19 -07:00
|
|
|
preset = arg or "" -- must be done before requiring strict!
|
2018-04-02 06:28:00 -07:00
|
|
|
local globalize = require("strict")
|
2017-06-28 02:33:18 -07:00
|
|
|
|
2017-09-07 11:41:44 -07:00
|
|
|
-- configuration.
|
2017-06-28 21:51:02 -07:00
|
|
|
|
2018-04-02 06:21:55 -07:00
|
|
|
local cfg = require("config")
|
|
|
|
local gcfg = require("gameconfig")
|
2017-09-08 03:43:32 -07:00
|
|
|
|
2017-09-07 11:41:44 -07:00
|
|
|
-- state.
|
|
|
|
|
2018-06-11 20:36:24 -07:00
|
|
|
local params_fn
|
|
|
|
local std_fn
|
|
|
|
|
2017-06-28 21:51:02 -07:00
|
|
|
local epoch_i = 0
|
|
|
|
local base_params
|
2017-09-07 12:00:09 -07:00
|
|
|
local trial_i = -1 -- NOTE: trial 0 is an unperturbed trial, if enabled.
|
2018-03-26 07:32:00 -07:00
|
|
|
local trial_neg = true
|
2018-06-09 08:56:18 -07:00
|
|
|
local trial_params --= {}
|
2017-06-28 21:51:02 -07:00
|
|
|
local trial_rewards = {}
|
|
|
|
local trials_remaining = 0
|
2018-06-09 08:56:18 -07:00
|
|
|
local es -- evolution strategy.
|
2017-06-28 21:51:02 -07:00
|
|
|
|
2017-07-05 20:26:27 -07:00
|
|
|
local trial_frames = 0
|
|
|
|
local total_frames = 0
|
2018-06-08 04:48:59 -07:00
|
|
|
local lagless_count = 0
|
2018-06-10 07:48:02 -07:00
|
|
|
local decisions_made = 0
|
2017-07-05 20:26:27 -07:00
|
|
|
|
2017-06-28 21:51:02 -07:00
|
|
|
local force_start = false
|
|
|
|
local force_start_old = false
|
|
|
|
|
|
|
|
local startsave = savestate.create(1)
|
2018-06-08 19:34:21 -07:00
|
|
|
local any_random = cfg.starting_world == 0 or cfg.starting_level == 0
|
2017-06-28 21:51:02 -07:00
|
|
|
|
|
|
|
local poketime = false
|
2017-06-29 02:50:33 -07:00
|
|
|
local max_time
|
2017-06-28 21:51:02 -07:00
|
|
|
|
2017-09-07 14:20:53 -07:00
|
|
|
local jp
|
|
|
|
|
|
|
|
local screen_scroll_delta
|
2017-06-28 21:51:02 -07:00
|
|
|
local reward
|
2017-06-29 02:50:33 -07:00
|
|
|
|
2017-06-28 21:51:02 -07:00
|
|
|
local powerup_old
|
|
|
|
local status_old
|
|
|
|
local coins_old
|
2017-07-05 20:26:27 -07:00
|
|
|
local score_old
|
2017-06-28 21:51:02 -07:00
|
|
|
|
2018-06-08 19:34:21 -07:00
|
|
|
local state_saved = false
|
2017-06-28 21:51:02 -07:00
|
|
|
local reset = true
|
|
|
|
|
|
|
|
local state_old = ''
|
2018-03-27 04:04:44 -07:00
|
|
|
local last_trial_state
|
2017-06-28 21:51:02 -07:00
|
|
|
|
2017-06-28 02:33:18 -07:00
|
|
|
-- localize some stuff.
|
|
|
|
|
|
|
|
local abs = math.abs
|
2018-06-09 09:27:13 -07:00
|
|
|
local assert = assert
|
2017-06-28 02:33:18 -07:00
|
|
|
local ceil = math.ceil
|
2018-06-09 09:27:13 -07:00
|
|
|
local collectgarbage = collectgarbage
|
2017-06-28 02:33:18 -07:00
|
|
|
local exp = math.exp
|
2018-06-09 09:27:13 -07:00
|
|
|
local floor = math.floor
|
|
|
|
local insert = table.insert
|
|
|
|
local ipairs = ipairs
|
2017-06-29 02:50:33 -07:00
|
|
|
local log = math.log
|
2018-06-09 09:27:13 -07:00
|
|
|
local max = math.max
|
|
|
|
local min = math.min
|
|
|
|
local open = io.open
|
|
|
|
local pairs = pairs
|
|
|
|
local pow = math.pow
|
|
|
|
local print = print
|
2017-06-28 02:33:18 -07:00
|
|
|
local random = math.random
|
2017-06-29 02:50:33 -07:00
|
|
|
local randomseed = math.randomseed
|
2017-06-28 02:33:18 -07:00
|
|
|
local remove = table.remove
|
2018-06-09 09:27:13 -07:00
|
|
|
local select = select
|
2017-09-07 11:53:37 -07:00
|
|
|
local sort = table.sort
|
2018-06-09 09:27:13 -07:00
|
|
|
local sqrt = math.sqrt
|
|
|
|
local unpack = table.unpack or unpack
|
2017-06-28 02:33:18 -07:00
|
|
|
|
|
|
|
local band = bit.band
|
|
|
|
local bor = bit.bor
|
|
|
|
local bxor = bit.bxor
|
|
|
|
local bnot = bit.bnot
|
|
|
|
local lshift = bit.lshift
|
|
|
|
local rshift = bit.rshift
|
|
|
|
local arshift = bit.arshift
|
|
|
|
local rol = bit.rol
|
|
|
|
local ror = bit.ror
|
|
|
|
|
2018-05-13 16:34:08 -07:00
|
|
|
local emu = emu
|
2018-04-02 07:29:12 -07:00
|
|
|
local gui = gui
|
|
|
|
|
2018-05-12 13:38:51 -07:00
|
|
|
local util = require("util")
|
2018-06-13 13:51:12 -07:00
|
|
|
local argmax = util.argmax
|
|
|
|
local argsort = util.argsort
|
|
|
|
local calc_mean_dev = util.calc_mean_dev
|
|
|
|
local clamp = util.clamp
|
|
|
|
local copy = util.copy
|
|
|
|
local empty = util.empty
|
|
|
|
local lerp = util.lerp
|
|
|
|
local softchoice = util.softchoice
|
2018-05-12 13:38:51 -07:00
|
|
|
local unperturbed_rank = util.unperturbed_rank
|
2018-06-13 13:51:12 -07:00
|
|
|
local exists = util.exists
|
2018-05-12 13:38:51 -07:00
|
|
|
|
2018-05-12 13:55:04 -07:00
|
|
|
local game = require("smb")
|
|
|
|
game.overlay = cfg.enable_overlay
|
|
|
|
|
2017-06-28 21:51:02 -07:00
|
|
|
-- utilities.
|
|
|
|
|
2018-05-03 06:33:17 -07:00
|
|
|
local log_map = {
|
|
|
|
epoch = 1,
|
|
|
|
trial_mean = 2,
|
|
|
|
trial_std = 3,
|
|
|
|
delta_mean = 4,
|
|
|
|
delta_std = 5,
|
|
|
|
step_std = 6,
|
2018-06-12 16:36:40 -07:00
|
|
|
weight_mean = 7,
|
|
|
|
weight_std = 8,
|
|
|
|
test_trial = 9,
|
2018-06-12 18:00:05 -07:00
|
|
|
decisions = 10,
|
2018-05-03 06:33:17 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
local function log_csv(t)
|
|
|
|
if cfg.log_fn == nil then return end
|
|
|
|
local f = open(cfg.log_fn, 'a')
|
|
|
|
if f == nil then error("Failed to open log file "..cfg.log_fn) end
|
|
|
|
local values = {}
|
|
|
|
for k, v in pairs(t) do
|
|
|
|
local i = log_map[k]
|
|
|
|
if i == nil then error("Unexpected log key "..tostring(k)) end
|
|
|
|
values[i] = v
|
|
|
|
end
|
|
|
|
for k, i in pairs(log_map) do
|
|
|
|
if values[i] == nil then error("Missing log key "..tostring(k)) end
|
|
|
|
end
|
|
|
|
for i, v in ipairs(values) do
|
|
|
|
f:write(tostring(v))
|
|
|
|
if i ~= #values then f:write(",") end
|
|
|
|
end
|
|
|
|
f:write('\n')
|
|
|
|
f:close()
|
|
|
|
end
|
|
|
|
|
2017-09-09 12:46:35 -07:00
|
|
|
-- network parameters.
|
2017-06-28 02:33:18 -07:00
|
|
|
|
|
|
|
package.loaded['nn'] = nil -- DEBUG
|
|
|
|
local nn = require("nn")
|
|
|
|
|
|
|
|
local network
|
2018-06-09 07:20:20 -07:00
|
|
|
local nn_x, nn_tx, nn_ty, nn_tz, nn_y, nn_z
|
2017-09-09 12:37:01 -07:00
|
|
|
local function make_network(input_size)
|
2017-09-07 12:15:41 -07:00
|
|
|
nn_x = nn.Input({input_size})
|
2018-04-02 06:21:55 -07:00
|
|
|
nn_tx = nn.Input({gcfg.tile_count})
|
2018-06-07 13:40:31 -07:00
|
|
|
nn_ty = nn_tx:feed(nn.Embed(#game.valid_tiles, 2))
|
2018-06-10 07:41:45 -07:00
|
|
|
|
|
|
|
nn_tz = nn_ty
|
2018-06-12 18:00:42 -07:00
|
|
|
if cfg.reduce_tiles then
|
2018-06-13 11:18:10 -07:00
|
|
|
nn_tz = nn_tz:feed(nn.Reshape{11, 17 * 2})
|
2018-06-12 18:00:42 -07:00
|
|
|
nn_tz = nn_tz:feed(nn.DenseBroadcast(5))
|
|
|
|
nn_tz = nn_tz:feed(nn.Relu())
|
|
|
|
-- note: due to a quirk in Merge, we don't need to flatten nn_tz.
|
|
|
|
end
|
2018-06-10 07:41:45 -07:00
|
|
|
|
2017-09-07 16:06:30 -07:00
|
|
|
nn_y = nn.Merge()
|
|
|
|
nn_x:feed(nn_y)
|
2018-06-09 07:20:20 -07:00
|
|
|
nn_tz:feed(nn_y)
|
2017-09-07 16:06:30 -07:00
|
|
|
|
2018-06-09 07:20:07 -07:00
|
|
|
--[[
|
2018-03-31 09:40:35 -07:00
|
|
|
nn_y = nn_y:feed(nn.Dense(128))
|
2018-04-02 06:21:55 -07:00
|
|
|
if cfg.deterministic then
|
|
|
|
nn_y = nn_y:feed(nn.Relu())
|
|
|
|
else
|
|
|
|
nn_y = nn_y:feed(nn.Gelu())
|
|
|
|
end
|
2018-05-07 07:22:48 -07:00
|
|
|
if cfg.layernorm then nn_y = nn_y:feed(nn.LayerNorm()) end
|
2018-06-09 07:20:07 -07:00
|
|
|
--]]
|
2018-03-31 09:40:35 -07:00
|
|
|
|
2017-09-08 03:43:32 -07:00
|
|
|
nn_z = nn_y
|
2018-06-12 18:00:42 -07:00
|
|
|
nn_z = nn_z:feed(nn.Dense(#gcfg.jp_lut), true, cfg.bias_out)
|
2017-09-08 03:43:32 -07:00
|
|
|
nn_z = nn_z:feed(nn.Softmax())
|
|
|
|
return nn.Model({nn_x, nn_tx}, {nn_z})
|
2017-06-28 02:33:18 -07:00
|
|
|
end
|
|
|
|
|
2017-09-09 12:46:35 -07:00
|
|
|
-- learning and evaluation.
|
|
|
|
|
2018-06-09 08:56:18 -07:00
|
|
|
local ars = require("ars")
|
2018-06-10 07:40:20 -07:00
|
|
|
local snes = require("snes")
|
2018-06-09 09:27:13 -07:00
|
|
|
local xnes = require("xnes")
|
2018-06-09 08:56:18 -07:00
|
|
|
|
2017-09-07 11:41:44 -07:00
|
|
|
local function prepare_epoch()
|
2018-06-09 08:56:18 -07:00
|
|
|
trial_neg = false
|
|
|
|
|
2017-09-07 11:41:44 -07:00
|
|
|
base_params = network:collect()
|
2018-04-03 09:13:11 -07:00
|
|
|
if cfg.playback_mode then return end
|
|
|
|
|
2018-06-09 09:27:13 -07:00
|
|
|
print('preparing epoch '..tostring(epoch_i)..'...')
|
2017-09-07 11:41:44 -07:00
|
|
|
empty(trial_rewards)
|
2018-04-02 07:29:12 -07:00
|
|
|
|
2018-06-10 10:34:17 -07:00
|
|
|
if cfg.es == 'xnes' then
|
|
|
|
print("sigma:", es.sigma)
|
|
|
|
elseif cfg.es == 'snes' then
|
|
|
|
local sigma_mean, sigma_dev = calc_mean_dev(es.std)
|
2018-06-11 20:39:22 -07:00
|
|
|
--print("sigma:", sigma_mean, sigma_dev)
|
2018-06-12 16:19:32 -07:00
|
|
|
print("sigma 50%:", sigma_mean)
|
2018-06-11 20:39:22 -07:00
|
|
|
print("sigma 95%:", sigma_mean + sigma_dev * 1.64485)
|
2018-06-10 10:34:17 -07:00
|
|
|
end
|
|
|
|
|
2018-06-09 08:56:18 -07:00
|
|
|
local precision
|
2018-05-02 04:06:28 -07:00
|
|
|
if cfg.graycode then
|
2018-06-09 08:56:18 -07:00
|
|
|
precision = (pow(cfg.deviation, 1/-0.51175585) - 8.68297257) / 1.66484392
|
2018-05-02 04:06:28 -07:00
|
|
|
print(("chosen precision: %.2f"):format(precision))
|
|
|
|
end
|
2018-04-02 07:29:12 -07:00
|
|
|
|
2018-06-09 08:56:18 -07:00
|
|
|
local dummy
|
2018-06-09 09:27:13 -07:00
|
|
|
if cfg.es == 'ars' then
|
2018-06-09 08:56:18 -07:00
|
|
|
trial_params, dummy = es:ask(precision)
|
2018-06-12 16:19:32 -07:00
|
|
|
elseif cfg.es == 'snes' then
|
|
|
|
trial_params, dummy = es:ask_mix()
|
2018-06-09 08:56:18 -07:00
|
|
|
else
|
|
|
|
trial_params, dummy = es:ask()
|
2018-03-26 07:32:00 -07:00
|
|
|
end
|
|
|
|
|
2018-06-09 08:56:18 -07:00
|
|
|
trial_i = -1
|
2018-03-26 07:32:00 -07:00
|
|
|
end
|
|
|
|
|
2017-09-07 11:41:44 -07:00
|
|
|
local function load_next_trial()
|
2018-06-09 09:44:47 -07:00
|
|
|
if cfg.negate_trials then
|
|
|
|
trial_neg = not trial_neg
|
|
|
|
else
|
|
|
|
trial_neg = true
|
|
|
|
end
|
2017-09-07 11:41:44 -07:00
|
|
|
trial_i = trial_i + 1
|
2018-04-02 06:21:55 -07:00
|
|
|
if trial_i == 0 and not cfg.unperturbed_trial then
|
2018-06-12 16:00:15 -07:00
|
|
|
trial_neg = false
|
2017-09-07 12:00:09 -07:00
|
|
|
trial_i = 1
|
|
|
|
end
|
|
|
|
if trial_i > 0 then
|
2018-06-09 08:56:18 -07:00
|
|
|
--print('loading trial', trial_i)
|
|
|
|
network:distribute(trial_params[trial_i])
|
2017-09-07 12:00:09 -07:00
|
|
|
else
|
2018-06-09 08:56:18 -07:00
|
|
|
--print("test trial")
|
|
|
|
network:distribute(base_params)
|
2018-05-13 16:34:08 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-06-28 21:51:02 -07:00
|
|
|
local function learn_from_epoch()
|
|
|
|
print()
|
2017-07-05 20:26:27 -07:00
|
|
|
|
2018-05-06 20:57:18 -07:00
|
|
|
local current_cost = trial_rewards[0] -- may be nil!
|
|
|
|
|
2018-04-02 06:21:55 -07:00
|
|
|
if cfg.unperturbed_trial then
|
2018-05-06 20:57:18 -07:00
|
|
|
local nth_place = unperturbed_rank(trial_rewards, current_cost)
|
2017-09-07 12:00:09 -07:00
|
|
|
|
|
|
|
-- a rank of 1 means our gradient is uninformative.
|
|
|
|
print(("test trial: %d out of %d"):format(nth_place, #trial_rewards))
|
2017-07-05 20:26:27 -07:00
|
|
|
end
|
2017-06-28 21:51:02 -07:00
|
|
|
|
2018-05-13 16:34:08 -07:00
|
|
|
local delta_rewards = {} -- only used for logging.
|
2018-04-02 06:21:55 -07:00
|
|
|
if cfg.negate_trials then
|
2018-06-09 08:56:18 -07:00
|
|
|
for i = 1, #trial_rewards, 2 do
|
|
|
|
local ind = floor(i / 2) + 1
|
|
|
|
local pos = trial_rewards[i + 0]
|
|
|
|
local neg = trial_rewards[i + 1]
|
|
|
|
delta_rewards[ind] = abs(pos - neg)
|
2018-05-03 06:33:17 -07:00
|
|
|
end
|
2018-03-26 07:32:00 -07:00
|
|
|
end
|
|
|
|
|
2018-06-12 16:36:40 -07:00
|
|
|
local step
|
2018-06-09 09:27:13 -07:00
|
|
|
if cfg.es == 'ars' and cfg.ars_lips then
|
2018-06-12 16:36:40 -07:00
|
|
|
step = es:tell(trial_rewards, current_cost)
|
2018-05-13 16:34:08 -07:00
|
|
|
else
|
2018-06-12 16:36:40 -07:00
|
|
|
step = es:tell(trial_rewards)
|
2017-06-28 21:51:02 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
local step_mean, step_dev = calc_mean_dev(step)
|
2018-05-03 06:33:17 -07:00
|
|
|
print("step mean:", step_mean)
|
|
|
|
print("step stddev:", step_dev)
|
2018-06-09 08:56:18 -07:00
|
|
|
|
|
|
|
base_params = es:params()
|
2017-06-29 02:50:33 -07:00
|
|
|
|
2018-06-15 15:24:55 -07:00
|
|
|
-- TODO: move this all to es:decay methods.
|
2018-06-11 20:39:22 -07:00
|
|
|
if cfg.es == 'snes' then
|
|
|
|
if cfg.sigma_decay > 0 then
|
|
|
|
for i, v in ipairs(es.std) do
|
2018-06-15 15:24:55 -07:00
|
|
|
es.std[i] = v * (1 - es.sigma_rate * cfg.sigma_decay)
|
2018-06-11 20:39:22 -07:00
|
|
|
end
|
2018-06-10 07:41:32 -07:00
|
|
|
end
|
2018-06-15 15:33:11 -07:00
|
|
|
if cfg.param_decay > 0 then
|
2018-06-11 20:39:22 -07:00
|
|
|
for i, v in ipairs(base_params) do
|
2018-06-15 15:33:11 -07:00
|
|
|
base_params[i] = v * (1 - es.param_rate * cfg.param_decay * es.std[i])
|
2018-06-11 20:39:22 -07:00
|
|
|
end
|
|
|
|
end
|
2018-06-14 13:25:54 -07:00
|
|
|
elseif cfg.es == 'xnes' then
|
|
|
|
if cfg.sigma_decay > 0 then
|
|
|
|
es.sigma = es.sigma * (1 - cfg.sigma_decay)
|
|
|
|
end
|
2018-06-15 15:33:11 -07:00
|
|
|
if cfg.param_decay > 0 then
|
2018-06-14 13:25:54 -07:00
|
|
|
for i, v in ipairs(base_params) do
|
2018-06-15 15:33:11 -07:00
|
|
|
base_params[i] = v * (1 - es.param_rate * cfg.param_decay)
|
2018-06-14 13:25:54 -07:00
|
|
|
end
|
|
|
|
end
|
2018-06-11 20:39:22 -07:00
|
|
|
else
|
2018-06-15 15:33:11 -07:00
|
|
|
if cfg.param_decay > 0 then
|
2018-06-11 20:39:22 -07:00
|
|
|
for i, v in ipairs(base_params) do
|
2018-06-15 15:33:11 -07:00
|
|
|
base_params[i] = v * (1 - cfg.param_decay)
|
2018-06-11 20:39:22 -07:00
|
|
|
end
|
2018-06-10 10:34:17 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-06-09 08:56:18 -07:00
|
|
|
es:params(base_params)
|
|
|
|
|
2018-05-03 06:33:17 -07:00
|
|
|
local trial_mean, trial_std = calc_mean_dev(trial_rewards)
|
|
|
|
local delta_mean, delta_std = calc_mean_dev(delta_rewards)
|
2018-06-15 15:33:11 -07:00
|
|
|
local param_mean, param_std = calc_mean_dev(base_params)
|
2018-05-03 06:33:17 -07:00
|
|
|
|
|
|
|
log_csv{
|
|
|
|
epoch = epoch_i,
|
|
|
|
trial_mean = trial_mean,
|
|
|
|
trial_std = trial_std,
|
|
|
|
delta_mean = delta_mean,
|
|
|
|
delta_std = delta_std,
|
|
|
|
step_std = step_dev,
|
2018-06-15 15:33:11 -07:00
|
|
|
weight_mean = param_mean,
|
|
|
|
weight_std = param_std,
|
2018-05-06 20:57:18 -07:00
|
|
|
test_trial = current_cost or 0,
|
2018-06-12 18:00:05 -07:00
|
|
|
decisions = decisions_made,
|
2018-05-03 06:33:17 -07:00
|
|
|
}
|
|
|
|
|
2018-04-02 06:21:55 -07:00
|
|
|
if cfg.enable_network then
|
2017-07-05 20:26:27 -07:00
|
|
|
network:distribute(base_params)
|
2018-06-11 20:36:24 -07:00
|
|
|
network:save(params_fn)
|
2018-06-10 07:40:20 -07:00
|
|
|
|
|
|
|
if cfg.es == 'snes' then
|
|
|
|
local f = assert(open(std_fn, "w"))
|
|
|
|
for _, v in ipairs(es.std) do f:write(("%f\n"):format(v)) end
|
|
|
|
f:close()
|
|
|
|
end
|
|
|
|
|
2017-07-05 20:26:27 -07:00
|
|
|
else
|
2018-06-15 15:33:11 -07:00
|
|
|
print("note: not updating params in playable mode.")
|
2017-07-05 20:26:27 -07:00
|
|
|
end
|
2017-06-28 21:51:02 -07:00
|
|
|
|
|
|
|
print()
|
|
|
|
end
|
|
|
|
|
2018-04-02 05:54:53 -07:00
|
|
|
local function joypad_mash(button)
|
|
|
|
local jp_mash = {
|
|
|
|
up = false,
|
|
|
|
down = false,
|
|
|
|
left = false,
|
|
|
|
right = false,
|
|
|
|
A = false,
|
|
|
|
B = false,
|
|
|
|
select = false,
|
|
|
|
start = false,
|
|
|
|
}
|
|
|
|
assert(jp_mash[button] == false, "invalid button: "..tostring(button), 1)
|
|
|
|
jp_mash[button] = emu.framecount() % 2 == 1
|
|
|
|
joypad.write(1, jp_mash)
|
|
|
|
end
|
|
|
|
|
2018-06-08 19:34:21 -07:00
|
|
|
local function loadlevel(world, level)
|
2018-06-09 08:56:18 -07:00
|
|
|
-- TODO: move to smb.lua. rename to load_level.
|
2018-06-08 19:34:21 -07:00
|
|
|
if world == 0 then world = random(1, 8) end
|
|
|
|
if level == 0 then level = random(1, 4) end
|
|
|
|
emu.poweron()
|
|
|
|
while emu.framecount() < 60 do
|
|
|
|
if emu.framecount() == 32 then
|
|
|
|
local area = game.area_lut[world * 10 + level]
|
|
|
|
game.W(0x75F, world - 1)
|
|
|
|
game.W(0x75C, level - 1)
|
|
|
|
game.W(0x760, area)
|
|
|
|
end
|
|
|
|
if emu.framecount() == 42 then
|
|
|
|
game.W(0x7A0, 0) -- world screen timer (reduces startup time)
|
|
|
|
end
|
|
|
|
joypad_mash('start')
|
|
|
|
emu.frameadvance()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-06-28 21:51:02 -07:00
|
|
|
local function do_reset()
|
2018-05-12 13:38:51 -07:00
|
|
|
local state = game.get_state()
|
2017-09-07 11:41:44 -07:00
|
|
|
-- be a little more descriptive.
|
2018-05-12 13:38:51 -07:00
|
|
|
if state == 'dead' and game.get_timer() == 0 then state = 'timeup' end
|
2018-03-27 04:04:44 -07:00
|
|
|
|
2018-05-12 13:56:04 -07:00
|
|
|
if trial_i >= 0 then
|
2018-03-27 04:04:44 -07:00
|
|
|
if trial_i == 0 then
|
|
|
|
print('test trial reward:', reward, "("..state..")")
|
2018-04-02 06:21:55 -07:00
|
|
|
elseif cfg.negate_trials then
|
2018-03-27 04:04:44 -07:00
|
|
|
--local dir = trial_neg and "negative" or "positive"
|
|
|
|
--print('trial', trial_i, dir, 'reward:', reward, "("..state..")")
|
|
|
|
|
|
|
|
if trial_neg then
|
|
|
|
local pos = trial_rewards[#trial_rewards]
|
|
|
|
local neg = reward
|
|
|
|
local fmt = "trial %i rewards: %+i, %+i (%s, %s)"
|
2018-06-09 08:56:18 -07:00
|
|
|
print(fmt:format(floor(trial_i / 2),
|
|
|
|
pos, neg, last_trial_state, state))
|
2018-03-27 04:04:44 -07:00
|
|
|
end
|
|
|
|
last_trial_state = state
|
|
|
|
else
|
|
|
|
print('trial', trial_i, 'reward:', reward, "("..state..")")
|
|
|
|
end
|
|
|
|
else
|
|
|
|
print("reward:", reward, "("..state..")")
|
|
|
|
end
|
2017-06-28 21:51:02 -07:00
|
|
|
|
2018-03-26 07:32:00 -07:00
|
|
|
if trial_i >= 0 then
|
2018-04-02 06:21:55 -07:00
|
|
|
if trial_i == 0 or not cfg.negate_trials then
|
2018-03-26 07:32:00 -07:00
|
|
|
trial_rewards[trial_i] = reward
|
|
|
|
else
|
|
|
|
trial_rewards[#trial_rewards + 1] = reward
|
|
|
|
end
|
|
|
|
end
|
2017-06-28 21:51:02 -07:00
|
|
|
|
2018-06-09 08:56:18 -07:00
|
|
|
if epoch_i == 0 or (trial_i == #trial_params and trial_neg) then
|
2017-06-29 02:50:33 -07:00
|
|
|
if epoch_i > 0 then learn_from_epoch() end
|
2018-04-03 09:13:11 -07:00
|
|
|
if not cfg.playback_mode then epoch_i = epoch_i + 1 end
|
2017-06-28 21:51:02 -07:00
|
|
|
prepare_epoch()
|
2018-06-09 09:27:13 -07:00
|
|
|
collectgarbage()
|
2018-06-08 19:34:21 -07:00
|
|
|
if any_random then
|
|
|
|
loadlevel(cfg.starting_world, cfg.starting_level)
|
|
|
|
state_saved = false
|
|
|
|
end
|
2017-06-28 21:51:02 -07:00
|
|
|
end
|
2017-06-28 17:14:56 -07:00
|
|
|
|
2018-06-12 15:59:36 -07:00
|
|
|
max_time = 6 * sqrt(480 / #trial_params * (epoch_i - 1)) + 60
|
|
|
|
max_time = clamp(max_time, cfg.min_time, cfg.max_time)
|
2018-06-09 08:56:18 -07:00
|
|
|
max_time = ceil(max_time)
|
|
|
|
|
|
|
|
-- TODO: game.reset(cfg.starting_lives, cfg.start_big)
|
|
|
|
|
2018-05-12 13:38:51 -07:00
|
|
|
if game.get_state() == 'loading' then game.advance() end -- kind of a hack.
|
2017-06-28 17:14:56 -07:00
|
|
|
reward = 0
|
2018-05-12 13:38:51 -07:00
|
|
|
powerup_old = game.R(0x754)
|
|
|
|
status_old = game.R(0x756)
|
|
|
|
coins_old = game.R(0x7ED) * 10 + game.R(0x7EE)
|
|
|
|
score_old = game.get_score()
|
2017-06-28 17:14:56 -07:00
|
|
|
|
2018-03-26 07:32:00 -07:00
|
|
|
-- set number of lives. (mario gets n+1 chances)
|
2018-05-12 13:38:51 -07:00
|
|
|
game.W(0x75A, cfg.starting_lives)
|
2018-03-26 07:32:00 -07:00
|
|
|
|
2018-04-02 06:21:55 -07:00
|
|
|
if cfg.start_big then
|
2018-03-26 07:32:00 -07:00
|
|
|
-- make mario "super".
|
2018-05-12 13:38:51 -07:00
|
|
|
game.W(0x754, 0)
|
|
|
|
game.W(0x756, 1)
|
2018-03-26 07:32:00 -07:00
|
|
|
end
|
2017-06-28 17:14:56 -07:00
|
|
|
|
2018-06-09 08:56:18 -07:00
|
|
|
-- end of game.reset()
|
2017-06-29 02:50:33 -07:00
|
|
|
|
2018-06-08 19:34:21 -07:00
|
|
|
if state_saved then
|
2017-06-29 02:50:33 -07:00
|
|
|
savestate.load(startsave)
|
|
|
|
else
|
|
|
|
savestate.save(startsave)
|
|
|
|
end
|
2018-06-08 19:34:21 -07:00
|
|
|
state_saved = true
|
2017-06-29 02:50:33 -07:00
|
|
|
|
2017-09-07 14:20:53 -07:00
|
|
|
jp = nil
|
|
|
|
screen_scroll_delta = 0
|
2018-05-07 07:20:59 -07:00
|
|
|
trial_frames = 0
|
2017-06-28 17:14:56 -07:00
|
|
|
emu.frameadvance() -- prevents emulator from quirking up.
|
|
|
|
|
2017-06-28 21:51:02 -07:00
|
|
|
load_next_trial()
|
|
|
|
|
2017-06-28 17:14:56 -07:00
|
|
|
reset = false
|
|
|
|
end
|
|
|
|
|
2017-06-28 21:51:02 -07:00
|
|
|
local function init()
|
2018-04-02 06:21:55 -07:00
|
|
|
network = make_network(gcfg.input_size)
|
2017-06-28 21:51:02 -07:00
|
|
|
network:reset()
|
2017-09-07 16:06:43 -07:00
|
|
|
network:print()
|
2017-06-28 21:51:02 -07:00
|
|
|
print("parameters:", network.n_param)
|
|
|
|
|
2018-04-02 06:21:55 -07:00
|
|
|
if cfg.init_zeros then
|
2018-03-26 07:32:00 -07:00
|
|
|
local W = network:collect()
|
|
|
|
for i, w in ipairs(W) do W[i] = 0 end
|
|
|
|
network:distribute(W)
|
|
|
|
end
|
|
|
|
|
2017-06-28 21:51:02 -07:00
|
|
|
emu.poweron()
|
|
|
|
emu.unpause()
|
2018-06-08 05:51:17 -07:00
|
|
|
if not cfg.playable_mode then emu.speedmode("turbo") end
|
2017-06-29 02:50:33 -07:00
|
|
|
|
2018-06-08 19:34:21 -07:00
|
|
|
if not any_random then
|
|
|
|
loadlevel(cfg.starting_world, cfg.starting_level)
|
2018-04-02 05:54:53 -07:00
|
|
|
end
|
|
|
|
|
2018-06-11 20:36:24 -07:00
|
|
|
params_fn = cfg.params_fn or ('network%07i.txt'):format(network.n_param)
|
|
|
|
std_fn = params_fn:gsub(".txt", "")..".sigma.txt"
|
|
|
|
|
|
|
|
if exists(params_fn) then
|
|
|
|
network:load(params_fn)
|
2018-06-10 07:35:28 -07:00
|
|
|
end
|
2018-06-09 08:56:18 -07:00
|
|
|
|
2018-06-09 09:27:13 -07:00
|
|
|
if cfg.es == 'xnes' then
|
|
|
|
-- if you get an out of memory error, you can't use xNES. sorry!
|
|
|
|
-- maybe there'll be a patch for FCEUX in the future.
|
2018-06-10 07:33:38 -07:00
|
|
|
es = xnes.Xnes(network.n_param, cfg.epoch_trials,
|
2018-06-15 15:24:55 -07:00
|
|
|
cfg.base_rate, cfg.deviation, cfg.negate_trials)
|
2018-06-10 07:40:20 -07:00
|
|
|
elseif cfg.es == 'snes' then
|
|
|
|
es = snes.Snes(network.n_param, cfg.epoch_trials,
|
2018-06-15 15:24:55 -07:00
|
|
|
cfg.base_rate, cfg.deviation, cfg.negate_trials)
|
2018-06-10 07:40:20 -07:00
|
|
|
-- TODO: clean this up into an interface:
|
2018-06-12 16:19:32 -07:00
|
|
|
es.min_refresh = cfg.min_refresh
|
2018-06-10 07:40:20 -07:00
|
|
|
|
|
|
|
if exists(std_fn) then
|
|
|
|
local f = assert(open(std_fn, "r"))
|
|
|
|
for i=1, network.n_param do
|
|
|
|
es.std[i] = assert(tonumber(assert(f:read())))
|
|
|
|
end
|
|
|
|
f:close()
|
|
|
|
end
|
2018-06-09 09:27:13 -07:00
|
|
|
elseif cfg.es == 'ars' then
|
2018-06-09 08:56:18 -07:00
|
|
|
es = ars.Ars(network.n_param, cfg.epoch_trials, cfg.epoch_top_trials,
|
2018-06-15 15:24:55 -07:00
|
|
|
cfg.base_rate, cfg.deviation, cfg.negate_trials)
|
2018-06-09 08:56:18 -07:00
|
|
|
else
|
|
|
|
error("Unknown evolution strategy specified: " + tostring(cfg.es))
|
|
|
|
end
|
2018-06-10 10:34:06 -07:00
|
|
|
|
2018-06-15 15:33:11 -07:00
|
|
|
es.param_rate = cfg.param_rate
|
2018-06-15 15:24:55 -07:00
|
|
|
es.sigma_rate = cfg.sigma_rate
|
|
|
|
es.covar_rate = cfg.covar_rate
|
|
|
|
es.rate_init = cfg.sigma_rate -- just for SNES?
|
|
|
|
|
2018-06-10 10:34:06 -07:00
|
|
|
es:params(network:collect())
|
2017-06-28 21:51:02 -07:00
|
|
|
end
|
|
|
|
|
2018-04-03 09:13:11 -07:00
|
|
|
local function prepare_reset()
|
|
|
|
if cfg.playback_mode then return end
|
|
|
|
reset = true
|
|
|
|
end
|
|
|
|
|
2018-06-08 04:48:59 -07:00
|
|
|
local function draw_framecount(x, y, frames)
|
|
|
|
local tf0 = frames % 1000
|
|
|
|
local tf1 = (frames % 1000000 - tf0) / 1000
|
|
|
|
local tf2 = (frames - tf0 - tf1) / 1000000
|
|
|
|
gui.text(x, y, ("%03i,%03i,%03i"):format(tf2,tf1,tf0), '#FFFFFF', '#0000003F')
|
|
|
|
end
|
|
|
|
|
2017-09-07 14:20:53 -07:00
|
|
|
local function doit(dummy)
|
2018-05-12 13:38:51 -07:00
|
|
|
local ingame_paused = game.get_state() == "paused"
|
2017-09-07 15:11:57 -07:00
|
|
|
|
|
|
|
-- every few frames mario stands still, forcibly decrease the timer.
|
|
|
|
-- this includes having the game paused.
|
|
|
|
-- TODO: more robust. doesn't detect moonwalking against a wall.
|
2017-09-09 12:46:35 -07:00
|
|
|
-- well, that shouldn't happen anymore now that i've disabled left+right.
|
2018-05-12 13:38:51 -07:00
|
|
|
local timer = game.get_timer()
|
|
|
|
if ingame_paused or random() > 1 - cfg.timer_loser and game.R(0x1D) == 0 and game.R(0x57) == 0 then
|
2017-09-07 15:11:57 -07:00
|
|
|
timer = timer - 1
|
|
|
|
end
|
2018-04-03 09:13:11 -07:00
|
|
|
if not cfg.playback_mode then
|
|
|
|
timer = clamp(timer, 0, max_time)
|
|
|
|
if cfg.enable_network then
|
2018-05-12 13:38:51 -07:00
|
|
|
game.set_timer(timer)
|
2018-04-03 09:13:11 -07:00
|
|
|
end
|
2017-09-07 15:11:57 -07:00
|
|
|
end
|
|
|
|
|
2018-06-10 07:48:02 -07:00
|
|
|
draw_framecount(12, 215, decisions_made)
|
2017-09-07 15:11:57 -07:00
|
|
|
|
2018-05-12 13:38:51 -07:00
|
|
|
screen_scroll_delta = screen_scroll_delta + game.R(0x775)
|
2017-09-09 12:37:01 -07:00
|
|
|
|
2017-09-07 14:20:53 -07:00
|
|
|
if dummy == true then
|
2017-09-09 12:37:01 -07:00
|
|
|
-- don't invoke AI this frame. (keep holding the old inputs)
|
2018-03-31 09:40:35 -07:00
|
|
|
gui.text(89, 16, ("%+5i"):format(reward), '#FFFFFF', '#0000003F')
|
2017-09-07 14:20:53 -07:00
|
|
|
return
|
2017-06-28 02:33:18 -07:00
|
|
|
end
|
|
|
|
|
2018-05-12 13:38:51 -07:00
|
|
|
empty(game.sprite_input)
|
|
|
|
empty(game.tile_input)
|
|
|
|
empty(game.extra_input)
|
2017-06-28 02:33:18 -07:00
|
|
|
|
2018-06-08 16:43:22 -07:00
|
|
|
local controllable = game.R(0x757) == 0 and game.R(0x758) == 0
|
2018-05-12 13:38:51 -07:00
|
|
|
local x, y = game.getxy(0, 0x86, 0xCE, 0x6D, 0xB5)
|
|
|
|
local powerup = game.R(0x754)
|
|
|
|
local status = game.R(0x756)
|
|
|
|
game.mark_sprite(x + 8, y + 24, -powerup - 1)
|
2018-06-13 11:18:10 -07:00
|
|
|
-- TODO: this will have to do until sprite type embed is added:
|
|
|
|
insert(game.extra_input, (status - 1) * 256)
|
2017-06-28 02:33:18 -07:00
|
|
|
|
2018-05-12 13:38:51 -07:00
|
|
|
local vx, vy = game.S(0x57), game.S(0x9F)
|
|
|
|
insert(game.extra_input, vx * 16)
|
|
|
|
insert(game.extra_input, vy * 16)
|
2017-07-05 20:26:27 -07:00
|
|
|
|
2018-04-02 06:21:55 -07:00
|
|
|
if cfg.time_inputs then
|
2018-04-02 05:54:53 -07:00
|
|
|
for i=2,5 do
|
2018-05-07 07:27:51 -07:00
|
|
|
local v = band(trial_frames, lshift(1, i)) == 0 and -181 or 181
|
2018-05-12 13:38:51 -07:00
|
|
|
insert(game.extra_input, v)
|
2018-04-02 05:54:53 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-05-12 13:38:51 -07:00
|
|
|
game.handle_enemies()
|
|
|
|
game.handle_fireballs()
|
|
|
|
--game.handle_blocks() -- blocks being hit. not interactable; we don't care!
|
|
|
|
game.handle_hammers()
|
|
|
|
game.handle_misc()
|
|
|
|
game.handle_tiles()
|
2017-06-28 02:33:18 -07:00
|
|
|
|
2018-05-12 13:38:51 -07:00
|
|
|
local coins = game.R(0x7ED) * 10 + game.R(0x7EE)
|
2017-06-28 02:33:18 -07:00
|
|
|
local coins_delta = coins - coins_old
|
|
|
|
-- handle wrap-around.
|
|
|
|
if coins_delta < 0 then coins_delta = 100 + coins - coins_old end
|
|
|
|
-- remember that 0 is big mario and 1 is small mario.
|
|
|
|
local powerup_delta = powerup_old - powerup
|
|
|
|
-- 2 is fire mario.
|
|
|
|
local status_delta = clamp(status - status_old, -1, 1)
|
2018-05-12 13:38:51 -07:00
|
|
|
local flagpole_bonus = game.R(0xE) == 4 and cfg.frameskip or 0
|
2017-07-05 20:26:27 -07:00
|
|
|
--local reward_delta = screen_scroll_delta + status_delta * 256 + flagpole_bonus
|
2018-05-12 13:38:51 -07:00
|
|
|
local score_delta = game.get_score() - score_old
|
2017-07-05 20:26:27 -07:00
|
|
|
if score_delta < 0 then score_delta = 0 end
|
2018-06-08 14:59:43 -07:00
|
|
|
local reward_delta = screen_scroll_delta + cfg.score_multiplier * (score_delta + flagpole_bonus)
|
2017-09-07 14:20:53 -07:00
|
|
|
screen_scroll_delta = 0
|
2017-06-29 02:50:33 -07:00
|
|
|
|
2018-04-02 06:21:55 -07:00
|
|
|
if cfg.decrement_reward and reward_delta == 0 then reward_delta = reward_delta - 1 end
|
2018-03-26 07:32:00 -07:00
|
|
|
|
2018-06-08 19:35:09 -07:00
|
|
|
if not ingame_paused and game.get_state() ~= 'win_walking' then
|
2018-06-08 05:12:21 -07:00
|
|
|
-- note that we exclude points gained walking into the castle.
|
|
|
|
-- this way, we avoid adding the timer-based fireworks to our reward,
|
|
|
|
-- which are basically unwanted noise due to the way they trigger.
|
2018-06-08 19:35:09 -07:00
|
|
|
if flagpole_bonus > 0 or controllable then
|
|
|
|
reward = reward + reward_delta
|
|
|
|
end
|
2018-06-08 05:12:21 -07:00
|
|
|
end
|
2017-06-28 02:33:18 -07:00
|
|
|
|
2017-07-05 20:26:27 -07:00
|
|
|
--gui.text(72, 12, ("%+4i"):format(reward_delta), '#FFFFFF', '#0000003F')
|
2018-03-31 09:40:35 -07:00
|
|
|
gui.text(89, 16, ("%+5i"):format(reward), '#FFFFFF', '#0000003F')
|
2017-06-28 02:33:18 -07:00
|
|
|
|
2018-05-12 13:38:51 -07:00
|
|
|
if game.get_state() == 'dead' and state_old ~= 'dead' then
|
|
|
|
--print("dead. lives remaining:", game.R(0x75A, 0))
|
|
|
|
if game.R(0x75A, 0) == 0 then prepare_reset() end
|
2017-06-28 02:33:18 -07:00
|
|
|
end
|
2018-05-12 13:38:51 -07:00
|
|
|
if game.get_state() == 'lose' then
|
2017-09-09 12:46:35 -07:00
|
|
|
-- this shouldn't happen if we catch the deaths as above.
|
2017-06-28 02:33:18 -07:00
|
|
|
print("ran out of lives.")
|
2018-04-03 09:13:11 -07:00
|
|
|
if not cfg.playback_mode then prepare_reset() end
|
2017-06-28 02:33:18 -07:00
|
|
|
end
|
|
|
|
|
2017-06-28 21:51:02 -07:00
|
|
|
-- lose a point for every frame paused.
|
2017-06-29 02:50:33 -07:00
|
|
|
--if ingame_paused then reward = reward - 1 end
|
2018-04-03 09:13:11 -07:00
|
|
|
if ingame_paused then reward = reward - 402; prepare_reset() end
|
2017-06-28 21:51:02 -07:00
|
|
|
|
|
|
|
-- if we've run out of time while the game is paused...
|
|
|
|
-- that's cheating! unpause.
|
|
|
|
force_start = ingame_paused and timer == 0
|
|
|
|
|
2017-09-07 14:20:53 -07:00
|
|
|
local X = {}
|
2018-05-12 13:38:51 -07:00
|
|
|
for i, v in ipairs(game.sprite_input) do insert(X, v / 256) end
|
|
|
|
for i, v in ipairs(game.extra_input) do insert(X, v / 256) end
|
2018-04-02 06:21:55 -07:00
|
|
|
nn.reshape(X, 1, gcfg.input_size)
|
2018-05-12 13:38:51 -07:00
|
|
|
nn.reshape(game.tile_input, 1, gcfg.tile_count)
|
2017-06-28 02:33:18 -07:00
|
|
|
|
2018-05-07 07:20:59 -07:00
|
|
|
trial_frames = trial_frames + cfg.frameskip
|
2018-05-12 13:38:51 -07:00
|
|
|
if cfg.enable_network and game.get_state() == 'playing' or ingame_paused then
|
2018-04-02 06:21:55 -07:00
|
|
|
total_frames = total_frames + cfg.frameskip
|
2017-09-08 03:27:10 -07:00
|
|
|
|
2018-05-12 13:38:51 -07:00
|
|
|
local outputs = network:forward({[nn_x]=X, [nn_tx]=game.tile_input})
|
2017-07-05 20:26:27 -07:00
|
|
|
|
2018-04-02 06:21:55 -07:00
|
|
|
local eps = lerp(cfg.eps_start, cfg.eps_stop, total_frames / cfg.eps_frames)
|
|
|
|
if cfg.det_epsilon and random() < eps then
|
|
|
|
local i = floor(random() * #gcfg.jp_lut) + 1
|
2018-05-12 13:38:51 -07:00
|
|
|
jp = copy(gcfg.jp_lut[i], jp)
|
2017-09-08 03:43:32 -07:00
|
|
|
else
|
2018-04-02 06:21:55 -07:00
|
|
|
local choose = cfg.deterministic and argmax or softchoice
|
2018-03-27 04:04:44 -07:00
|
|
|
local ind = choose(unpack(outputs[nn_z]))
|
2018-05-12 13:38:51 -07:00
|
|
|
jp = copy(gcfg.jp_lut[ind], jp)
|
2017-07-05 20:26:27 -07:00
|
|
|
end
|
|
|
|
|
2017-06-28 21:51:02 -07:00
|
|
|
if force_start then
|
|
|
|
jp = {
|
|
|
|
up = false,
|
|
|
|
down = false,
|
|
|
|
left = false,
|
|
|
|
right = false,
|
|
|
|
A = false,
|
|
|
|
B = false,
|
|
|
|
start = force_start_old,
|
|
|
|
select = false,
|
|
|
|
}
|
2018-06-10 07:48:02 -07:00
|
|
|
else
|
|
|
|
decisions_made = decisions_made + 1
|
2017-06-28 21:51:02 -07:00
|
|
|
end
|
2017-06-28 02:33:18 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
coins_old = coins
|
|
|
|
powerup_old = powerup
|
|
|
|
status_old = status
|
2017-06-28 21:51:02 -07:00
|
|
|
force_start_old = force_start
|
2018-05-12 13:38:51 -07:00
|
|
|
state_old = game.get_state()
|
|
|
|
score_old = game.get_score()
|
2017-09-07 14:20:53 -07:00
|
|
|
end
|
|
|
|
|
2017-09-09 12:46:35 -07:00
|
|
|
init()
|
|
|
|
|
2017-09-07 14:20:53 -07:00
|
|
|
while true do
|
2018-05-12 13:38:51 -07:00
|
|
|
gui.text(4, 12, game.get_state(), '#FFFFFF', '#0000003F')
|
2017-09-07 14:20:53 -07:00
|
|
|
|
2018-05-12 13:38:51 -07:00
|
|
|
while gcfg.bad_states[game.get_state()] do
|
2017-09-07 14:20:53 -07:00
|
|
|
-- mash the start button until we have control.
|
2018-04-02 05:54:53 -07:00
|
|
|
joypad_mash('start')
|
2018-04-03 09:13:11 -07:00
|
|
|
prepare_reset()
|
2017-09-07 14:20:53 -07:00
|
|
|
|
2018-05-12 13:38:51 -07:00
|
|
|
game.advance()
|
|
|
|
gui.text(4, 12, game.get_state(), '#FFFFFF', '#0000003F')
|
2017-09-07 14:20:53 -07:00
|
|
|
|
2018-05-12 13:38:51 -07:00
|
|
|
while game.get_state() == "loading" do game.advance() end -- kind of a hack.
|
|
|
|
state_old = game.get_state()
|
2017-09-07 14:20:53 -07:00
|
|
|
end
|
|
|
|
|
2018-06-08 04:48:59 -07:00
|
|
|
if reset then
|
|
|
|
do_reset()
|
|
|
|
lagless_count = 0
|
|
|
|
end
|
2017-09-07 14:20:53 -07:00
|
|
|
|
2018-04-02 06:21:55 -07:00
|
|
|
if not cfg.enable_network then
|
2017-09-07 14:20:53 -07:00
|
|
|
-- infinite time cheat. super handy for testing.
|
2018-05-12 13:38:51 -07:00
|
|
|
if game.R(0xE) == 8 then
|
|
|
|
game.set_timer(667)
|
2017-09-07 14:20:53 -07:00
|
|
|
poketime = true
|
|
|
|
elseif poketime then
|
|
|
|
poketime = false
|
2018-05-12 13:38:51 -07:00
|
|
|
game.set_timer(1)
|
2017-09-07 14:20:53 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
-- infinite lives.
|
2018-05-12 13:38:51 -07:00
|
|
|
game.W(0x75A, 1)
|
2017-09-07 14:20:53 -07:00
|
|
|
end
|
|
|
|
|
2018-06-08 04:48:59 -07:00
|
|
|
local doot = jp == nil or lagless_count % cfg.frameskip == 0
|
2017-09-07 14:20:53 -07:00
|
|
|
doit(not doot)
|
|
|
|
|
|
|
|
-- jp might still be nil if we're not ingame or we're not playing.
|
|
|
|
if jp ~= nil then joypad.write(1, jp) end
|
|
|
|
|
2018-06-08 04:48:59 -07:00
|
|
|
game.advance() -- remember, this skips lag frames.
|
|
|
|
lagless_count = lagless_count + 1
|
2017-06-28 02:33:18 -07:00
|
|
|
end
|