子ども時代のトラウマ難易度ゲームMight and Magic Book Oneに、大人になってから挑みましたが、変わらず撃沈しました。やっぱり難しいです。でもエミュレータなら、Lv.1で最速クリアする動画を作れるんです。いわゆるTAS(Tool Assisted Speed-run)動画です。
TASするにあたっては、定番のBizHawkというエミュレータを使います。ROMをロードしてTool - TAStudioを選ぶと、フレーム単位でキー入力を選べる画面が出現します。
操作に自信があるならRecording modeを使って自分の操作を記録し、あとでファインチューンすると良いでしょう。私は操作にあまり自信がなかったので、1フレームずつチマチマとマウスで入力しました。
とりあえずエンディングまで辿り着いたので、YouTubeにもアップロード([TAS] NES Might and Magic Book One (J) 23:30 - YouTube)しました。YouTubeのアップロード側機能は初めて使いましたが、とても使いやすいですね。
TASとしてのクオリティはあまり良くないです。攻略ルートがかなり適当で、これより早くクリアできる動画は簡単に作れると思います。容易に改善できそうなところは、
5人パーティーだとオーラのクエストが長いし、戦闘で行動できない人が出やすく無駄が多いので避けたいですが、4人パーティーだと「ようせいx 3」が出ないので、妥協しました。
あとMight and Magicの大きな特徴として、敵から逃げるとMAPの特定の位置に必ず戻される仕様があります。わざとエンカウントし逃げれば、歩くよりも早く移動ができる可能性があります。TASではぜひとも活用すべき事項ですが、エンカウントのルールがわからず、エンカウント確率もかなり低いため、総当たりだと全然エンカウントしません。これは厳しい。ギブアップ。
とまあ、色々諦めたしょぼいTAS動画を作っただけで非常に疲れました……。世の中のハイレベルTAS動画の作者さんは凄いですわ〜。
Might and Magicファミコン版は日本語版と英語版があります。このうち英語版のTASは既に存在していて、クリアタイムがたったの8分([TAS] NES Might and Magic by Dammit in 08:07.72 - YouTube)です!超早ですね。
英語版はアイテムを別のアイテムに化けさせるバグがあって、そのバグを突くとゴミから終盤のクエストでもらえるはずのアイテムを錬成でき、ほぼ全てのクエストを無視してラストダンジョンに挑めます。
日本語版ではバグが修正されているほか、クエストの仕様も違うため、少なくとも同じ攻略法ではクリアできません。
Nintendo Switch Onlineに加入すると、ファミコンやスーパーファミコンのソフトでも遊べるので、子供の時に挫折したゲーム(最近だとファミコンウォーズ、スーパーピクロス)をやっています。グラフィクスは近年のゲームと比べるまでもなくショボいですけど、今も名作は名作です。面白いですね。
子どもの時に挫折したゲームはいくつもありますが、ナンバーワンがMight and Magic Book One : the Secret of the Inner Sanctumです。この時代に流行ってたWizardryみたいな3D風の迷路を歩いていく海外製RPGです。難易度が異常に高くて有名で、小学生の私はLv.2すら拝むことなく諦めました。
この手のSwitchには収録されていないソフトも遊びたいなあ?と思って、ニューファミコンを物色していたのですが、結構でかくて邪魔そうだし、中古の割に高いです。今でも人気なのか……侮ってましたね。
ですが、我々にはPCとエミュレータがあるじゃないですか。幸いなことに、ファミコンソフトそのものはそんなに高くないので、ROMダンパーでROMを吸い出して、PCで遊ぶことにしました。
私はレトロダンパー(メーカーのサイト)を使っています。クライアントを起動して、認識ボタンを押し、吸い出すだけでOKなので便利です。
早速、中古のカセットを購入しました。外観は割とズタボロというか、年季入った姿です。ま、動けば良いのさ。
ファミコン版Might and Magic Book Oneのカセット
GAKKENのロゴの通り、なぜか日本語ローカライズは学習研究社(学研)が行っています。今見ると不思議です。教科書作ってる学研が、なぜゲームの移植を……。
レトロダンパーさんで吸い出すときはこんな感じになります。吸い出したROMをエミュレータに放り込んでみたところ、正常に動作しているようです。良きかな良きかな。
まずは普通に遊んでみました。今なら意外とクリアできるのでは?と思ったのも束の間、あっ、無理でした。調子乗ってすみませんでした。在りし日の絶望が蘇りました。
最初から難しすぎます。基本的には1バトルごとに休憩+セーブって感じです。攻撃が当たらないのも辛く、一方的にボコられて死んでしまいます。死んだら復活させるお金がないのでリセットです。
さらにWikipediaを見てびっくりしたのは、次の一文です。
「ファミコン版は(中略)やや簡単に調整された部分が多い」
えっ?これで?嘘だろ……??オリジナル版はどれだけ鬼畜難易度なの?
息抜きにYouTubeで攻略動画を見ていると、ラストダンジョン(イドの迷宮、アストラル世界)の音楽がとてもカッコ良いですね。何とか辿り着きたいですが、最初の町(ソーピガルの町)から脱出できていない身からすると、果てしなく遠いです。
TASに挑んだ記録。
解析したときの情報。その他。
Might and Magicの攻略、解析の参考になるサイトです。
日記を漁って携帯の遍歴を書き出してみました。日記を書く習慣がなかった頃の機種や時期は不明です。
(基本的には)長く使っていた機種は気に入っていた機種です。ガラケー時代はいずれも良い機種で、バッテリーが死ぬまで使ってました。最後のP-03Bだけ1年しか使っていませんが、不満があったわけではなく、知人に携帯を譲るため手放しました。たしか。
スマホ時代は国内メーカーの質は明らかに落ちました。SO-02Cはソツなく良かったんですけど、ストレージが少なすぎで買い替え直前は容量不足で挙動不審でした。SH-01Fは性能良いものの、電池がなくなるのが早く、本体が熱すぎでした。この機種で懲りてAndroidハイエンド機を買わなくなりました。
今になって調べてみたところ、この2機種はマシな部類だったようで、富士通ARROWSのように「カイロ機能搭載」「電話ができない」「メールがこない」など、怨嗟にまみれたレビューが未だに残っている機種もあります。悲惨です。
日本だけ異常にiPhone普及率が高い理由って、国内メーカーが2010年代初頭にやらかしたから……!?と思ってしまいました……。
メモ: 技術系の話はFacebookから転記しておくことにした。
目次: RISC-V
メインCPUからサブCPUを起こすとき基本的には、
RAMの初期値が不定であると仮定すると、サブCPUが下手にポーリングすると、不定値によって条件が成立してしまい、メインCPUからの起動司令がないのに勝手に起動してしまう事態に陥ります。
先程書いた基本的な構造を素直に書くとこんなコードになるでしょう。
/* メインCPUはHARTID=8, サブCPUはHARTID=0...3とする */
#define HARTID_MAIN 8
#define HARTID_SUB_START 0
#define HARTID_SUB_END 3
#define HARTID_MAX 9
struct {
int boot_wait;
int boot_done;
} init_core[HARTID_MAX] = {};
int get_hartid(void)
{
int i;
__asm__ volatile("csrr %0, mhartid" : "=r"(i));
return i;
}
/* メインCPUが実行する */
void boot_main(void)
{
for (int i = HARTID_SUB_START; i < HARTID_SUB_END; i++) {
init_core[i].boot_wait = 1;
}
}
/* サブCPUが実行する */
void boot_sub(void)
{
int hartid = get_hartid();
while (!init_core[hartid].boot_wait) {
/* busy loop */
}
}
残念ながらこのコードは正常に動作しません。共有RAMつまりinit_core[hartid].boot_waitの値が起動直後から != 0だったとき、boot_sub() はboot_main() からの起動司令を待つことなく起動してしまうからです。
共有RAMの不定値に対処する方法を考えます。基本的にはサブCPUが変数を初期化(boot_wait = 0)してから待ちに入れば良いのですが、新たな問題が生じます。メインCPUとサブCPUの実行順序はどちらが先という保証はないため、
以上の順で実行されるとメインCPU側の起動司令が消されてしまい、ハングアップする可能性があります。この問題の回避のため、変数を1つ追加し、サブCPUのブートが終わるまで、メインCPUは繰り返し起動司令を送るように変更します。
先程書いた基本的な構造を素直に書くとこんなコードになるでしょう。
/* メインCPUはHARTID=8, サブCPUはHARTID=0...3とする */
#define HARTID_MAIN 8
#define HARTID_SUB_START 0
#define HARTID_SUB_END 3
#define HARTID_MAX 9
struct {
int boot_wait;
int boot_done;
} init_core[HARTID_MAX] = {};
int get_hartid(void)
{
int i;
__asm__ volatile("csrr %0, mhartid" : "=r"(i));
return i;
}
/* メインCPUが実行する */
void boot_main(void)
{
for (int i = HARTID_SUB_START; i < HARTID_SUB_END; i++) {
init_core[i].boot_done = 0;
while (!init_core[i].boot_done) {
init_core[i].boot_wait = 1;
}
}
}
/* サブCPUが実行する */
void boot_sub(void)
{
int hartid = get_hartid();
init_core[hartid].boot_wait = 0;
init_core[hartid].boot_done = 0;
while (!init_core[hartid].boot_wait) {
/* busy loop */
}
init_core[hartid].boot_done = 1;
}
残念ながらこのコードも正常に動作しません。共有RAMへの値の反映が他のCPUに即座に見えること(アトミック性)を暗に期待しているからです。
今日のマルチコアシステムでは、boot_wait = 0としたときに、他のCPUにも即座に同じ値が見えているとは限りません。主な要因としては、
などがあります。通常の変数への代入、参照が他のCPUに即座に値が見えないことにより、おかしくなるパターンはいくつか考えられそうですが、ありがちなパターンとして、
以上の順で実行されるとメインCPU側が起動司令を送らないまま、サブCPU側も何もできずハングアップする可能性があります。この問題の回避のため、通常の変数への代入、参照ではなく他のCPUにも値が見えるように初期化、代入(アトミックアクセスする)必要があります。
従来C言語でアトミックアクセスを行うためには、実装対象アーキテクチャの知識やアセンブラの記述を必要とするなど、やや困難が伴いました。ですがC11でアトミックアクセス用の定義stdatomic.hが追加されたことで、アトミックアクセスはかなり楽になりました。素敵ですね。
ひとまず速度を全く気にせず、全てのアクセスをアトミックアクセスに入れ替えると、こんなコードになるでしょう。
/* メインCPUはHARTID=8, サブCPUはHARTID=0...3とする */
#define HARTID_MAIN 8
#define HARTID_SUB_START 0
#define HARTID_SUB_END 3
#define HARTID_MAX 9
struct {
atomic_int boot_wait;
atomic_int boot_done;
} init_core[HARTID_MAX] = {};
int get_hartid(void)
{
int i;
__asm__ volatile("csrr %0, mhartid" : "=r"(i));
return i;
}
/* メインCPUが実行する */
void boot_main(void)
{
for (int i = HARTID_SUB_START; i < HARTID_SUB_END; i++) {
atomic_store(&init_core[i].boot_done, 0);
while (!atomic_load(&init_core[i].boot_done)) {
atomic_store(&init_core[i].boot_wait, 1);
}
}
}
/* サブCPUが実行する */
void boot_sub(void)
{
int hartid = get_hartid();
atomic_store(&init_core[hartid].boot_wait, 0);
atomic_store(&init_core[hartid].boot_done, 0);
while (!atomic_load(&init_core[hartid].boot_wait)) {
/* busy loop */
}
atomic_store(&init_core[hartid].boot_done, 1);
}
C11のアトミックアクセスは何も指定しない場合、一番制限の強い(= 確実に他のCPUに見えるものの、アクセス速度は遅い)memory_order_seq_cstアクセスになります。マルチコアのブートを行うにあたって、常に制限が強いアクセスは必要ありませんが、とりあえずこれで動くはず。
まれにxtermの256色指定エスケープシーケンスに対応していない端末があってvimの表示が変な色になってしまいます。チェック用のスクリプトを作っておきました。単純に背景色を変更するエスケープシーケンスと、空白文字、色を元に戻すエスケープシーケンスを連打するだけです。
#!/bin/sh
ESC_ORG="\e[0m"
print_colors()
{
for i in ${*};
do
printf " %3d\e[%dm " ${i} ${i};
echo -n ${ESC_ORG}
done
echo
}
print_xterm_colors()
{
for i in ${*};
do
printf " %3d\e[48;5;%dm " ${i} ${i};
echo -n ${ESC_ORG}
done
echo
}
echo "System colors (ESC[Nm):"
print_colors `seq 40 47`
echo
echo "xterm 256 colors (ESC[48;5;Nm):"
for i in `seq 0 8 248`;
do
j=`expr ${i} + 7`
print_xterm_colors `seq ${i} ${j}`
done
実行するとこんな感じになります。
対応していない端末だとこうなりますと言いたいところでしたが、対応していない端末が見当たりませんでした。前はあった気がするんだけどなあ……?
何kHzの音まで聞こえるかテストするサイト、聞こえチェック | Panasonic が、以前Twitterでちょっと話題になりました。
私の場合15kHzまでは聞こえますが、それ以上(17kHz, 19kHz)は全く聞こえません。鳴ってんのか?これ??
まずブラウザの影響を排除するため、上記のサイトから音源をダウンロードします。WavではなくMP3ファイルでした。
直接オーディオプレイヤーで聞いても15kHz以外は聞こえません。ブラウザのせいじゃなかった。私の耳は全くあてにならないので、オシロスコープにご登場願います。
19kHz再生時の波形(グラフはキャプチャし忘れて17kHzのまま。右下の周波数表示が19kHzを示している)
いやあ、バッチリ綺麗にSin波が鳴ってます。私は全く聞こえませんね、これが老いかぁ……。
< | 2021 | > | ||||
<< | < | 10 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | - | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | - | - | - | - | - | - |
合計:
本日: