2010年3月27日
ゲーム作成
最近、なぜかソフトウェアが作りたくてしょうがない。
思うに、若いころは仕事で毎日ロジカルなアルゴリズムを考えて、それをどうハードウェアとしてインプリするのか毎日頭を唸らせていたのだが、歳を取ってくると、そのような仕事は若い者に任せ、自分はどうでもいい?管理に追われる。
まぁ、プロジェクト管理にも相当ロジカルな思考は必要だが、同じロジカルでもちょっと分野と範囲が違う。
そういうこともあり、何かロジカルな思考をする場はないかと探し、さすがに個人レベルでのハードウェア設計は無理があるので、結局ソフトウェアを作成でそれを実現している気がするのである。
で、冒頭の画像であるが、子供向けに久々にゲームを作ってみた。
昔、大学生時代は、自分でたくさんのゲームを作っては、みんなと遊んでいた。
当時はPC-8801/9801の時代で、グラフィックもしょぼいのだが、アイデア勝負で幾つか面白いものを作った記憶がある。
Managed Direct X
実のところ、最近ごく一般のWindowsアプリばかり作っていたので、最近ゲームではどういった開発環境で行えばよく分からなくなっていた。
また、自分の好みはC++だが、最近、C#を好んで使っている。
簡単に調べた限りでは、C#でManaged DirectXという組み合わせでゲームが作れるらしいことが分かった。
さっそくDirectX SDKを落としてきて環境を揃え、Visual C#でゲーム作成に取り掛かったのだが、その作り、基本アルゴリズムが非常に懐かしかった。
というのも、最近のVisual Basic/C++/C#などの開発環境でのWindowsアプリ開発はオブジェクト指向が徹底しており、全てイベント・ドリブンでプログラミングする。つまり、何かボタンをクリックしたりして、なんらかのイベントが発生して初めてそれに対応する関数が呼び出されて動作する。単にプログラムを起動しただけだと、開発者がプログラムした部分は一切動作しないのである。
これは逆に言えば、ボタンを押されたことを検知したり、それに対応してボタンが凹むように見せたり、マウスがどこでどう動いたかは、全てWindowsプログラムのベースの部分で面倒を見てくれるということだ。通常開発者は、その辺の細かいところは一切関知せず、ボタンが押された後のことだけをプログラムすれば良いようになっている。
しかし、DirectXベースでゲームの開発となるとガラッと様相が変わる。
ウィンドウだけあげるから、あとは全部自分でしてね・・・という感じである。
これは、毎度毎度一定周期(通常1/60秒)ごとに、自分自身でボタンを含めて、ありとあらゆるものを描かなければならないのである。当然マウスがどこでどうなっているのかも自分で調べなければならない。
これが懐かしい。
昔のMS-DOSベースのゲームは全てこうだった。画面全体をポンと渡されて、後はご自由にという世界である。
XNA Framework
ところが、かなり完成に近づいたところで色々と調べているうちに、Manged DirectXはもうなくなり、XNA Frameworkに取って代わると書いてあるではないか!?
しまった、事前の調査が甘かった。
どうも、Microsoftはゲーム開発者向けにXNA FrameworkなるものをVisual C#向けに提供しており、簡単にWindows/X-Boxのゲーム開発ができるようになっているらしい。
今さら古いManaged DirectXなんかやっててもしょうがない。さっそくXNAへ乗り換えだ。
移植が面倒だなと思っていたが、同じVisual C#ベースであり元々DirectX 9cを容易に使えるようにしたものであるので、移植は1時間ほどであっけなく完了。
おぉ!Managed Direct Xの時より動きが滑らかだ!
さすがにゲーム専用の開発環境だ。
左:敵のミサイルが当たって負けたところ。右:山が一撃で吹き飛ぶ、超大型ミサイルのアイテムもあるのだ!
ってなわけで完成。
先に書いたとおり、ボタンから始まり何から何まで1/60秒ごとに書き直している。もう面倒なので、ボタンが押されても凹んだりしない。
ちゃんとネットワークで対戦できる(ま、一人で遊んでもよいが・・・)。
プログラム自身はたったの30KB程度しかないのだが、画面の通り、爆発映像にエライ気合を入れており、Particle Illusionで作った凝った爆発アニメセルが120枚分など、グラフィックデータやサウンドが3MB弱もあったりするのが、チトバランスが悪い。それにしても山のグラフィックをもう少し何とかすれば、かなり見てくれは良くなるはずだが・・・面倒。
それと、まだちょっとバグっているようだが、子供たちが遊ぶ分には問題がないので、もう直さない。
それにしても、XNAにもネットワーク機能が用意されているのだが、Windows Liveと密接に結びついてて、普通の人にとっては制限多すぎ。結局.NET Frameworkの普通のネットワーク・クラスを使用した。この辺、もう少し考えてほしいな>Microsoft!
というわけで、気が向いたら遊んでみてください。
ただし、DirectX 9cがインストールされ、グラフィックス・アダプタがPixcel Shader/Virtex Shader共に1.1のサポートが必要(5-6年前のSiS搭載の古いノートPCだと動かないかも)。
なおネットワークはUDPで、ポート60020を使っているので、Firewallソフトが動作している場合は該当ポートを除外してください。
DirectX ダウンロード (既にDirectXがインストールされている場合は不要)
XNA3.1 Runtime (必須)
ミサイルゲーム本体 (zip:2.8MB)
既知問題:ネットワークはUDPを採用しており、パケット・ロストは考慮していないので、なんらかの理由でロストすると、延々と相手のパケットを待つためそこで止まります。Firewallの警告が出たりすると確実にパケットが届いていないので、動かなくなります。その場合はゲームを再起動してください。
投稿者 abeshin : 23:13 | コメント (0)
2010年3月25日
泰我:小学校卒業
早いもので泰我も小学校卒業です。
左は入学式当日の写真。
ずっと小さいままかと思っていたけど、さすがに6年間の月日は長い。
卒業証書の授与を待っているところ。
無事に証書を授与されました。
さて、4月から中学生。
スポーツに、勉強に頑張れ!
投稿者 abeshin : 18:06 | コメント (0)
2010年3月15日
戦艦大和
部屋を片付けていたら、作りかけの戦艦大和が出てきた。
エッチングパーツを駆使してかなり精巧に作っているのだが、そのせいか?2002年から作り始めて未だに完成していない。もう埃だらけだ・・・・。
投稿者 abeshin : 11:58 | コメント (0)
2010年3月 2日
2010年うるう年問題
ありゃ!ソニーもやってしまいましたか。
「使用している時計機能が2010年をうるう年と認識していました。」
とのこと。
ソニー「も」と書いたのは、笑えない切実な理由から。
私のチームが全く同じミスを犯し、今年をうるう年と誤認識して時計を狂わせてしまうということをやらかしてしまったから。
私の方は事前に分かっていたので、パッチ適用を推進して何とか一昨日~昨日にかけてのXデーを乗り切ったところ。
2/29-3/1は徹夜してました。
ゲームなら一日くらい我慢していればよいが、もう、こっちはハラハラものだった。
うるう年
Webを見まわすと、ソニーは小学生でも出来る簡単な計算を何故間違えたんだ?という論調が多い。幾つか例外の年はあるが、4で割って余り0を求めるだけなのに馬鹿なのか?という流れだ。
念のために正確にうるう年の定義を書くと
- 西暦年が4で割り切れる年はうるう年
- ただし、西暦年が100で割り切れる年は平年
- ただし、西暦年が400で割り切れる年はやはりうるう年
となっている。
馬鹿なのか?という論調の人は単純に以下の式をひっぱり出してきて、何故これを間違えるんだ?と説く。
year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
いやいや、さすがにそんなミスではないだろう。
今回の話を総合すると、ソフトウェアというより、ハードウェア側のRTC(リアル・タイム・クロック)の問題にも思える。なぜなら、基本的に同じソフトウェアを積むPS3でも古い機種しか問題が発生しないのだという。仮にソフトウェアか、ファームウェアに問題があったとすると、新機種では既知問題として修正が入っていたことになる。
インターネットを介して頻繁にPS3のシステム・ソフトウェアの更新を行っているソニーが、さすがにこの問題を放置するとも考えづらい。
従い私は、これはハードウェアのRTCの問題ではないかとみている。
RTCでのミス
ここから、自分の設計チームの恥ずかしい話になる。
さきほど、ハードウェアのカレンダ/時計であるRTCの話をしたが、パソコンやサーバで使用されるRTCはレガシーIOと呼ばれ、IBM PC/AT互換機時代の仕様をずっと引きずっている。
WindowsもLinuxも、カーネルから直接このRTCにアクセスしているわけで、仕様を勝手に拡張してもいけないし、一切変えられない部分でもある。
従い、一般的なコンピュータのRTCには、今でも「年」は下二ケタ分の8bitしかない。
はて?下二けたで年を表して大騒ぎしたような・・・・。
そう、2000年問題。
2000年問題はソフトウェアが年の下二桁しか使っておらず、それに単純に1900を足して年を示していたために、00になった時に1900年と誤認識するという問題だ。
これは当時ソフトウェアのせいにされていたが、実のところ、ハードウェアのカレンダは未だに下二桁のままだったりする。レガシーIOなので、そう簡単に変えられないのだ。変えてしまうと正しく動作しないOSが続出し、もっと痛い目にあうという事だ。
従い、後は上で動くソフトウェア(OS)が適切に判断、処理しろということなのであろう。
さて、ハードウェア、特にLSIの開発の話に入る。
当時我々はIntel Xeonプロセッサ向けのノースブリッジを開発していたのだが、とある理由から、通常サウスブリッジの中にあるレガシーIOの大半をノースブリッジに埋め込む必要性が出てきた。
RTCもその一つで、カレンダ、時計として動作するために、もちろん、うるう年の処理も入れなければならない。
しかし、実際問題
Leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
こんな式に相当する剰余回路なんて組み込んだらそれこそ「馬鹿」呼ばわりされるのがオチである。
ソフトウェアでは1行で済む式だが、これを専用のHW回路で組むと膨大な回路が必要になる。従い、LSI設計者はこの式をそのまま回路にするなどということは、決してしないのである。
LSI設計者は常にシンプルな回路を目指して設計する。このとき、部下である設計者はこう考えたようだ。
100の剰余、400の剰余は無視しよう。なぜなら、既に2000年は過ぎており、この条件は100年後の話だ。そんな無駄な回路は入れない。
現実を考慮すればこの判断は正しい。
であれば、4の剰余だけ考えればよいが、剰余回路を組むのは馬鹿げている。どのみち年は下二桁の8bitしかないのだから、もっと単純化できるはずだ。
この判断も正しい。
しかし、彼が設計したのは以下のような回路であった。(HW記述言語のVerilog-HDL)
Leap = ~|Year[1:0];
要は、Year[7:0]の8bitのうち、さらに下2ビットだけを見て、00bだったらうるう年だと判断しているのである。NOR回路1個で出来る恐ろしく単純な回路だ。
さて、検証してみよう。
コンピュータ内部では数字を16進数で扱うので、年 - 16進数表記(hを付ける) - 2進数表記(bを付ける)と分かりやすいように書き改めてみた。
00年 - 00h - 0000 0000b - うるう年
01年 - 01h - 0000 0001b - 平年
02年 - 02h - 0000 0010b - 平年
03年 - 03h - 0000 0011b - 平年
04年 - 04h - 0000 0100b - うるう年
05年 - 05h - 0000 0101b - 平年
06年 - 06h - 0000 0110b - 平年
07年 - 07h - 0000 0111b - 平年
08年 - 08h - 0000 1000b - うるう年
09年 - 09h - 0000 1001b - 平年
10年 - 0Ah - 0000 1010b - 平年
11年 - 0Bh - 0000 1011b - 平年
12年 - 0Ch - 0000 1100b - うるう年
13年 - 0Dh - 0000 1101b - 平年
14年 - 0Eh - 0000 1110b - 平年
15年 - 0Fh - 0000 1111b - 平年
16年 - 10h - 0001 0000b - うるう年
17年 - 11h - 0001 0001b - 平年
18年 - 12h - 0001 0010b - 平年
19年 - 13h - 0001 0011b - 平年
20年 - 14h - 0001 0100b - うるう年
この辺で良いだろう。
これは正しそうに見える・・・・・・・
が、駄目なのだ。これだと2010年をうるう年と誤認識するのだ。
なぜならば、先ほど「コンピュータ内部では数値は全て16進数で扱う」と書いた。
唯一の例外がある。それがRTCの日時なのだ。
RTCに限り2進化10進表現(BCD)で扱わなければならないのだ!(*注1)
つまり、9 - 10 と進むとき、16進数的に書けば09h - 0Ahではなく、09h - 10hと大きくジャンプするのだ。
先ほどの表を正しく書き直すと以下になる。
00年 - 00h - 0000 0000b - うるう年
01年 - 01h - 0000 0001b - 平年
02年 - 02h - 0000 0010b - 平年
03年 - 03h - 0000 0011b - 平年
04年 - 04h - 0000 0100b - うるう年
05年 - 05h - 0000 0101b - 平年
06年 - 06h - 0000 0110b - 平年
07年 - 07h - 0000 0111b - 平年
08年 - 08h - 0000 1000b - うるう年
09年 - 09h - 0000 1001b - 平年
年 - 0Ah - 0000 1010b - BCDでは未使用
年 - 0Bh - 0000 1011b - BCDでは未使用
年 - 0Ch - 0000 1100b - BCDでは未使用
年 - 0Dh - 0000 1101b - BCDでは未使用
年 - 0Eh - 0000 1110b - BCDでは未使用
年 - 0Fh - 0000 1111b - BCDでは未使用
10年 - 10h - 0001 0000b - うるう年
11年 - 11h - 0001 0001b - 平年
12年 - 12h - 0001 0010b - 平年
13年 - 13h - 0001 0011b - 平年
14年 - 14h - 0001 0100b - うるう年
おぉ!
なんということだ、今年がうるう年になってしまうではないか?!
さらに、うるう年のはずの2012年が平年になり、平年の2014年に再びうるう年と誤判断・・・・・
PS3が同じミスをしているとは思えないが、ハードウェア開発では回路の単純化を考えるあまり、その使用形態(この場合BCD)をすっかり忘れる場合があるということだ。
ちなみに、BCDを忘れたのはうるう年の判断部分だけだ。それ以外のところは全てBCDで回路が組まれている。
うーん、魔がさしたのか?!
で、最もシンプルで正しい回路は以下だと思う。
Leap = ( (~Year[4] & ~Year[1] ) | (Year[4] & Year[1] ) ) & ~Year[0];
もちろん、現実を考慮して2100年のことは考えてもいない(ハードウェアなので、そこまで故障せずに動作しているとは到底思えない)。
IntelのRTC
ちなみに、デファクト・スタンダードであるIntelのサウスブリッジ(ICH)の仕様書は、IntelのWebサイトから誰でもダウンロードできるが、RTCに関しては次のような記述がある。
The leap year determination for adding a 29th day to February does not take into account the end-of-the-century exceptions. The logic simply assumes that all years divisible by 4 are leap years. According to the Royal Observatory Greenwich, years that are divisible by 100 are typically not leap years. In every fourth century (years divisible by 400, like 2000), the 100-year-exception is over-ridden and a leap-year occurs.
Note that the year 2100 will be the first time in which the current RTC implementation would incorrectly calculate the leap-year.
訳
「うるう年検出に関し、世紀末の例外は考慮していない。回路は、単純に4で割り切れる年をうるう年とみなす。グリニッジ天文台によれば、通常100で割り切れる年はうるう年ではない。4世紀ごとに(2000年のように400で割り切れる年)、100年例外は無効化され、うるう年となる。
2100年は、このRTCがうるう年の計算を間違える最初の年となることに注意してください。」
ま、そういうこと。繰り返すが、ハードウェアでまともに
Leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
なんてインプリをする馬鹿はいないのである。さらに、「year % 4」の部分も実際には、
Leap = ( (~Year[4] & ~Year[1] ) | (Year[4] & Year[1] ) ) & ~Year[0];
なっているだけである。
*注1:実のところ、RTCにはBCD以外にバイナリー・モードも存在している。もちろん、我々の設計したRTCは両方のモードで正しく動作するようになっている。さて、うるう年の判断を誤ったと書いたが、実はバイナリー・モードでは正しい挙動であり、Linuxでは正しく動作することが確かめられている。問題なのは、WindowsはRTCのバイナリー・モードをサポートしていない事だ。