COBOLで書かれたMinecraftサーバー
(github.com/meyfa)- CobolCraftはCOBOLで実装されたMinecraftサーバーで、執筆時点での最新バージョンであるMinecraft 1.21.4をサポートする
- すでに無限地形生成、動的チャンク読み込み、ワールド・プレイヤーデータのディスク保存、既存ワールドのインポート、マルチプレイ、サーバーステータス表示、ブロック破壊・設置、インベントリ、クラフト、アイテム拾得、チャット、コマンドなどを実装している
- 複数の状態・向き・相互作用を持つブロックは、正しく動作させるために専用コードを大量に必要とし、まだサポートされていないブロックも多い
- Linux x86_64またはarm64向けにGnuCOBOLで開発されており、Dockerを使った配布も可能だが、Windowsなど他のOSでのサポートはテストされていない
- Minecraftの標準datapackと公式サーバー・クライアントの
.jarからJSONデータを抽出し、コンパイル時のCOBOLコード生成とランタイムデータの読み込みに利用している
CobolCraftが実装したMinecraftサーバー機能
- CobolCraftはCOBOLで書かれたMinecraftサーバーであり、Minecraft 1.21.4をサポートする
- 実装済み機能には以下が含まれる
- 無限地形生成と動的チャンク読み込み
- ワールドとプレイヤーデータのディスク保存
- Minecraftファイルフォーマット対応と既存ワールドのインポート
- 同時接続プレイヤー数を設定できるマルチプレイ
- サーバー一覧でオンライン表示されるping/server status
- ブロック破壊と設置、自動生成されたloot tableコード
- 右クリックベースのブロック相互作用
- プレイヤーインベントリ
- 2x2および3x3クラフト
- アイテムエンティティとアイテム拾得
- チャット
- ゲーム内コマンドと対話型コンソールコマンド
server.propertiesベースの設定whitelist.jsonに保存される永続whitelist- 非常に基本的なブロック・プレイヤー衝突とエンティティ物理
- 落下ダメージ、voidダメージ、死亡、リスポーン
ブロック対応範囲と制限
- 複数の状態、向き、相互作用を持つブロックは、正しい動作のために特化したコードを大量に必要とする
- まだサポートされていないブロックが多い
- 動作するブロックには以下が含まれる
- torches
- slabs
- stairs
- logsのようなrotated pillars
- 相互作用しないbuttons
- doors
- trapdoors
- beds
- signs
ビルドと実行環境
- CobolCraftはGnuCOBOLで開発されており、Linuxでの実行を対象としている
- 対象アーキテクチャはx86_64とarm64
- Windowsなど他のOSでのサポートはテストされていない
- Dockerを使えばプラットフォーム非依存の配布が可能
- Linux配布に必要な項目は以下の通り
- GnuCOBOL 3.1.2以上
- 性能面ではGnuCOBOL 3.2以上を推奨
makegcc,g++zlib- 公式サーバー
.jarのダウンロードに必要なcurl - サーバー
.jarからデータを抽出するために必要なJava 21以上
- ビルドと実行コマンドは以下の通り
make --jobs=$(nproc)
make run
- Docker Hubイメージを使うか、自分でビルドして実行できる
docker pull meyfa/cobolcraft:latest
git clone https://github.com/meyfa/CobolCraft.git cobolcraft && cd cobolcraft
docker build --tag meyfa/cobolcraft .
サーバー設定とネットワークアクセス
- サーバー設定は
server.propertiesファイルを編集して行う - このファイルは初回起動時に、サポートされるすべてのオプションのデフォルト値とともに自動生成される
server-port: デフォルト値25565level-name: デフォルト値"world"white-list: デフォルト値falsemotd: デフォルト値"CobolCraft"max-players: デフォルト値10、最大100
- デフォルトではサーバーは
localhost:25565を通じて自身のシステムからのみアクセス可能 - ローカルネットワーク、VPN、ポートフォワーディング、レンタルサーバーなど外部からアクセス可能にするには、Docker実行時に
0.0.0.0:25565:25565でポートをバインドできる
docker run --rm -it -p 0.0.0.0:25565:25565 meyfa/cobolcraft
COBOLでMinecraftサーバーを作った理由
- 開発者は以前COBOL経験がまったくなかったが、COBOLに関する噂やスティグマを見て、この言語をもっと知りたいと思った
- 言語を学ぶ最良の方法として、何かを実際に書くやり方を選んだ
- Minecraftコードの複雑さと規模のため、COBOLでMinecraftサーバーを書くという選択は良いアイデアでもあり悪いアイデアでもあったと評価している
- 他の言語では簡単な作業も、一から作る必要があった
- JSONのパースとエンコード
- 複数種類のバイナリデータ処理
- リアルタイムのマルチプレイネットワーキング
- オブジェクト指向的なMinecraftシステムを手続き型言語へ移し替える作業
- 急な学習曲線によってCOBOLとその概念を深く調査・理解することになり、その過程はやりがいがあったと述べている
- COBOLを初めて使う人には GnuCOBOL Programmer's Guide を勧めている
- Minecraftプロトコルの学習資料としては wiki.vg documentation を参照できる
- 場合によってはWireguardのようなツールで実際のサーバートラフィックを見ることも、情報の流れを理解するのに役立つことがある
ソース構成と実行バイナリ
- COBOLソースコードは主に
src/ディレクトリにあるsrc/main.cobがエントリポイントsrc/server.cobにはサーバー起動コードとゲームロジックがある
codegen/ディレクトリにはCOBOLで書かれたコードジェネレーターがある- Minecraft標準datapackなどのJSONデータから追加のソースコードを生成する
cpp/ディレクトリには、COBOLでは扱いにくいOS連携のためのC++ソースがある- 低レベルTCPソケット管理
- 高精度タイミング
- プロセスシグナル処理
- すべてのCOBOLおよびC++ソースは1つの
cobolcraftバイナリにコンパイルされる
Minecraftデータ抽出とJSON処理
- 公式Minecraft Java Editionサーバーおよびクライアントアプリケーションには多くのデータが含まれている
- ブロック、アイテム、エンティティタイプ
- biomes
- パケットprotocol ID
- ツルハシで採掘できるブロックのようなtags
- recipes
- ブロックを壊した際に、どの条件でどのアイテムが落ちるかを示すloot tables
- CobolCraftの
Makefileには、公式.jarをダウンロードしてデータをJSONとして抽出するターゲットがある - 抽出されたJSONデータは2つの方法で使われる
- コンパイル時にブロックloot tableなどのCOBOLコードを自動生成
- ランタイム時にデータをメモリへ読み込む
- どちらの処理でも、COBOLで書かれユニットテスト済みの汎用JSONパーサーを使用している
テストとアップデート
- ユニットテストは
tests/ディレクトリにある - テストはcopybookベースのカスタムテストフレームワークを使用する
- テストスイート、ユニット、assertionを追跡する
- 実行終了時にサマリーを提供する
- テスト実行コマンドは
make test - テストの主な目的は、デバッグが難しいJSON・バイナリデータのエンコードとデコードのような領域を検証すること
- ゲームロジック自体のテストはそれほど重要ではないと見ている
- 新しいMinecraftバージョンへサーバーを更新する手順とテスト段階は
Updating.mdにある
ライセンスと商標
- CobolCraftはMIT Licenseで配布されている
- 「Minecraft」はMojang Synergies ABの商標
- CobolCraftはMojangと提携しておらず、Mojangの承認を受けたプロジェクトでもない
1件のコメント
Hacker Newsの意見
COBOLをめぐる噂やスティグマは多いので、実際にどんな洞察を得たのか記事にしてくれるとうれしい。
自分もそういう話ばかり聞いてきたし、COBOL初心者がかなり複雑な初プロジェクトを実装する中で何に直面したのか気になる。
それでも昔のFORTRANのように空白を無視して、
DO 10 I=1.10がループ構文エラーではなくDO10I = 1.10という代入文として静かにコンパイルされるようなことはなかった。本来意図していたコードはDO 10 I=1,10だったはずなのに。興味があるなら、COBOLからC#へのコンパイラを作りながら得た内容がここにある: https://github.com/otterkit/otterkit-cobol/issues/40
今では、COBOLは高水準アセンブラにすぎないと確信するようになった。
本当に素晴らしい。
高校の卒業プロジェクトで、サッカーのベッティングオッズを自動化する完全なCOBOLシステムを作った。すでに時代遅れの技術だったが、学校はまだ時代に追いついていなかった。
とんでもなく場違いだったけれど、一行一行が好きだった。タイプするたびに「パンチカードを覚えているか?」とささやいてくる言語には、妙に満足感のあるところがある。
思い込みかもしれないが、CやここでのCOBOLのような単純で平凡な言語で書かれた、小さいけれど印象的なサイドプロジェクトをかなり頻繁に見る気がする。
逆に、似たようなRustプロジェクトはコード行数が10倍くらいあるのに、ほとんど動かないことが多いように見える。
自分の仮説は、単純な言語はアイデアを青写真のように素早く形にし、汚いコードベースでもとりあえず動かしやすいというものだ。一方で現代的な言語は、より長く持つコードを書くことを強制する。あるいは、現代的な言語が何か間違っているのかもしれない。
3〜5年開発中なのに完成していないサーバーもあるし、https://github.com/MCHPR/MCHPRS のようにレッドストーン展示用として特定機能に集中したものもある。
このCOBOLサーバーはまだライティング処理を実装しておらず、モブのスポーンもそれに依存するので、最も難しい部分の一つだ。一部のブロックも完全には実装されていない。Minecraftサーバーを完成させるには何年も必要なので、素早く何かを作ることが常に良い道とは限らない。
Rustで作業中のゲームが2つあるが、適切なエンジンを選べば、ごく最小限のゲームプレイ・プロトタイプはかなり簡単だった。だが機能を追加するほどコードは大きく膨らみ、シングルプレイからマルチプレイへ変える過程はめちゃくちゃだった。流行を追ってはまた取り除くのに時間を浪費したこともある。
Rustは、ゲームオブジェクトが複雑につながったグラフ構造を本当に嫌う。1つのゲームイベントが複数タイプの更新につながる状況のたびに摩擦が起きる。ただ受け入れて少し多めにコードを書く選択肢もあったし、初期により多くのコードを書いてでも後で時間を節約できる体系的な解法を探す選択肢もあった。
後者を選んでいくつか実験したが、損益分岐点が1人の小規模プロジェクトには合わないほど遠くにある感じがする。
同じ言語の中でも差が一桁以上出ることがある。Rustには使える3Dエンジンが2つあり、一方は有名でコントリビューターも多く、ベイエリアの給与を置き換えられるほどのスポンサー支援も受けている。もう一方はほぼ1人で作っていて、貯金でしのいだりフルタイムの仕事と交互にやったりしている。
ところが前者のエンジンは宣伝に大きく注力し、複数の機能を何年も約束しているが見せたものは少なく、後者は機能数と実装品質の両方で先を行っている。
結局は姿勢の違いだと思う。趣味でコードを書く人、明確な目標を置いて達成に集中する人、金のためにやる人、大衆的な承認に引かれる人がいる。汚いコードベースが必ず必要なわけではないが、生産的な中間地帯はある。見せびらかすことに集中する人ほど、流行や派手なアーキテクチャを追いがちだ。
Rustの中核的な前提は、開発速度と柔軟性をメモリ安全性と引き換えにすることだが、ゲーム開発では開発速度と柔軟性のほうがメモリ安全性よりはるかに重要だと分かった。
すでに細部まで青写真が固まった形式仕様のマイクロカーネルなら、Rustは素晴らしい選択肢になりうる。逆に、何が面白いゲームプレイになるのかを見るために壁へ泥を素早く投げつける必要があるなら、Rustはほとんどどんな言語よりもその過程を難しくし、利点もはっきりしない。より長い時間をかけて作った、速くて汚いゲームプレイの断片が、少しだけメモリ安全になる程度だ。
Rustでゲーム開発をやったあと、その用途から完全に離れたのは自分だけではない。たとえば “Leaving Rust gamedev after 3 years” [0] があり、これまでのRust関連のHacker News記事の中でも最も議論され、支持されたものの一つだ。
より広く見ると、RustがCobolよりはるかに誇張された注目を受けているのは明らかだ。だから、そうした誇大な流れに影響されやすい開発者、たいていは熱心な初心者たちが、Rustでオープンソースや趣味プロジェクトに果敢に挑んだ例が多い。逆にCobolでMinecraftサーバーを書くには、もう少し多くの奇抜さと度胸が必要で、それは概してより多い経験と結びついている。
[0] https://news.ycombinator.com/item?id=40172033
ちなみに、自分は自分を後者に分類している。
長く持つコードを書こうとしているうちに、ある時点で何も終わらせられない状態になったことがある。何年もかけてバランスを見つけ、ゴミのようなコードでも反復的に改善していけば最終的にはまともなものに変わると学び、それ以来そうしている。
https://raw.githubusercontent.com/meyfa/CobolCraft/main/src/...
手続き型言語の背景がある人にとっては、実際それほど理解しにくいわけではなく、約20年前に見たVBで書かれたゲームサーバーを少し思い出す
1978年にCOBOLの使用をやめてからは、この言語を知っているとも絶対に認めなかった
このコードを見ないで済むように願いながら十字を切り、濃いコーヒーを飲みに行く :-)
それでも、これをやり遂げたのは印象的
からかってもいいが、コードはかなり読みやすい
何が起きているのか理解するために何分もにらめっこしなければならない一部のモダンな言語とは対照的
社内のC++メーリングリストで、特定の1行のコードがメモリリークを起こすか尋ねたことがある。純粋にSTLテンプレートと型変換を使うコードだった。ところが専門家たちは、私の書き方が正しいかどうかで合意できなかった。リークするという人もいれば、しないと見る人もいた
この小さな実話は、C++について多くを物語っている。JavaScriptもこういうものであふれている
1976年にプログラミングを始め、COBOLとICL PLANを学び、パンチカードを使っていたが、研修を終えた後は端末を使った。プログラムは100%バッチプログラムだった
誰でもソースコードを読んで理解できるよう、可読性を非常に重視していた。ただし、プログラムが失敗したときに生成されるコアダンプを読んで理解しなければならない点が、その可読性をある程度相殺していた。よくても失敗を特定のコード行まで追跡できる程度で、そのためプログラムを頭の中で実行してみる習慣が身についた
政府機関を離れて商用プログラミングに移ったときも、80年代初めまでは依然としてCOBOLとバッチプログラムだった。3年間夜間サポートを担当したが、そのときCOBOLの価値が分かった。初めて見るリストとコアダンプを手にしても、たいていかなり早く直せた。もちろん、常に戦術的な修正にすぎないという条件付きではあった
やや冗長になり得るが、より「モダン」な言語よりずっと読みやすい
少なくとも理論上はそうだった
MOVE FUNCTION MIN(BLOCK-ENTRY-MINIMUM-STATE-ID(LK-BLOCK), STATE-ID) TO BLOCK-ENTRY-MINIMUM-STATE-ID(LK-BLOCK)高校時代、パキスタンの小さな都市でCOBOLを少し学んだ
悪くはなく、財務諸表をまねるプロジェクトをやった。奇妙なところはあったが、たいていの人間、つまり非プログラマーにとっては、どんなプログラミング言語でもかなり奇妙なはずなので、COBOLに付いた悪評はよく理解できない
同じ時期にCも学び、そちらはその後も残った :-)
Cobolプログラマーは希少で高給を得る、といつも聞く
このプロジェクトのせいで求人オファーが殺到したのか気になる
Cobolは非常に複雑な業務運用でよく使われる
そうでなければ、クロスコンパイラを1つ作って終わりにできたはずだ
COBOLは実際、かなりクールな言語に見える
コードも本当によく整理されている
ユニットテストがある点が気に入った