スーパーマリオブラザーズにReplay機能をつける
authorNariさんのスーパーマリオブラザース(http://d.hatena.ne.jp/authorNari/20080422/1208880928)すごいですね。Rubyを知っている人はソースをぜひ読んでみてください。美しい!シンプル!スーパーマリオってこんなにシンプルに記述できるんだ!って思いました。
さて、authorNariさんといえばGC。スーパーマリオブラザースはちょうどいいベンチマークになるんじゃないかなと思い、Replay機能をつけてみました。
使い方
1 main.rb中の$replayの代入文を
$replay = :record
にします
2 普通にプレーします。多分、反応が鈍くなってるんじゃないかと思います。
3 終わるときは必ずESCキーで終了してください。
4 main.rbのあるディレクトリにkey.logなるファイルが出来ています。これは、キー入力のログをMarshalで書き出したものです。
5 $replayの代入文を
$replay = :replay
にします
6 普通に起動するとあら不思議!
main.rbとgamestart.rbを書き換えています。diffを取ると面倒なので、ファイル全体を置きます。
main.rb
# # super nario brother # (c)2008, nari # http://d.hatena.ne.jp/authorNari/ # $: << File.join(File.dirname(__FILE__), 'mario') require 'sdl' require 'lib/fpstimer.rb' require 'lib/input.rb' require 'mario/scene' require 'mario/material' $test = false # :record プレーを記録します # :replay プレーを再生します # その他 普通にゲームをします $replay = :replay require 'gamestart' unless $test require 'test' if $test
gamestart.rb
# # super nario brother # (c)2008, nari # http://d.hatena.ne.jp/authorNari/ # require 'sdl' require 'lib/fpstimer' require 'lib/input' require 'mario/scene' require 'mario/material' require 'mario/life' KEYS = [:exit, :left, :right, :up, :down, :ok, :a, :b] class FakeInput def initialize File.open("key.log", "r") do |fp| @event = Marshal.load(fp) end @time = 0 end KEYS.each do |k| module_eval("def #{k};@event[@time]? @event[@time].include?(:#{k}):nil; end") end def poll @time += 1 end def [](key) if @event.has_key?(@time) then @event[@time].include?(key) else nil end end end class Input define_key SDL::Key::ESCAPE, :exit define_key SDL::Key::LEFT, :left define_key SDL::Key::RIGHT, :right define_key SDL::Key::UP, :up define_key SDL::Key::DOWN, :down define_key SDL::Key::RETURN, :ok define_key SDL::Key::A, :a define_key SDL::Key::B, :b end def map_init(screen) map = Scene::Builder.new{ mapping :title, Scene::Title.new { success :map_1 } mapping :map_1, Scene::FlowWorld.new{ success :title miss :title } }.scene_map map[:title].screen_build { background Material::BackGround.new_single_image(0, 0, SDL::Surface.load("mario/image/title.png")) } map[:map_1].screen_build { sky 12000 ground 12000, 600 block Material::ItemBox.new(800, 420) block Material::WeakBlock.new(1000, 420) block Material::ItemBox.new(1047, 420) block Material::WeakBlock.new(1094, 420) block Material::ItemBox.new(1141, 420) block Material::WeakBlock.new(1188, 420) block Material::ItemBox.new(1094, 240) block Material::Pipe.new(1370, 505) block Material::Pipe.new(1840, 460) block Material::Pipe.new(2280, 415) block Material::Pipe.new(2720, 415) block Material::WeakBlock.new(3730, 420) block Material::ItemBox.new(3777, 420) block Material::WeakBlock.new(3824, 420) block Material::WeakBlock.new(3871, 240) block Material::WeakBlock.new(3918, 240) block Material::WeakBlock.new(3965, 240) block Material::WeakBlock.new(4012, 240) block Material::WeakBlock.new(4059, 240) block Material::WeakBlock.new(4106, 240) block Material::WeakBlock.new(4153, 240) block Material::WeakBlock.new(4200, 240) block Material::WeakBlock.new(4400, 240) block Material::WeakBlock.new(4447, 240) block Material::WeakBlock.new(4494, 240) block Material::ItemBox.new(4541, 240) block Material::WeakBlock.new(4541, 420) block Material::WeakBlock.new(4900, 420) block Material::WeakBlock.new(4947, 420) block Material::ItemBox.new(5200, 420) block Material::ItemBox.new(5300, 420) block Material::ItemBox.new(5300, 240) block Material::ItemBox.new(5400, 420) block Material::WeakBlock.new(5600, 420) block Material::WeakBlock.new(5750, 240) block Material::WeakBlock.new(5797, 240) block Material::WeakBlock.new(5844, 240) block Material::WeakBlock.new(6050, 240) block Material::ItemBox.new(6097, 240) block Material::ItemBox.new(6144, 240) block Material::WeakBlock.new(6191, 240) block Material::WeakBlock.new(6097, 420) block Material::WeakBlock.new(6144, 420) block Material::Pipe.new(7900, 505) block Material::WeakBlock.new(8150, 420) block Material::WeakBlock.new(8197, 420) block Material::ItemBox.new(8244, 420) block Material::WeakBlock.new(8291, 420) block Material::Pipe.new(8700, 505) floor Material::Floor.new_fill_image(0, 600, 3270, 100, SDL::Surface.load("mario/image/floor_block.png")) floor Material::Floor.new_fill_image(3420, 600, 700, 100, SDL::Surface.load("mario/image/floor_block.png")) floor Material::Floor.new_fill_image(4300, 600, 3000, 100, SDL::Surface.load("mario/image/floor_block.png")) floor Material::Floor.new_fill_image(7450, 600, 3000, 100, SDL::Surface.load("mario/image/floor_block.png")) left_triangle_block Material::StrongBlock, 6350, 4 right_triangle_block Material::StrongBlock, 6650, 4 left_triangle_block Material::StrongBlock, 7082, 4, 5 right_triangle_block Material::StrongBlock, 7450, 4 left_triangle_block Material::StrongBlock, 8820, 8, 9 goal 9860 enemy Life::Kuribo.new(900, 550) enemy Life::Kuribo.new(2000, 550) enemy Life::Kuribo.new(2500, 550) enemy Life::Kuribo.new(2560, 550) enemy Life::Kuribo.new(3830, 200) enemy Life::Kuribo.new(3890, 200) enemy Life::Kuribo.new(4670, 550) enemy Life::Kuribo.new(4730, 550) enemy Life::NokoNoko.new(5100, 550) enemy Life::Kuribo.new(5430, 550) enemy Life::Kuribo.new(5490, 550) enemy Life::Kuribo.new(5750, 550) enemy Life::Kuribo.new(5810, 550) enemy Life::Kuribo.new(6100, 550) enemy Life::Kuribo.new(6160, 550) enemy Life::Kuribo.new(8300, 550) enemy Life::Kuribo.new(8360, 550) player Life::Mario.new(200, 550) } map end def main(buf) SDL.init(SDL::INIT_VIDEO|SDL::INIT_AUDIO|SDL::INIT_JOYSTICK) SDL::Mixer.open SDL::TTF.init if defined?(SDL::RELEASE_MODE) SDL::Mouse.hide screen = SDL.set_video_mode(Scene::SCREEN_WIDTH, Scene::SCREEN_HIGHT, 16, SDL::HWSURFACE|SDL::DOUBLEBUF|SDL::FULLSCREEN) else screen = SDL.set_video_mode(Scene::SCREEN_WIDTH, Scene::SCREEN_HIGHT, 16, SDL::HWSURFACE|SDL::DOUBLEBUF) end if $replay == :replay then input = FakeInput.new else input = Input.new end map = map_init(screen) scene = map[:title] timer = FPSTimerLight.new timer.reset tc = 0 loop do input.poll tc += 1 if $replay == :record then KEYS.each do |key| if input[key] then if buf[tc] == nil then buf[tc] = [] end buf[tc].push key end end end break if input[:exit] s_next = scene.act(input) scene.render(screen) scene = map[s_next].rebuild if s_next timer.wait_frame{ if defined?(SDL::RELEASE_MODE) screen.flip else screen.update_rect(0, 0, Scene::SCREEN_WIDTH, Scene::SCREEN_HIGHT) end } end end buf = {} main(buf) if $replay == :record then File.open("key.log", "w") do |fp| Marshal.dump(buf, fp) end end