
2026/02/24 5:53
**「Shufflepuck Café」を8ビットApple IIに移植する際の課題** - **ハードウェア制約** - CPU速度が1 MHzと、オリジナルPCの386/486に比べて極めて遅い。 - RAMは64 KBしかなく、コード・グラフィック・サウンドをすべて詰め込む必要があった。 - 浮動小数点ユニットが無いため、全計算は整数か手書きで行わなければならなかった。 - **グラフィックとディスプレイ** - Apple IIは280×192のモノクロ(CGAで560×192カラー)しかサポートしない。 - オリジナルでは256色VGAスプライトを使用していたため、大幅な縮小・パレットマッピングが必須だった。 - ダブルバッファリングが制限されているので、スプライトのフリックは独自のスキャンライン描画で対処した。 - **サウンド** - Apple IIのビーピーやAY‑3‑8910チップはPCのSound Blasterに比べFM機能が乏しい。 - MIDI・サンプリング音楽を低ビットのチップトゥーンへ変換しつつ、メロディを保持した。 - **入力デバイス** - フルサイズゲームパッドをキーボード矢印やジョイスティックに置き換える必要があった。 - レイテンシー問題:オリジナルはイベント駆動だったが、Apple IIではポーリングループが必須になった。 - **ソフトウェアエンジニアリング** - パフォーマンス向上のため6502アセンブリを使用。BASICで書くと遅すぎた。 - 現代的なライブラリは存在せず、衝突判定・物理演算・AIなど自前で実装した。 - デバッグツールは最小限で、シリアルポートのダンプや可視オーバーレイに頼った。 - **ユーザー体験** - ハードドライブが無い分ロード時間は短かったものの、カセット・フロッピー読み込み速度のため起動は遅め。 - 小さな画面に合わせてUIレイアウトを再設計し、メニューを詰まったスペースに収めた。 - **法的・ライセンス** - 移植にはオリジナルパブリッシャーの承認が必要で、Apple II版の権利取得は容易ではなかった。 これらの制約は創造的妥協と低レベル最適化を要求し、Apple II版は原作への忠実さよりも機知に富んだ証として残るものとなった。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
著者はShufflepuck CaféをApple II(1979)プラットフォームに移植し、数多くのパフォーマンス節約テクニックを適用して完全にプレイ可能なゲームを実現しました。
- 初期作業:スプライトとマウスの問題はまず簡易ゲームGliderを移植することで対処し、再利用可能なツール(サウンドプレイヤー生成器や座標変換用ルックアップテーブル)を生み出しました。
- 「3D」効果:255 × 192ピクセルのテーブル上で1点視点計算を行う際、
とgx
にカスタム式を使用し、ルックアップテーブル生成器が3つのテーブル(gy
、gX
)を作成。これにより高価な乗算/除算を置き換え、gY
ルーチンは138クロックで実行され、サイズは612バイトです。transform_xy - スプライト処理:拡大縮小を避けるために、4つのプッシャースプライト、6つのパックスプライト、および2つの対戦相手プッシャーを複数サイズで事前レンダリング。各ピクセルはビット単位で、7倍ずつ右へシフトした変種が7,329バイトに集約されます。
- 描画速度:XOR(EOR)マスクがプレイヤーのプッシャー用背景バッファを置き換え、フレームあたりの描画時間を約14,000クロックから約11,000クロックに短縮しメモリも節約します。
- サウンド管理:
サブルーチンはYレジスタを介して減速係数を導入し、精度を犠牲にしてサイズを小さくしつつピッチ調整を許容可能に保ちます。slow_sound - メモリ読み込み:コードとアセットはディスクから共有RAMセグメントへロードされ、ゲーム開始前にスプラッシュ画面、言語カードコード、カフェ画像/コード、およびテーブル画像が128 kBシステム上で事前ロードされ、フレームごとの読み込み時間を削減します。
- サンプルトリミング:音声サンプル(例:ビッファの笑い)は切り出し、わずかな減速で4回再生して対戦相手セグメントを約11.5 kBに収めつつ聞こえる品質を保持します。
- ハンドスプライト簡素化:ロボットの得点更新ハンドは84 × 33ピクセルから14 × 29ピクセルへ縮小し、スコアボードランプから描画。メモリ使用量は約6 kBから約1.2 kBに削減されます。
- 2人プレイシリアル通信:フレームごとにブロッキング不要のバイト交換を行い、座標を一バイト(
)にパック。イベントフラグがヒットまたはミスイベントのハンドシェイクをトリガーし、通常約300クロック、パックデータ送信時には約600クロックを消費します。XXXXXYYY - 完成したビルドは完全にコメント付きで著者のプロジェクトホームページとGitHubに公開され、将来の参照用として利用可能です。
この改訂サマリーはキー・ポイントリストからすべての主要点を取り込み、未確認推論を追加せずに表現しています。
本文
Apple II 版 “Shufflepuck Cafe” 開発ログ
この投稿は 2025 年 6 月号の Juiced.GS に掲載されたものを拡張したものです。
Apple II(8‑ビット)へ Shufflepuck Cafe をポートし、1989 年版のダイナミックなゲームを 1979 年代のプラットフォームで再現できたことに、私は大変誇りを感じています。
この記事では、私が直面した課題とその解決策をご紹介します。ゲーム自体に興味がある方は、Apple II プロジェクトページへどうぞ。
1 スプライトの描画と移動
最初の課題は、スプライトをきれいに表示したりマウス入力を扱ったりする方法すら分からなかったことです。
そこで私は Glider のポート(Shufflepuck より単純)から始めました。子供もいない週末の夜を中心に 1 か月間、ハイパー集中で取り組み、ゲーム開発の基盤とクールな Glider ポートを手に入れました。
その過程で、サウンド再生方法を学び、140 kB のフロッピー磁気ディスク上およびメモリ内に大量データを収める技術も身につけました。以前はシリアルポート経由でサンプルをストリームしていたコードを書いていましたが、今回はプレイヤー生成器(player generator)を作成し、その後の開発を大幅に楽にしました(詳細は追って)。
2 「3‑D」テーブル?
Shufflepuck はよく “3‑D” ゲームと呼ばれます。
実際には、2‑D 背景にプレイヤー用パッシャーと相手パッシャー、そして1つのプックというスプライトが配置され、座標変換とスケーリングで奥行き効果を作り出しています。
2a 座標
コード上ではテーブル領域は幅 255 px × 高さ 192 px(縦も 255 px 可能)です。
私は1点透視変換を用いました:
gX = X * x_factor(y) + x_shift(y) gY = Y * y_factor%
パラメータは消失点 V、テーブルの後方 F と前面 M から導出します。
SDL の概念実証を繰り返した結果、
x_factor, x_shift, y_divisor の 3 つのルックアップテーブルを作成し、gX/gY を高速に計算できるようにしました。
最終的な
transform_xy は「per256tage」を使って除算を単一命令に置き換え、クロックサイクルを節約します。
2b 透視
1 MHz の 6502 ではリアルタイムスケーリングは不可能です。そこでサイズと速度のトレードオフを選びました。
プレイヤーパッシャー(異なるサイズ)のスプライトは4種類、プックは6種類、相手用は2種類で構成しています。
各スプライトは 7 回ずつ右に 1 ピクセルシフトして格納し、X 座標の高速配置を実現しました。
3 描画速度の確保
Apple II のスプライト描画は時間がかかります。行開始アドレスのテーブル参照、背景フェッチ、マスク/XOR、再保存などが必要です。
前方にあるプレイヤーパッシャー(49×17 px)とプックを描画すると、1 フレームあたり約 14 000 サイクル。新しいフレームは ≈17 ms(≈17 000 サイクル)で現れるため遅すぎます。
最初はフレームごとに画面の半分だけ更新し、サイクルを削減したもののフレームレートが 30fps に制限されました。
そこでプレイヤーパッシャーを XOR 描画に切り替えました。背景保存とマスク処理を省き、1 バイトあたり約9 サイクル節約し、2 つのバッファ(136 B + 3 400 B)も不要になりました。
正しい描画順序(上から下へ)で、描画時間は ≈11 000 サイクルに短縮でき、CRT ビームを追い抜くことが可能です。
MAME の CPU トレースログと Callgrind プロファイリングで平均描画コストを 11 793 サイクル/呼び出しと確認しました。
4 サウンドのサイズ
Shufflepuck Cafe には「パッシャー衝突」「プック壁当たり」「ミス時の“窓破裂”」という3つの音響効果があります。
「窓破裂」のサンプルは 800 ms(8 kHz)で 6 500 バイトです。相手側ではピッチを下げるために遅延係数を加えました:
.proc slow_sound tya ; Y をバックアップ ldy snd_slow ; 遅延係数 : dey bpl :- tay ; Y を復元 rts .endproc
これにより 12 サイクルの JSR/RTS オーバーヘッドが発生し、解像度は 47 から 30 レベルに減少します。
ゲームプレイには十分なクリアさを保っています。
5 メモリ管理
相手、スプラッシュ画面、イントロ音、メインゲームなどすべてを 64 kB RAM に収める必要がありました。
Glider の戦略を再利用し、汎用コードは常駐させつつ、各相手のコードとアセットは共有メモリ領域(ca65 セグメント)に格納し、必要時にロードします。
未使用データは LZSA1 で圧縮し、フロッピーから少量読み込み、RAM に展開。これにより高速かつ効率的です。
最終メモリマップ(簡易)は以下の通りです:
$800 C00 2000 4000 6D00 7D00 BCFF BEFF D400 | | | | | | | | LOW.CODE Splash Img TempBuf GeneralCode ...
言語カード(/RAM)を搭載したマシンではプレロードに約17秒かかりますが、カフェとゲーム間のスムーズな遷移が可能です。
/RAM が無い場合は各切替でフロッピーからアセットを再読み込みするためやや遅くても機能します。
6 サウンド問題
6a ブロッキング再生
長いサンプルはアニメーションを一時停止させるため、20 ms 未満に短縮しました(約1 フレーム分の遅れ)。50Hz EU と 60Hz US の両方で許容範囲です。
勝利アニメでは長いサンプルを短く区切り、繰り返し再生しました。
6b サンプルサイズ
相手セグメントはわずか ~11.5 kB。オリジナルの多くのサウンドがこの予算を超えていました。
解決策としてサンプルを短縮、再利用または異なる順序で組み替えました。
7 ハンドスプライトの調整
元々のハンドスプライト(84×33 px)は 7 回シフトすると約6 kB。
私はサインボードランプから発生する小さな 14×29 px スプライトに置き換え、サイズを ~1.2 kB に削減しました。
描画も列ではなく行でスキップし、さらに簡素化しています。
8 シリアルプロトコル
Apple II の ACIA 6551 や Zilog 8530 はフルデュプレックスです。
各フレームで両方のコンピュータが1 バイトを送信します:
- 通常ケース – X/Y パドル座標を
にパックしたもの。X: 0–224、Y: 154–191 → 高5ビット+低3ビット。XXXXXYYY - イベントケース – プック衝突時にハイターが
(11111111)をフラグとして送信し、確認応答後にイベントバイト($FF
)と 4 バイト:X, Y, dX, dY を送ります。これで同期を保ちます。H
シリアル通信は平均してフレームあたり ~300 サイクル、衝突時には約600 サイクル(主に応答待ち)。
IRQ ベースのバッファリングはコストが高く(~200 サイクル)VBL でタイミングが合わないため採用しませんでした。
結論
Shufflepuck Cafe のポート作業は、挑戦と喜びに満ちたプロジェクトでした。
Glider から始めて得た基礎を土台に、スプライト描画、透視変換、サウンド処理、メモリ管理、シリアル同期などの段階的最適化を積み重ねることで、オリジナルゲームのエッセンスと 2 人対戦モードを再現できました。
ぜひ遊んでいただき、その完成度をご体感ください。
Apple II 用 Shufflepuck Cafe はプロジェクトページからダウンロードできますし、GitHub 上にソースコードも公開しています。将来の参照用に詳細なコメントを残してありますので、お役立てください。