Zigでライフゲームを作ってみる
Zigがずっと気になっていたので、とりあえずライフゲームを作ってみることに。
作っている最中に思ったことなどの備忘録的な記事です。詳しい実装については記しておりません。
ライフゲーム
セルオートマトンと呼ばれるものの一種。
単純なルールで生命の誕生や進化、淘汰などをシミュレーションしたもの。
詳細はwikipediaを参照してください。
私はニコニコ動画のライフゲームの世界という動画で知りました。この動画とても面白いのでおすすめです。
Zig
ここ最近、新しいJavascriptランタイムであるBunが話題となり、そこでZigが使われていたことによりよく目にするようになりました。
実際にどんな言語なのか触っていましたが、低レイヤーの言語をほとんど触らないせいでかなり苦戦しました...
兎にも角にもHello, world
const print = @import("std").debug.print;
pub fn main() void {
print("Hello, world!\n", .{});
}
debug.print
は、第一引数に文字列のフォーマット、第二引数に文字列の中で表示したい値の配列を受け取る。ここでは特に表示したい値はないので空の配列.{}
を渡している。
.{}
の.
は型の省略を意味しているようです。
例えば、Zigの配列の初期化は次のように表せますが、
// array literal
var array: [4]u8 = [_]u8{ 11, 22, 33, 44 };
変数とともに型も宣言しているので、配列リテラルの方は型を省略できる。
// array literal
var array: [4]u8 = .{ 11, 22, 33, 44 };
配列の初期化
セルオートマトンは、二次元配列にbool型の値を持たせることで表現する。
Zigで配列の持つ値を全て同じにするには、
// initialize an array to zero
const all_zero = [_]u16{0} ** 10;
ただ、二次元配列では簡単な書き方が見つからなかったので、普通に配列を回すのが良いかも。
乱数
乱数を使うために、Zigの標準ライブラリを見てみましょう。
検索欄でrand
と検索すると、std.rand
が出てきました。
DefaultPrngという名前でPRNG(擬似乱数生成器)が用意されているので、こちらを使わせていただきましょう。(ドキュメントの内容はあまり充実していないように見えたので、ソースコードのコメントを見た方が欲しい情報が手に入れられそうです。)
タイムスタンプをseedに使いました。
このような感じで乱数を使うことができます。
const std = @import("std");
const prng = std.rand.DefaultPrng;
const time = std.time;
const print = std.debug.print;
pub fn main() void {
var rand = prng.init(@intCast(u64, time.milliTimestamp()));
print("{}\n", .{rand.random().int(u32)});
}
配列を関数に渡す
何も考えずに配列を渡したらエラッた。
const std = @import("std");
const print = std.debug.print;
fn printArray(array: []i32) void {
for (array) |value| {
print("{}", .{value});
}
}
pub fn main() void {
const array = [_]i32{ 3, 1, 4, 1, 5, 9, 2 };
printArray(array);
}
❯ zig run main.zig
./main.zig:12:16: error: expected type '[]i32', found '[7]i32'
printArray(array);
仮引数側の配列の型をconst
にして、実引数で配列へのポインタを渡せば良いらしい。
const std = @import("std");
const print = std.debug.print;
fn printArray(array: []const i32) void {
for (array) |value| {
print("{}", .{value});
}
}
pub fn main() void {
const array = [_]i32{ 3, 1, 4, 1, 5, 9, 2 };
printArray(&array);
}
❯ zig run main.zig
3141592
ここら辺が理解不足でよくわかっていない。
配列のコピー
@memcpy
という低レベル組み込み関数があるようですが、これには安全機構がないので基本的には使わずに、次のように書いてくださいとあります。
const array1 = [_]i32{ 3, 1, 4, 1, 5, 9, 2 };
var array2: [array1.len]i32 = undefined;
for (array1) |b, i| array2[i] = b;
オプティマイザは上記の書き方を@memcpy
に変えてくれるそうです。
また、標準ライブラリもあります。
const mem = @import("std").mem;
const array1 = [_]i32{ 3, 1, 4, 1, 5, 9, 2 };
var array2: [array1.len]i32 = undefined;
mem.copy(i32, &array2, &array1);
ライフゲームを作る過程で主に詰まったところは以上になります。
感想
普段はPythonでコードを書くことが多いのでなかなか苦戦しました。ただ、Rustにチャレンジしてみた時よりは苦しみが少ないように感じました。C言語を書き慣れている人にとっては全く問題ないかもしれません。
低レイヤーに近い言語はプログラミングしている感があって楽しいですね。
RustやZig、最近ではCarbonなるものが出てきていますが、Web技術だけでなく低レイヤーの方の技術も騒がしくなっているように思うので、これからの進化が楽しみです。
作ったもの
せめて構造体を使うように書き直したい...
debug.print
で表示しているのをなんとかしたい...
Discussion