mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-09 18:07:27 -04:00
128 lines
4.6 KiB
Lua
128 lines
4.6 KiB
Lua
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
love = love or require("tests.love_stub")
|
|
local T = require("tests.harness")
|
|
local F = require("tests.engine._g3u_fixture")
|
|
local Table = require("src.battle.g3u.Table")
|
|
local Match = require("src.battle.g3u.Match")
|
|
local Scope = require("src.battle.g3u.Scope")
|
|
|
|
local function deep(a, b)
|
|
if type(a) ~= type(b) then return false end
|
|
if type(a) ~= "table" then return a == b end
|
|
for k, v in pairs(a) do if not deep(v, b[k]) then return false end end
|
|
for k in pairs(b) do if a[k] == nil then return false end end
|
|
return true
|
|
end
|
|
|
|
local function legalSame(a, b)
|
|
for seat = 0, 1 do
|
|
if not deep(a:legalActions(seat), b:legalActions(seat)) then return false end
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function battle(t, seed)
|
|
local rnd = F.lcg(seed)
|
|
local p0, p1 = F.randomParty(t, rnd, rnd(4)), F.randomParty(t, rnd, rnd(4))
|
|
local opts = { table = t, parties = { [0] = p0, [1] = p1 }, seed = seed * 7919 }
|
|
local a, b = Match.new(opts), Match.new(opts)
|
|
local ea, eb = a:start(), b:start()
|
|
if not deep(ea, eb) then return false, "start events differ" end
|
|
local steps, hashes = 0, 0
|
|
while a.phase ~= "over" do
|
|
steps = steps + 1
|
|
if steps > 600 then return false, "battle did not end" end
|
|
if a.phase ~= b.phase then return false, "phase differs" end
|
|
if not legalSame(a, b) then return false, "legal actions differ at turn " .. a.turn end
|
|
if a.phase == "choose" then
|
|
local acts = { [0] = F.pick(a, 0, rnd), [1] = F.pick(a, 1, rnd) }
|
|
ea, eb = a:submit(acts), b:submit(acts)
|
|
else
|
|
local picks = {}
|
|
for seat = 0, 1 do
|
|
if a:needs(seat) then
|
|
local l = a:legalActions(seat)
|
|
picks[seat] = l[rnd(#l)].index
|
|
end
|
|
end
|
|
ea, eb = a:replace(picks), b:replace(picks)
|
|
end
|
|
if not deep(ea, eb) then return false, "events differ at turn " .. a.turn end
|
|
if a:hash() ~= b:hash() then return false, "hash differs at turn " .. a.turn end
|
|
hashes = hashes + 1
|
|
end
|
|
if b.phase ~= "over" or not deep(a.result, b.result) then return false, "result differs" end
|
|
for turn = 0, a.turn do
|
|
if a:hash(turn) == nil or a:hash(turn) ~= b:hash(turn) then return false, "turn hash " .. turn end
|
|
end
|
|
return true, a.turn
|
|
end
|
|
|
|
local function run(label, t, count, base)
|
|
Scope.resetTrips()
|
|
local ok, turns, failure = 0, 0, nil
|
|
for i = 1, count do
|
|
local good, info = battle(t, base + i)
|
|
if good then
|
|
ok = ok + 1
|
|
turns = turns + info
|
|
elseif not failure then
|
|
failure = ("seed %d: %s"):format(base + i, tostring(info))
|
|
end
|
|
end
|
|
T.eq(ok, count, label .. ": every battle stays in lockstep " .. tostring(failure or ""))
|
|
T.check(turns > count * 3, label .. ": battles run real turns (" .. turns .. ")")
|
|
T.eq(#Scope.trips, 0, label .. ": no cache, text or math.random read during a match " .. tostring(Scope.trips[1]))
|
|
end
|
|
|
|
local t1 = assert(Table.build(F.gen1()))
|
|
local t2 = assert(Table.build(F.gen2()))
|
|
run("gen1 fixture", t1, 120, 1000)
|
|
run("gen2 fixture", t2, 80, 5000)
|
|
|
|
local function solo(t, seed, script)
|
|
local rnd = F.lcg(seed)
|
|
local p0, p1 = F.randomParty(t, rnd, 3), F.randomParty(t, rnd, 3)
|
|
local m = Match.new({ table = t, parties = { [0] = p0, [1] = p1 }, seed = script and script.seed or seed })
|
|
m:start()
|
|
local log, i = { steps = {}, seed = seed }, 0
|
|
while m.phase ~= "over" and i < 400 do
|
|
i = i + 1
|
|
local step = script and script.steps[i]
|
|
if m.phase == "choose" then
|
|
step = step or { [0] = F.pick(m, 0, rnd), [1] = F.pick(m, 1, rnd) }
|
|
if script and not (m:isLegal(0, step[0]) and m:isLegal(1, step[1])) then break end
|
|
m:submit(step)
|
|
else
|
|
if not step then
|
|
step = {}
|
|
for s = 0, 1 do if m:needs(s) then step[s] = m:legalActions(s)[1].index end end
|
|
end
|
|
local okR = pcall(m.replace, m, step)
|
|
if not okR then break end
|
|
end
|
|
log.steps[i] = step
|
|
end
|
|
local hashes = {}
|
|
for turn = 0, m.turn do hashes[turn] = m:hash(turn) end
|
|
return log, hashes
|
|
end
|
|
|
|
local log, first = solo(t1, 77)
|
|
local _, again = solo(t1, 77, log)
|
|
T.same(again, first, "a replay run after the first match finished reproduces every turn hash")
|
|
local _, other = solo(t1, 77, { seed = 78, steps = log.steps })
|
|
T.check(other[1] ~= first[1] or other[2] ~= first[2] or other[3] ~= first[3],
|
|
"a different seed changes the turn hashes")
|
|
|
|
for _, pair in ipairs({ { "red", "g1r-red" }, { "gold", "g1r-gold" } }) do
|
|
local data = F.real(pair[1], pair[2])
|
|
if data then
|
|
run(pair[1] .. " cache", assert(Table.build(data)), 60, 9000)
|
|
else
|
|
print("[skip] " .. pair[1] .. " cache not imported")
|
|
end
|
|
end
|
|
|
|
T.finish("g3u lockstep")
|