Skip to content

Instantly share code, notes, and snippets.

@taotao54321
Created June 5, 2023 04:46
Show Gist options
  • Save taotao54321/6a4cd1bbc4345cf4bd4c42d2babb6e16 to your computer and use it in GitHub Desktop.
Save taotao54321/6a4cd1bbc4345cf4bd4c42d2babb6e16 to your computer and use it in GitHub Desktop.
NES Flipull (v1.0) - a script to examine frame costs for throwing block
--[[
フリップル (FC) 着手の時間コスト調査スクリプト for FCEUX
(投げる位置, 当たる位置) ごとの所要フレームを調べる。
自機が最上段にいる状態で起動すること。
--]]
-- メモリから u8 値を読み取る。
local function mem_read_u8(addr)
return memory.readbyte(addr)
end
-- メモリに u8 値を書き込む。
local function mem_write_u8(addr, value)
memory.writebyte(addr, value)
end
-- 指定された入力で 1 フレーム進める。
local function run_frame(input)
joypad.set(1, input)
emu.frameadvance()
end
-- 指定された入力で n フレーム進める。
local function run_frames(n, input)
for _i = 1, n do
run_frame(input)
end
end
local ADDR_BLOCKS = 0x0400
local ADDR_WALLS = 0x0440
local ADDR_PIPES = 0x044C
-- 自機ピクセル座標yを得る。
local function get_hero_pixel_y()
return mem_read_u8(0x0302)
end
-- 座標 (x,y) にブロック block を配置する。
-- 座標は 6x6 のブロック領域内でなければならない。
local function put_block(x, y, block)
assert(0 <= x and x < 6)
assert(6 <= y and y < 12)
local addr = ADDR_BLOCKS + 8 * (y - 6) + x
mem_write_u8(addr, block)
end
-- 座標 (x,y) に壁を配置する。
local function put_wall(x, y)
assert(0 <= x and x < 8)
assert(0 <= y and y < 12)
local addr = ADDR_WALLS + y
local value = mem_read_u8(addr)
local value_new = bit.bor(value, bit.lshift(1, (7 - x)))
mem_write_u8(addr, value_new)
end
local BLOCK_ERASE = 1 -- 消去するブロック。初期状態では 6x6 のブロック領域全てと保持ブロックがこれになる。
local BLOCK_STOP = 2 -- 投げたブロックと置換されるブロック。置換が起こる位置に配置される。
-- 局面を調査用に初期化する。
local function init_position()
-- 全ての壁/パイプを除去する。
for i = 0, 12-1 do
mem_write_u8(ADDR_WALLS + i - 1, 0)
end
for i = 0, 12-1 do
mem_write_u8(ADDR_PIPES + i - 1, 0)
end
-- 6x6 のブロック領域を全て BLOCK_ERASE で埋める。
for y = 6, 12-1 do
for x = 0, 6-1 do
put_block(x, y, BLOCK_ERASE)
end
end
-- 保持ブロックを BLOCK_ERASE にする。
mem_write_u8(0x030F, BLOCK_ERASE)
end
-- 自機を指定した座標yまで移動させる。
local function move_hero(hero_y)
for _i = 1, hero_y do
run_frame({ down = true })
run_frames(15, {})
end
assert(get_hero_pixel_y() == 16 * (2 + hero_y))
end
local COST_INF = 99999
-- (自機座標y, 衝突直前の座標) を与えたときの時間コスト (F) を返す。
-- 不可能な場合は COST_INF を返す。
--
-- たとえば、hero_y = 11, dst = (1, 11) ならば、(1, 11) まで貫通消去し、(0, 11) で置換が起こる。
local function calc_cost(hero_y, dst_x, dst_y)
-- 調査対象の状況になるように局面を変更する。
if hero_y < 6 then
-- hero_y < 6 の場合、壁に当たって下に落ちる形になる。
if dst_x > 0 then
put_wall(dst_x - 1, hero_y)
end
if dst_y < 11 then
put_block(dst_x, dst_y + 1, BLOCK_STOP)
-- 置換によりミスにならないよう細工する。
if dst_x == 5 then
put_block(4, dst_y, BLOCK_STOP)
else
put_block(5, 11, BLOCK_STOP)
end
end
else
-- hero_y >= 6 の場合、必ず横からブロックに当たる形になる。
-- (壁に当たって下に落ちてブロックに当たるケースは存在しない)
-- この場合、dst_y < hero_y となることはありえず、
-- dst_y > hero_y となるのは一番奥まで貫通して下に落ちるケースのみ。
if dst_y < hero_y then
return COST_INF
end
if dst_y == hero_y then
if dst_x == 0 and dst_y < 11 then
put_block(0, dst_y + 1, BLOCK_STOP)
elseif dst_x ~= 0 then
put_block(dst_x - 1, dst_y, BLOCK_STOP)
end
end
if dst_y > hero_y then
if dst_x ~= 0 then
return COST_INF
end
if dst_y < 11 then
put_block(0, dst_y + 1, BLOCK_STOP)
end
end
-- 置換によりミスにならないよう細工する。
put_block(5, 6 + (hero_y - 6 + 1) % 6, BLOCK_STOP)
end
move_hero(hero_y)
local frame_start = emu.framecount()
local input_move = get_hero_pixel_y() == 0x20 and { down = true } or { up = true }
-- ブロックを投げる。
run_frame({ A = true })
while true do
-- 上または下を入力し、実際にキー入力が受け付けられたら終了。
run_frame(input_move)
if mem_read_u8(0x0303) ~= 0 then
break
end
end
local frame_end = emu.framecount()
return frame_end - frame_start - 1
end
local wtr = nil
local function work()
-- 自機が最上段にいることを確認。
assert(get_hero_pixel_y() == 0x20)
-- 最初の emu.frameadvance() が実際にはフレームを進めない不具合の対策。
run_frame({})
-- 調査用の初期状態を作る。
init_position()
-- 初期状態のステートをセーブ。
local state = savestate.object()
savestate.save(state)
for hero_y = 0, 12-1 do
for dst_x = 0, 6-1 do
for dst_y = 6, 12-1 do
local cost = calc_cost(hero_y, dst_x, dst_y)
wtr:write(string.format("%d\t%d\t%d\t%d\n", hero_y, dst_x, dst_y, cost))
savestate.load(state)
end
end
end
end
local function main()
emu.speedmode("maximum")
wtr = io.open("Flipull-cost.txt", "w")
work()
wtr:close()
emu.speedmode("normal")
end
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment