Thanks Driven Life

日々是感謝

Emacs で動く NES エミュレータを作っている話

本記事は Emacs Advent Calendar 2018 の22日目の記事です。

成果物

まずは現時点 (12/22) での動作状況です。

https://github.com/gongo/emacs-nes

nestest.nes palette_pal.nes
f:id:gongoZ:20181222155809g:plain:w300 f:id:gongoZ:20181222160136j:plain:w300

使い方はいつか README の方に書きますが(いつか)、ざっと書くと:

  1. ソースコードもってくる
  2. nes*.el があるディレクトリに load-path を通す
  3. load-library nes
  4. M-x nes*.nes ファイルを選択

これで動くはずです。Byte Compile 推奨。

経緯

様々な言語で NES (= Nintendo Entertainment System) のエミュレータを実装する、というネタは昔からあります。私も何かしらの言語でやってみようかな? とボンヤリ考えたまま特に手をつけていませんでした。

そんな日々の中で参加した builderscon tokyo 2018 で、とある発表を見ました。

ファミコンエミュレータの創り方 - builderscon tokyo 2018

「何やっているのか良くわかんねえな?」と思いながらも「しかし面白いな!」という感想を得て、いよいよ自分でも何かで書いてみるかーという気持ちが再燃しました。

ちなみに「どの言語で実装するか?」ということですが、まだ誰も作っていなさそう*1Emacs Lisp でやってみることにしました。

記事の概要

  • 書いてあること
    • Emacs Lisp でそれっぽく動くところまでの話
  • 書いてないこと

動作するまでの話

時系列でふんわりと

1. まず ROM パーサの実装

.nes ファイルのフォーマット iNES の仕様どおりに読み込んでいきました。

2. CPU の実装

とにかく CPU が無いと始まらない! ということで CPU 周りに手をつけはじめました。

  1. 命令セットをとにかく Emacs Lisp に落としていく作業
  2. CPU 周りで必要な要素などを Emacs Lisp に落としていく作業

一通り落とし終わったので、いざ動くか試してみるぞ! と検証を開始しました。

検証に使用したのは

This is the best test to start with when getting a CPU emulator working for the first time.

と書かれるまでに便利な、 nestest.nes という「まずはこれ PASS したら次に進もうな」というテスト用 ROM です。

本来の nestest.nes は「コントローラ(ジョイスティック)でテストしたい CPU 命令を選んでテストを開始する」というものなのですが、この時点ではまだ画面やコントローラの実装できていません。そこで「コントローラの操作は無視して、ROM を読み込んで全ての CPU 命令に対するテストを実施する」というようなサンプルプログラムを書きました。

;; 当時のコードではなく、現状のコードで再現した形です
;; (この時はまだ ppu や interrupt は用意していなかったので)
(let ((cart (nes/cartridge-load "/path/to/nestest.nes"))
      (cpu (make-nes/cpu :interrupt (make-nes/interrupt) :ppu (make-nes/ppu))))
  (nes/cpu-set-working-ram cpu (make-vector #x0800 0))
  (nes/cpu-set-program-rom cpu (lexical-let ((cart cart))
                                 (lambda (addr)
                                   (nes/cartridge-read-from-prg-rom cart addr))))
  ;;
  ;; 画面から操作せず、テストだけを実行するために
  ;; nestest.txt の1行目の状況を開始地点(program counter)にセットしている
  ;;
  ;; memo: 長くなるのでここには書いていないが、 status register とかも
  ;;       1行目と合わせておく
  ;;
  (nes/cpu-reset cpu)
  (setf (nes/cpu-register->pc (nes/cpu->register cpu)) #xC000)

  (while t
    (let* ((r (nes/cpu->register cpu))
           (pc (nes/cpu-register->pc r))
           (opcode (nes/cpu-read cpu pc))
           (operand (nes/cpu-read cpu (1+ pc) :word))
           (inst (aref nes/instruction:MAP opcode))
           )
      (message "%04X %02X %s A:%02X X:%02X Y:%02X P:%02X SP:%02X"
               pc
               opcode
               (if (null operand) "    " (format "%4X" operand))
               (nes/cpu-register->acc r)
               (nes/cpu-register->idx-x r)
               (nes/cpu-register->idx-y r)
               (logior (lsh (if (nes/cpu-register->sr-negative r)  1 0) 7)
                       (lsh (if (nes/cpu-register->sr-overflow r)  1 0) 6)
                       (lsh (if (nes/cpu-register->sr-reserved r)  1 0) 5)
                       (lsh (if (nes/cpu-register->sr-break r)     1 0) 4)
                       (lsh (if (nes/cpu-register->sr-decimal r)   1 0) 3)
                       (lsh (if (nes/cpu-register->sr-interrupt r) 1 0) 2)
                       (lsh (if (nes/cpu-register->sr-zero r)      1 0) 1)
                       (lsh (if (nes/cpu-register->sr-carry r)     1 0) 0))
               (nes/cpu-register->sp r)
               )
      (nes/cpu-step cpu)
      )))

このプログラムを実行すると、下記のような結果が *Message* バッファに書き出されます:

C000 4C C5F5 A:00 X:00 Y:00 P:24 SP:FD
C5F5 A2 8600 A:00 X:00 Y:00 P:24 SP:FD
C5F7 86 8600 A:00 X:00 Y:00 P:26 SP:FD
C5F9 86 8610 A:00 X:00 Y:00 P:26 SP:FD
C5FB 86 2011 A:00 X:00 Y:00 P:26 SP:FD
C5FD 20 C72D A:00 X:00 Y:00 P:26 SP:FD
C72D EA B038 A:00 X:00 Y:00 P:26 SP:FB
C72E 38  4B0 A:00 X:00 Y:00 P:26 SP:FB
C72F B0 A204 A:00 X:00 Y:00 P:27 SP:FB
C735 EA B018 A:00 X:00 Y:00 P:27 SP:FB
C736 18  3B0 A:00 X:00 Y:00 P:27 SP:FB
C737 B0 4C03 A:00 X:00 Y:00 P:26 SP:FB
C739 4C C740 A:00 X:00 Y:00 P:26 SP:FB
C740 EA 9038 A:00 X:00 Y:00 P:26 SP:FB
C741 38  390 A:00 X:00 Y:00 P:26 SP:FB
C742 90 4C03 A:00 X:00 Y:00 P:27 SP:FB
C744 4C C74B A:00 X:00 Y:00 P:27 SP:FB
C74B EA 9018 A:00 X:00 Y:00 P:27 SP:FB
C74C 18  490 A:00 X:00 Y:00 P:27 SP:FB
...
...

あとはこの結果と、「 nestest.nespc=C000 以降の動きがこうなっていれば正解やで」が記載されている nestest.log を比較して、PC や各レジスタの値が同じまま最後まで到達したら CPU はひとまず実装終わり!

3. PPU の実装 (白黒・背景画像だけ)

ついに見える化を進めます。

ところで、Emacs で「指定した位置に、この色でピクセルを描画する」という実装を行う場合、様々な方法があると思います。 今回は 18日目の記事でも紹介されていた gamegrid.el を使用しました。 私自身も2年前に gamegrid.el で一ネタ 作っており、とりあえずこれでいいかーという気持ちで選定しました

nes-ppu.el

PPU の実装、ものすごく大変でした。 CPU と違って「目チェックしては修正して〜」の繰り返し*2。そんな PPU の実装を開始して一週間後、ついに…

ここまで来るとモチベーションも高まり、あとは勢いのまま突き進みました。

画面が使えるようになることで、他のテスト用ROMでも試せるようになり、更に便利に。

4. コントローラの実装

コントローラ操作はもちろんキー入力。キー入力と云えば Emacs の独壇場。特に問題もなく実装できました(2Pコンはまだ未実装なのですが)。

nes-keypad.el

コントローラも実装できたことで、以前テキスト比較でのみ使用していた nestest.nes も、ついに画面で結果を見ることに成功。

5. PPU の実装 (色有り・背景画像だけ)

そろそろ白黒から脱却する時期になりました。一気に実装

📝 当時は色テーブルが間違っており、色が変でした。

6. PPU の実装(スプライト画像)

背景画像とはまた別の計算が絡んできて一層混乱しました。

とはいえ、あとはひたすら微調整作業だったので、無事にそこそこ動作するまでもってこれました。終わり。

まだ実装できていないところ

1. スプライトの処理がまだ甘い

まだズレがある

f:id:gongoZ:20181222184432p:plain:w300

2. スクロール処理がおかしい

f:id:gongoZ:20181222155710g:plain:w300

この ROM は ギコ猫でもわかるファミコンプログラミング にある、背景画像スクロールの例題です。何かこう、移動がぎこちない。素直に左に進んで欲しい

3. 遅い

今のところ体感で 3 FPS ぐらいです。なんとか ギコ猫 がギリギリ動かせるぐらい(十字キーの左を押しっぱなしでこれぐらい):

f:id:gongoZ:20181222183547g:plain:w300

4. BGM

さすがに Emacs 単体では無理かもしれない

5. 縦横のサイズを調整するのが少し面倒くさい

gamegrid.el に限らず、Emacs で各ポジションに色つけたりする時は face や font を弄ると思います。 つまり、その位置にある文字の width と height が、そのまま NES エミュレータ側からみる「1ピクセルの width と height 」になります。

これの何が面倒くさいのかというと、基本的に使っているフォントは「縦のサイズ = 横*2」みたいなことが多いので:

f:id:gongoZ:20181222181533p:plain:w300

こんな感じで縦長になってしまいます( iTerm で 256 x 149 の様子。256 x 240 が領域なので、下の方が見えない)

仕方ないので、今のところ端末設定の「行間隔」に相当する設定を弄ってそれっぽく見えるように調整*3しています。(冒頭の画像とかはそれ)

f:id:gongoZ:20181222182238p:plain:w300

  • Emacs の設定だけでは上記のような行間隔設定ができない*4
  • おそらくそれ用(height == width)のフォントを用意すればいけるのかな…?

まとめ

深い解説のない、ただの開発日記みたいになりましたが如何でしたでしょうか。 取り組み始めて約2ヶ月(毎日やっていたわけではないですが)かかり、なんとかモノになったので良かったです。

難しいといえば難しいですが、やはり目に見えて良くなっていくのは開発してて面白いですね。 もう少しマシなゲームが動かせるようになったら、またどこかで報告します。*5

それでは、今年もお疲れ様でした。良い御年を

参考

*1:Lisp」という括りではいくつかありました https://news.ycombinator.com/item?id=8666976

*2:特にパターンテーブルの理解に時間をかけました。つまりどういうことやねん状態

*3:Emacs に限らず、端末に描画するタイプはここらへん調整が必要になりそう

*4:見つけきれてないだけかも。あると嬉しい

*5:吸い出し機買わないと…

年末調整Tシャツを作っています

最近は年末調整のことばかりを考えており、とはいえ実際に記入するのはもう少し先の話です。 息抜きも必要、しかしまったく離れるわけにもいかない。そんな気持ちでTシャツを SUZURI で販売しています。

各書類の詳しい説明は こちら(国税庁) から確認できます。

一例:

追記

買う人いるとは思わなかったんですよね…

Nokia Steel HR を1ヶ月使ってみた記録

ただの記録です

買ったもの

Nokia Steel HR 40mm モデルです

Nokia Steel HR - Withings ウォッチの各モデルの違いはどのようなものですか?

バッテリーについて

  • 前提

    • 継続心拍数モードは使っていない
  • 結果

    25日無充電でもまだ余裕ある

    日付 電池残量
    1/21 85% (購入時)
    2/15 8%

満タンから始めると1ヶ月ぐらいは持ちそう

Nokia Steel HR™ は、一回の充電で最大25日間使用可能ですが、使用可能な日数は継続心拍数モードの使用頻度により変動します。トレーニング中だけでなく、常時継続心拍数モードを使用すると、バッテリーの消耗がより増加します。

Nokia Steel HR - バッテリーの電池寿命はどれくらいですか?

着け心地

普段時計などはつけない(違和感気になるタイプ)が、着けた初日から問題なく睡眠に入れるぐらい

睡眠の記録について

だいたい23時あたりからは動き少ないと寝ている判定をされるっぽい

まとめ

SmartHR に入社していました

メリークリスマス(昨日)。皆様いかがお過ごしでしょうか。

去年は Emacs で雪を降らせていた 私ですが、今年も無事平和に過ごしております。

そんなこんなで2017年もそろそろ終わり。 今年は私の人生としては(かなりの)変化の年でもあったため、一年を軽く振り返りつつ、近況をご報告致します。

概要

2017年9月から SmartHR で働いています。職種はサーバーサイドエンジニア。 前職は PHP でしたが、今は Ruby on Rails をがんばっています。

以降、会社の説明はあまり無いので、そちらに興味ある方はこちらへお願いします

https://smarthr.co.jp/recruit

入社の経緯

2009年3月に大学院を卒業し、同年4月より沖縄の SIer (つまり前職)に新卒で入社。以降 PHP をやったりやらなかったりで気づけば2017年。 特に不満もなく(悪く言えばだらだらと過ごし)、また新しい年度が始まるんだなーという4月を迎えた頃

「あ、転職するか」

というモチベーションが一気に盛り上がりました*1。 この勢いを止めないようにしようと、次の職場を決める前にいろいろチーム内で引き継ぎなどをしつつ、スケジュールもだいたい決まった6月を過ぎたあたりで以下をツイートしました。

ありがたいことにいくつか声をかけていただきまして、良きタイミングでいろんな会社に遊びにいったり Skype などでお話を伺ったり。 いろいろありましたが最終的に現在の SmartHR でお世話になることが決定しました。

入社してどんな感じか

楽しいです。

「労務」という(私が思う)通常のエンジニアであれば直接接点のない領域に対してハックしていくという業務がとても新鮮で、良きです。 キャリアパスとしても、HR Tech という分野の最前線に立っていろいろ触れられるというのも強みだと思い、そこもモチベーションに繋がっています。

エンジニアチーム(に限らず他のチームも)凄い人ばかりで、日々勉強といった感じです。がんばりたい。

人見知りの私が慣れ親しんだ環境から離れて大丈夫か?と心配の方も居らっしゃると思います。 大丈夫、僕は元気ですということを示す、会社の人たちとの心温まるやりとりです。

業務以外

  • 人生初めての一人暮らし & 引越し(沖縄 → 東京) 。引越し前後はさすがにドタバタしていましたが、今はまったりしています。
  • 東京の勉強会やイベントにも顔を出しやすくなったけど、いざ現地にくるとなかなか行かないことがわかる
  • 前職は「夏はかりゆしウェア、冬はスーツ」という感じでしたが今は私服なので楽
    • スーツ時代も、服考えなくて良いというのはありましたが、ネクタイがつらかった

おわり

おまけ

初転職してあらためて知ったのですが、前職で一緒だったチームメンバーも、やっぱりみんな凄かったんだというのが再認識できた。そこが一番嬉しいかも。

*1:本当に何の不満も理由も無く、周りからも「急にどうした」みたいなリアクションをいただきました

builderscon tokyo 2017 に参加しました

完全に遅刻した


というわけで builderscon tokyo 2017 に参加しました。 去年は参加できなかったのですが、感想ブログとかを見て面白そうだなー今年は行きたいなーみたいな気分だったので良かった。

以下、雑に参加したセッションの感想

前夜祭(8/3)

撤退技術スペシャル祭り。うんうんそれも人生だよね

1日目(8/4)

2日目(8/5)


というわけ前夜祭もあわせて二日とちょっと。お疲れ様でした。楽しかったです。

個人的には朝食が美味しかったのも良かった。朝食最高!!

来年もあるなら是非参加したい

エクストリーム二度寝

説明しよう!!エクストリーム二度寝とは「朝、寿司を食ったあとに二度寝する」ことだ!!

実施例:

  1. 4:55 起床。諸々外出準備
  2. 5:30 築地市場大和寿司 到着。開店時間だが既に10人ぐらい並んでいて且つ店内は満席
  3. 5:55 店に入る
  4. 5:56 俺「おまかせで!」
  5. 6:05 職人「セットはこれで終わり」俺「おすすめ追加」「本マグロくってけ」「ひゅー♪」
  6. 6:10 店を出る
  7. 6:30 寝床に戻り、二度寝を開始する
  8. 8:30 起床

エクストリーム二度寝の欠点は起床後の出社欲が著しく低下することだが、本日僕はお休みです。

Current status

Wataru MIYAGUNIさん(@gongo)がシェアした投稿 -

デレステイベント「モーレツ★世直しギルティ!」への参加と振り返り

まずはイベント参加者の皆様、お疲れ様でした。 今回は私が推している堀裕子さんがランキング上位報酬ということで、「アイドルマスター シンデレラガールズ スターライトステージ」(以下 デレステ) をプレイ開始してから初めて 「2000位以内に入ってスターランク15のユッコ*1を手に入れるぞ!!」 と気合いを入れたイベント参加となりました。せっかくですので、イベント参加記録みたいなのを残そうと思い、本記事を作成しております。

デレステが配信開始されてから1年半以上経過しているので、今更「イベントの効率の良い進め方」みたいなのは書きませんが、 今後また同じように走るイベントが出てくるかもしれません。その時のために振り返りをし、反省点や次に繋げようということで、 どちらかというと個人的な備忘録みたいなものになります。あらかじめご了承ください。

イベント開始前

前々から「そろそろセクシーギルティのイベントきてもいいのでは?」と思っていた矢先のこの告知。 しかもゲーム内の予告音声でユッコが1番目ということで、「これはランキング上位報酬…つまり走るってことだな?」となりました。 この時点で、スタミナドリンクの在庫を確認しました。

正確には、プレゼントボックス内の最古スタドリは 2015年12月 でした。約17ヶ月分の無期限スタドリ。これなら大丈夫だろうとまずは一安心しました。

イベント期間中

突然ですが、イベント初日から最終日までの、スタドリ使用回数や取得ポイント数などをまとめた表がこちらです。

日付 イベントpt スタドリ10 スタドリ20 スタドリ30 スタドリMAX(84) 合計(スタミナ)
5/19 2678 10 10 10 0 600
5/20 5156 10 12 7 0 550
5/21 14113 10 14 10 11 1604
5/22 16237 0 1 0 0 20
5/23 20307 0 0 0 0 0
5/24 42003 10 11 10 0 620
5/25 51105 10 10 10 0 600
5/26 52480 10 12 10 0 640
5/27 66005 20 21 19 0 1190
5/28 86199 40 30 18 0 1540
合計 120 121 94 11 7364
  • 自然回復したものは含めていません
  • スタドリMAXは、現在の PLv が 184 で、その時点でのスタミナ最大値が84なので、それに併せています

1. 5/19 〜 5/20

この二日間は、スタミナ等倍で進めていました。理由としては「スタイナ消費量に関係なく、1回プレイ毎にエンブレムとは別で pt が 50 貰えるので、2倍でやるよりは経験値効率がいいだろう」という考えでした

2. 5/21 〜 5/23

この日からスタミナ2倍でプレイ。たしかに等倍プレイは経験値効率は良いのですが、時間とリアル体力が削られるので、ちんたらしてられねえ!となりました。

3. 5/24 〜 5/25

後半戦がスタートし、イベント曲が4倍プレイできるようになったので溜まったエンブレムを一気に消費。だいぶポイントが増えました

4. 5/26

慢心の日

この時点で1000位台だったので「まあ大丈夫だろう」と少しだけやってイベント曲もやらずに別のソシャゲをしていました

5. 5/27

ここで2000位ちょいになっていたが、エンブレムも5000個ぐらいあって、まあ大丈夫だろうとなって追い込みをかけず

6. 5/28(最終日)

ガーディアンズオブギャラクシー2を見にいったり夕方までだらだらしており、 「よっしゃあと2時間がんばるぜ!」と19時から再開した時にはかなりボーダーがやばい状態になっていました。前二日間のツケが一気にきた気分。

2時間ずっと、楽曲時間が2番目に短いらしい「キミとボクのミライ」をひたすらプレイ*2し、エンブレム600個あつまったらイベント曲をやる、というフローを組みました。 最後は20時59分40秒あたりで最後のイベント曲を開始し、どうにかエンブレムを使いきって終了。めっちゃ疲れた。

イベント終了後

スタドリ在庫

プレゼントボックス内に残っていた最古は 2016年11月 のスタドリでした。全部使い切るといいつつ、結構残りました。

PLv や「堀裕子」ファン数の推移

  • イベント開始前
    • PLv 180
    • ファン数 256万7699人
  • イベント終了後
    • PLv 184
    • ファン数 365万2376人

イベント中は、ほとんど「堀裕子4人 + 回復役1人 *3」という編成でプレイしていました

(If..) もしスタドリの在庫が無く、スタミナ回復を石を割るだけでイベントを走ろうとした場合

今回のスタドリによる回復量は、上表のとおり 7364 です。これを、例えばスタミナ最大値84のプレイヤーが石を割ったとすると

7364 / 84 = 約 88 回、石を割る

つまり 88*50 = 石4400 個 を割ることで2000位以内に入れる、ということでしょうか。 ショップのスタージュエル購入にある「スタージュエル4200個」が5000円なので、 5000円で推しのスターランク15のアイドルが手に入る と考えれば、如何でしょうか。

まとめ

ふりかえりの総括をすると

  • 最初から2倍プレイで問題なさそう
  • スタドリ在庫そのものには余裕があったが、肝心のプレイヤーの体力の問題が強い
  • 1日1200ぐらいスタミナ回復するぐらいのペースでいけば2000位以内はそこそこいける
  • 最終日はボーダーが2万増える(9999個までエンブレムを溜めた状態で全部使うと約2万ポイント取れるので、そういうことかも)

今回、初めて2000位以内がんばるぞ!とやったんですが、それを体験することで、上位3人のバケモノっぷりがより身に染みましたね。なんなんですかね…

とりあえずイベント曲もコミュも楽しかったので満足!!

おまけ

今回のポイント数とかをまとめた表は Emacs の org-mode で管理していました

https://gist.github.com/gongo/f2f9a9ca043895de5d460165cf7a3577

Raw を開くと TBLFM とか見れるので、もしお使いになる方が居らっしゃれば御自由にどうぞ

*1:正確には、ポイント報酬やランキング報酬で複数枚手に入るカードを全て掛け合せるとスターランクが15になる

*2:1番短いのは Snow Wings みたいですが、実は苦手で事故が怖かったので、油断してもどうにかなるキミとボクのミライにしました

*3:ライフ回復もってるガシャ版のSR裕子は未所持…