Created
June 5, 2023 04:46
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
フリップル (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