もうすぐ引越しです。頑張って部屋を片付けないと・・・

しばらくドタバタすると思し、これ以上作りこむモチベーションもないので、ここまで作ったものをあげておこうと思いました。

ダウンロード

pynes-0-0-1.zip

試し方

インストールはダウンロードしたzipファイルを展開するだけです。

必要なライブラリは

です。 両方とも easy_install psyco 、`easy_install pygame` でインストールできたはずです。

roms/ 以下に最低1つ以上ロムファイルを置いてください。現状、マッパーに対応してませんので、マッパー0のしか動く可能性はありません。現在動作を確認してるのは、前回あげさせていただいた TkShoot くらいです。市販のはほとんど動かないんじゃないでしょうか。

一応参考までにあげておくと、動く可能性があるのはGolf,DonkeyKongなどです。

bin/pynesi.py が起動用スクリプトです。コマンドラインから起動してください。起動したら、romファイルを番号で選択してください。

キーバーインドは

  • 十字キー : カーソル
  • スタート : テンキーの0
  • セレクト : テンキーのEnter
  • A : テンキーの3
  • B : テンキーの2

になってます。キーバーインドを代えたい方は src/pynes/pad.py を適当に書き換えてください。

self.keymap1 = {
  K_UP : NES_PAD_UP,
  K_DOWN : NES_PAD_DOWN,
  K_LEFT : NES_PAD_LEFT,
  K_RIGHT : NES_PAD_RIGHT,
  K_KP0 : NES_PAD_START,
  K_KP_ENTER : NES_PAD_SELECT,
  K_KP2 : NES_PAD_B,
  K_KP3 : NES_PAD_A
}

ここです。

とにかく、めちゃくちゃ遅いので、固まったと思ってもしばらくすると画面がちゃんと切り替わったりします。

よもや話

かなり適当です。前回(PythonによるNESエミュレータ開発4)から変わってません。マッパーっぽいのが用意してありますが、これはダミーです。他のエミュのソースを参考に必要そうな部分に適当にいれただけです。

一応、速度を重視しているものの、わかりやすく書いてるつもりなんで、Pythonが分かっていてかつ、エミュレータの基本的な構造が知りたい人には参考になるかもしれません。


こちら にも書いたとおり、Windowsではコマンドライン型ランチャーのcraftlaunchとexplorer.exeを連携させて使ってます。

最前面のディレクトリ(アクティブなディレクトリ)に対して、キーボードで、Linuxと同じ感覚でmkdirとかrmとかしたいなー、ってのを実現してみました。

今まではC言語で作った自前のライブラリで処理していたんですが、COMを使ったものに書き直しました。同様の目的にはAHKが使えるんですが、やっぱり使い慣れた言語でいろいろコマンドを作りたかったのでCOM+Javascriptにしてみました。もちろん、COMなのでRubyでもPerlでもPythonでも同様の実装が作れます。

craftlaunchを前提にしてますが、コマンドライン型のランチャーならなんでも適応できる方法だと思います。explorerを使っていてもキーボードだけでmkdir,rm,mvなんかが実行できちゃって、非常に便利です。

簡単なコマンドラインランチャーもどきも同梱してますので、現在コマンドライン型ランチャーをいれていない人でも試せます。

以下readmeからの転載です。

利用方法

explorehelper.wsf,explorehelper.js,getadir.exeは同じフォルダになければ なりません。

explorehelper.wsf [command] [args]

のように実行します。craftlaunchの場合はexplorhelper.wsfをコマンド登録し、 ctrl-eなどにショートカット登録しておくと良いでしょう。すると、 mkdirと入力→ctrl+eで最前面のフォルダに、フォルダを作成できます。

それぞれのファイルについて

simple_launcher.exe

コマンドライン型ランチャーを導入していない方でも試せるように、AHKで作った 簡単なランチャーを同梱してあります。ただしこのランチャーは

  • ホットキー(有効化)はalt+space
  • コマンド実行はctrl+e

に固定されています。つまり

「起動」→(「alt+space」でアクティブに)→「command入力」→「ctrl+e」で実行

という操作になります。気にいった人はちゃんとしたコマンドライン型ランチャー の導入をオススメします。

getahwnd.exe

最前面のExplorer.exeのウインドウハンドルを出力するだけのプログラムです。

explorehelper.js

JscriptによるCOMに対するヘルパーです。

explorehelper.wsf

Jscriptによるカスタムコマンドが定義されたファイルです。このファイルを編集 することによってcommandが自由に追加できます。

ビルトインコマンド

  • mv
  • mkdir
  • cmd
  • touch
  • rm
  • chvm : 表示モードを変更します。
  • ls : -x, -s, -tオプションのみ受け付けます。つまりフォルダ内をソートします。
  • verbs : 右クリックメニューを出します。shift+F10と同じ効果です。

説明の無いものはUnixのそれから類推してください。

カスタムコマンドの追加について

explorehelper.wsfを編集することによってcommandが自由に追加できます。

var procs = {
  mv : function() {
    var path = get_dir_and_focused_item_path();
    var name = InputBox("新しい名前を入力してください。", path.item._base_name());
    if(name) get_dir_and_focused_item().item.Name = name;
  }._item_proc()._auto_win_activate(),

  mkdir : function() {
    var path = get_dir_and_focused_item_path();
    var name = InputBox("フォルダ名を入力してください。");
    if(name) fs().GetFolder(path.win).SubFolders.Add(name);
  }._auto_win_activate(),
  .
  .
  .
  .

commandは上記のように、procオブジェクトのプロパティとして定義されます。その際、 command名がキー、値は引数なしの関数になります。キーに大文字は使用できません。

commandを定義する際にはexplorehelper.jsで定義されたヘルパを使用することができます。 カスタムcommandを定義しようとする人は当然、JScript(Javascript)が理解できる人 だと思いますので、ヘルパの詳細はexplorehelper.jsを見てください。ここではリスト のみ紹介します。

  • InputBox : 入力フォームを表示します。
  • MessageBox : メッセージを表示します。
  • Confirm : 確認フォームを表示します。
  • shell : Shell.Applicationオブジェクト返します。
  • wscript_shell : WScript.Shellオブジェクトを返します。
  • fs : Scripting.FileSystemObjectを返します。
  • win_activate : ディレクトリをアクティブにします。SendKeysする前に実行します。
  • get_dir_and_focused_item : アクティブなディレクトリと選択されているアイテムを返します。
  • get_dir_and_focused_item_lst : アクティブなディレクトリと選択されているアイテムのリストを返します。
  • get_dir_and_focused_item_path : アクティブなディレクトリと選択されているアイテムのパスを返します。
  • get_dir_and_focused_item_path_lst : アクティブなディレクトリと選択されているアイテムのパスのリストを返します。
  • $wsh_args : argsを格納した配列です。
  • Function.prototype._item_proc : アイテムに対する手続きであることを宣言します。アイテムを選択していない場合、処理が実行されなくなります。

  • Function.prototype._auto_win_activate : 手続きを終了後、ウインドウをアクティブにします。      


こんな感じです。COMなら大概なんでもできるので、便利です。しかもJScriptとexeなのでUSBメモリなどにいれて持ち運べますので、俺ポータビリティもそこそこあります。

また、いかにAHKスクリプトが簡単でも、普段使い慣れた言語でかけるほうが落ち着きます。AHKなんかをみてると、完全なDSLというのは俺的に扱うのがめんどくさくて(覚える熱意があればいいんだろうけど、趣味で使うものにそれほどの情熱がでるかというと・・・)なじみにくいようだ。Rakeみたいな言語内DSLがいいよね、やっぱ。ああ、2007年はRubyの年になるんでしょうかねえ。


image

パッド入力部分を書いたので、動くゲームも出てきました。といってもまだほとんどのゲームが動かないんですけど。画面は ブルジョアソフトウェア研究所 さんのTkShoot 1.00が動作している様子です。

さて、ここまできたので基本的にはこの企画も終了かなー、という感じがします。目的はPythonのパフォーマンスについて知ることだったので。

作成の過程でかなりPythonのパフォーマンス関連について勉強ができてよかったと思います。

速度

サウンドは作っていないので、それを除くと1frameだいたい0.4秒弱くらいで動きます(もちろんpsycoを導入して)。変にベタ書きしたりはしていません。わりとメソッドはちゃんと分割しています。ただ、LDA $ssss(absolute addressingのLDA)だけ、実行回数が多いので完全にベタ書きしました。

んで感じたのは

  • プリプロセッサでマクロを使えばそこそこ実用的な速度になりえるのではないか
  • bytecodehacks でインライン化すれば結構いけるんじゃないか。ただし、bytecodehacksがオブジェクトのメソッドには対応していないので、classを使わずに書くことになる。
  • 部分的にCで拡張モジュールを書けば、わりといけそう

ってことでしょうか。

まぁただ、JAVAとかC系全く分かりません><っていう人以外は、素直にC系かJAVAで書いたほうがいいと思います。

高速化のためにオブジェクトのプロパティをローカルにだしたり

read = self.memory.read
write = self.memory.write
# .
# .
  for i in xrange(foo):
    code = read(addr)
    # .
    # .
    # .

するので、無駄に行数も増えますし、ぶっちゃけ読みにくいです。それでも普段使い慣れてるLLで書けるってのは大きな利点だとは思います。

10年くらいして、マシンがもっと速くなることに期待しましょう、ということで(笑

その他雑感

現時点でも単純なゲームくらいなら、そこそこ動くのでコンピューターの仕組みの基礎を学ぶには、もしかしたらLLでエミュレータってのはいいかも。当然ですけど、デバッグの段階ではアセンブリ言語を書くことになりますし、そのアセンブリ言語の内容も完全に自分で処理するわけですから、単に本で読むよりは格段CPUやメモリについて詳しくなれると思います。ただ、CPUの仕組みを勉強しながら書くのはきついものがあるかもしれませんが・・・

他の利点としては「俺エミュレータ書いたんだぜ」と自慢できる(笑)、自分の書いたエミュレータでゲームが動くと結構感動できる、というくらいでしょうか。


というわけで、とりあえず動くようになりました。今のところソースをアップする気はないです。(してもほとんどのゲームは動かないし意味無い)

確実にいないと思いますが、もし、「俺もLLでエミュレータ書いてみるんだぜ!だからお前のしょーもないソースも参考にしてやるから見せるんだぜ!ついでにエミュってどうやって作るのか教えるんだぜ!」というような方や「おめーソースがないのに信用できるか!」という方がおられましたら、この記事のコメント欄やはてブなんかのコメントに、「うp」とか書いてください。適当にソースまとめてうpして、それをネタに簡単なエミュの書き方でも記事にします(笑

まぁとにかく書いてて楽しかったです。チャレンジ精神旺盛な方、そして時間があまっている方は是非LLでエミュにチャレンジしてみてください。

#追記  アップしました。コチラの記事へドウゾ。

PythonによるNESエミュレータ開発5