DCPU-16用テトリス

狭い円で広く知られているMinecraftの開発者であるHabréで既に書いたように、 Marcus "Notch" Perssonは現在、281,474,976,712,644の宇宙で行われる新しいゲームの開発に忙しくしています。



Minecraftのように、ゲームは非標準になります。メインの「トリック」は完全にエミュレートされたプロセッサであり、その制御下で宇宙船がビッグ...ユニバースの広がりを耕します。 0x10 C年のゲームのキャラクター(実際、ゲームはそう呼ばれている)は1980年からまっすぐになったため、DCPU-16プロセッサーの特性はその時代にほぼ対応しています:RAMの128キロバイト、100キロヘルツ、簡単な命令セット。



ゲームはまだ開発の非常に初期の段階にあるという事実にもかかわらず、プロセッサの仕様はレビューのためにすでに利用可能です -そして、コミュニティはすでに形成されており、その参加者は存在しないプラットフォームのためにあらゆる種類の興味深い作品を開発しています。 あなたの謙虚なしもべはこれらの人々の中にあります、そして、この投稿で私はこれらのことの1つについて話したいです: DCPU-16のためのテトリス実装。



(そしてすぐに免責事項:ビデオの音楽は芸術的、いわば表現力のために個別に重ねられます; DCPUは残念ながら音声出力をまだ許可していません)





「内部」DCPU-16の詳細



それで、スペックは何について話しているのでしょうか? プロセッサが16ビット(バイトを忘れることができます-ダブルバイトワードのみがアクセスされます)、12個のレジスタ(そのうち3つはSPPCおよびOはサービスタイプです)、65,536ワードのメモリがあります(つまり、 128キロバイト)、15個の基本オペコードと1個の追加オペコード、命令は1から3ワードで、これらはすべて1秒あたり約10万サイクルの頻度でエミュレートされます(サイクル数はオペコードとオペランドとして使用される「大リテラル」によって異なります)

基本オペコードは複雑ではありません-割り当て、算術(小数は小数点以下16ビットの固定精度でのみ表されます-オーバーフローレジスタ、分割時にO )、ビット操作、条件チェック。 遷移はありません。注意してください:それらはPCレジスタに値を直接割り当てることによって行われます(ご想像のとおり、次に実行される命令を指します)。 条件テストは、次のステートメントを単にスキップします。条件が満たされない場合、検証と通常の遷移を組み合わせて条件付き遷移が行われます。 唯一の非基本オペコードは、サブプロシージャを呼び出し、スタックの先頭に戻りアドレスを配置し、指定されたアドレスに移行することです。



オペランドには興味深いものがあります:通常のレジスタ、特定のアドレスのメモリ、アドレス+レジスタ値のメモリ、および直接設定された値に加えて、特別な値POPPEEK 、およびPUSHがあります (はい-これらは操作ではなく、オペランドです!)、まず、アクセスを許可しますスタックの最上位に移動し(一般的に、サービスレジスタが指すメモリに直接アクセスすることはできません)、次に、 SP値を1ずつ増減します。 その結果、 SET PUSH、Xを使用して、スタックに値を設定できます。SETX、POP-そこから取得し、 SET X、PEEK-スタックから取得せずに値をコピーします。 SET X、PUSHなどの真の奇妙な構造が受け入れられるようになりました。 SET POP、XおよびSET POP、PUSHでさえも。 彼らが何をしているかを理解することは、もはやささいなことではありません。



最後に、メモリについて。 スタックについては既に上記で説明しました。初期化中はすべてのレジスタが0であるため、0xFFFFで始まり(最初のPUSHは最初にSPを1つ減らし、その後値を書き込むだけです)、成長します。 さらに、ビデオバッファがアドレス0x8000にあることが判明しました-ディスプレイには各32文字の12行が含まれ、行ごとに格納され、各単語は1文字に対応します。 確かに、16ビットのうち、下位7ビットのみが文字コードに送られました(キリル文字のサポートを提供するために非常に変更する必要があります)。 16色、 CGAパレット(RGBI)が使用されます。







ディスプレイへのアクセスに加えて、キーボードから文字を読み取る機能があります。このために、サイズが16バイトの0x9000の循環バッファーが設計されています。 バッファーが機能するには、次の文字へのポインターを保存する必要があります-メモリー内の値がゼロ以外になるとすぐに、それを読み取り、0を書き込み、ポインターを1増やす必要があります(0x9010に達したら、0x9000にリセットします)。 ノッチはこれをすべて説明するために彼自身の例を引用しました



最後に、最新のデータによると、表示用に独自の文字セットを設定できます-それは0x8180(ビデオバッファーの直後)にあり、各単語は4x8ピクセルの1文字を表し、各バイトは1列に対応します。



Javascriptエミュレーター



DCPU-16デバイスは非常に単純で、そのパフォーマンスは高くありません。そのため、 JavaScriptでオンラインエミュレーターを作成することにしました。 もちろん、私はこのアイデアで一人ではありませんでした-今そのようなエミュレーターはすでに巨大なヒープですが、どんな狂ったプログラマーがこれを止めましたか?



結果は次のようなものです。



http://dcpu.ru/



実際には、これは単なるエミュレーターではなく、「スリーインワン」シャンプー-アセンブラー、エミュレーター、さらには逆アセンブラーです。 独自のシンボルテーブルを除き、上記のほぼすべてが可能です。 しかし、デバッグ時には現在の行が美しく強調表示され、それを組み立てると式の計算などの面白いことができます。 Notchのアセンブラーでマクロをさらにねじ込んでいくと思います。



まあ、それは彼についてではありません。 私は叙情的な余談を終え、記事のトピックに目を向ける。



テトリス



誰もが知っているように、 テトリスは1984年にアレクセイ・パジトノフによって発明されました。 作成者は同胞であり、ゲームはほぼDCPU-16と同じレベルのコンピューター向けに作成されました-実装に適したものは何ですか?



コードについて簡単に説明します。 キャラクターを再定義する可能性についてはまだ知りませんでしたので、普通のキャラクターで描くことにしました。1つのセルに2つのスペースを埋めます。 (4ターンのいずれかの)各数字は、合計7桁の4x4セルの正方形に入力できます-各16ワードの合計28の状態(画面に書き込まれるコードを直接保存することにしましたが、出力時にのみ2倍にします)。 これらはすべて、プログラムの最後に積み上げられます。



いくつかのサブプロシージャを書きました。 たとえば、画面に行を表示するには(「Score:」、「Level:」、「GAME OVER」の一部があります)、キーボードから文字を読み取るか、ここに擬似乱数ジェネレーターがあります。



:next_rand ; takes no arguments, returns random word in A MUL [rand_seed], 10061 ADD [rand_seed], 1 SET A, [rand_seed] SET PC, POP :rand_seed DAT 0xC00F
      
      





一般に、 線形合同法(LCG)の標準実装です。 彼はランダムに素数10061を取りました-誰かが最高の候補者を持っているなら、彼は修正を受け入れる準備ができています。 いずれにせよ、ここには暗号化はありません。特別な「ランダム性」を心配する必要はありません。 それは異なる人物を投げ、神に感謝するようです。 初期のrand_seedカーネルは次のように初期化されます。ループで起動すると、ユーザーがキーを押すまで値が増加します。 ちなみに、彼らは古いゲームでは「任意のキーを押す」もこのためだけだったと言っています。



最後に、画面から図形を消去して描画し、配置の可能性を確認できる最もmostなサブプロシージャ:



 :show_cur_piece ; display/clear/check current piece (A=0 - clear, A=1 - display, A=2 - check), if A=2 doesn't actually place anything, return B=1 is position is valid, 0 otherwise SET X, [piece_pos] ; place block at [X] (display) SET Y, [cur_piece] ; ...from [Y] (pieces array) SHL Y, 2 BOR Y, [cur_rot] SHL Y, 4 ADD Y, pieces SET I, 0 ; index :piece_cyc1 SET B, [Y] IFE B, 0 SET PC, piece_jmp1 IFG 2, A SET PC, piece_jmp2 IFG X, 0x8000 + 32*12 ADD PC, 3 IFE [X], 0 SET PC, piece_jmp1 SET B, 0 SET PC, POP :piece_jmp2 IFE A, 0 SET B, 0 SET [X], B SET [X + 1], B :piece_jmp1 ADD I, 1 ADD X, 2 ADD Y, 1 SET B, 1 IFE I, 16 SET PC, POP IFB I, 3 SET PC, piece_cyc1 ADD X, 32 - 8 SET PC, piece_cyc1
      
      





ご覧のとおり、すべてのチェックはビデオバッファ内で「インプレース」で直接行われます。 もちろん、これには小さなマイナスの効果があります-フィギュアを移動する場合は、最初にそれを消去し、次に新しい場所に配置する必要があります。これが失敗した場合は、古い場所に再度描画します。 消去のため、わずかな点滅が発生しますが、作業の速度は十分であることが判明し、ほとんど目立たなくなりました。 実際、図が1セル下に移動する前に、約2500回の反復を完了します(それぞれ、バッファー内のキーの存在が確認され、図が左または右に回転または移動します)。2000が2番目、1500が3番目などです。



私は最もネストされたループについて言及しましたが、さらに2つに包まれています-重力の下でフィギュアを下に移動できない場合、完全に満たされた行の存在をチェックし、それらを破棄し、新しいフィギュアを選択します(実際には、事前に選択して描画しますフィールド「次へ:」、ここで選択は「1歩前進」)、上からグラスに投げ込まれます。 終了できない場合は、外部サイクルも完了し、「Game Over」という美しい点滅の碑文が描かれます。



塗りつぶされた行のチェックは、現在の「書き込まれた」行と現在の「読み取り」行への2つのポインターを使用して、下から上に向かっていきます。 最初に、「読み取り可能」は、最上部に達するか、空の行に達するまで上昇します。 次に、「読み取り可能な」行から「書き込み可能な」行にセルがコピーされ、両方が1行高くなります。 いくつかの最適化:両方のポインターが同じ行に対応している場合、コピーは行われません。既に最上部にある場合、または既に消去された行の数が4になった場合(一度にそれ以上破棄できない場合)、「読み取り可能な」行ストップを上げようとします。 その結果、それは非常に迅速に動作し、生きることができます。



 :scan_lines ; search for complete lines, remove them and move all other down; update score & level SET A, 0x8000 + 32*11 + 11 ; start of next line to fill SET B, A ; start of next line to check SET J, 0 ; num of lines skipped :scan_cyc2 SET I, 0 ; horizontal index SET X, B :scan_cyc1 IFE [X], 0 SET PC, scan_jmp1 ADD X, 2 ADD I, 1 IFG 10, I SET PC, scan_cyc1 ADD J, 1 ; no gaps found, increase num of complete rows SUB B, 32 IFE J, 4 SET PC, scan_jmp1 IFG B, 0x8000 SET PC, scan_cyc2 :scan_jmp1 ; found a gap, or no more gaps can be found IFE A, B SET PC, scan_jmp2 ; no need to move anything, continue SET I, 0 :scan_cyc3 SET [A], [B] ADD I, 1 ADD A, 1 ADD B, 1 IFG 20, I SET PC, scan_cyc3 SUB A, 20 SUB B, 20 :scan_jmp2 SUB A, 32 IFG 0x8000, A SET PC, scan_end SUB B, 32 IFE J, 4 SET PC, scan_jmp1 IFG 0x8000, B SET PC, scan_jmp1 SET PC, scan_cyc2 :scan_end IFE J, 0 SET PC, POP ADD [lines], J SET J, [lines_score + J] MUL J, [level] ADD [score], J JSR update_score IFG 10, [lines] SET PC, POP SET [lines], 0 ADD [level], 1 JSR update_level SET PC, POP
      
      







完全なソースはそれを実行するためにここから入手できます -「 dcpu.ruエミュレーターで実行 」ボタンを見つけ、すでにそこにあります-「 実行 」ボタン。



参照資料







PSはい、私は自慢せざるを得ません-ノッチ自身上記のすべての努力の結果を賞賛しました :)



All Articles