はじめに
記事を書くの大変なので、多少雑になってしまうかもしれないです。自分の備忘録用程度で興味がある方は参考にしてくださったら嬉しいです。
経緯
自分のことを書くのが好きなので、ここからタラタラと自分語りが始まりますが、悪しからず。
自分はチェスが好きです。コロナ禍でチェスを chess.com のサイトでするようになって面白いな思って、結構熱心にプレイしてました(今はかつてほどの熱はない)。
当時、Netflix の Queen's Gambit っていうドラマのブレイクの影響もあり、チェスが結構流行ってました。Queen's Gambit まだ見てない方はチェス知らない方でも、超面白いと思うのでおすすめです。
なぜチェスエンジン
チェスの歴史をちょっと語らせてください。チェスってコンピュータサイエンスとの結びつき、結構あります。アラン・チューリングは、1948年にチェスのコンピュータプログラムを書いていたらしいです。
Wikipedia の抜粋:
1948年、当時まだ存在していなかったコンピュータチェスのプログラムを書き始める。1952年、当時のコンピュータは性能が低くそのプログラム実行には適さなかったため、自分でコンピュータをシミュレートしてチェスの試合を行ったが、一手打つのに30分かかったという。その対戦の棋譜が残っている[69]。同僚との対戦ではプログラムが負けているが、別の同僚の妻にはプログラムが勝利している。
あと、情報理論の父であるクロード・シャノンも
1949年にコンピュータチェスに関する画期的な論文「チェスのためのコンピュータプログラミング」[14]を発表し、力ずくの総当たりでなくコンピュータがチェスをする方法を示した。コンピュータがどの駒をどう移動するかを決定するのにシャノンが用いた方法が、評価関数に基づいたミニマックス法だった。評価関数は、駒の価値や、駒の位置の価値、移動の価値などをすべて数値化して「局面」の価値を評価するものであり、シャノンはその後のゲーム展開を探索木(Search tree)に分類してどの着手がもっとも良いかを探索する方法について考察している。この論文はコンピュータゲームでのコンピュータの思考プログラム設計の原典となった。
ちょうどアランチューリングと同じ頃ですね。と言うかここで、評価関数に基づく、ミニマックス法を提唱していたのか。。天才すぎる。(ミニマックス法は今度元気なときに記事にしたいと思っています)
現代で言うと、ディープマインドの創業者であり、2024年のノーベル賞受賞者であるデミス・ハサビスもチェスの神童と言われていて、コンピュータチェスのプログラムを書いてたと聞いたことがあります。
He first got interested in technology after buying his first computer in 1984, a ZX Spectrum 48K, funded from chess winnings. He taught himself how to program from books.[24] He subsequently wrote his first AI program on a Commodore Amiga to play the reversi board game.
頭が良い人はチェスが好き
だから、結局頭が良い人ってチェスが好きだし、チェスのプログラムを書きたがると言うことなんですよね。チェスってなんと表現したらいいのか分からないけど、幾何学的にシンプルなコマから構成されているところにロマンがあり、コンピュータとの相性が非常に良い。
チェスのボードも 8 x 8 なので、プログラミングに非常に向いています。ここまで完璧なゲームがあるのか。と言うことで、自分の勉強のためにもチェスのプログラミングを書くことは非常に良いと思いました。
チェスプログラムを書くことは前から興味があって、2024年の初頭にも挑戦していたのですが(その時は Pythonで)、あまりうまくいかず挫折しました。
今だからこそ、もっとプログラミングに詳しくなったので、再度コンピュータチェスのプログラムを書くことに挑戦しようと思ったわけです!
そして、今回選んだ言語は Rust です。いやいや、Rust 難しいからもっと簡単な言語からやれよって思われたかもしれないですが、自分はにわかながら Rust も面白そうだからやってみたいなとずっと思っていて、勉強できていないところでした。
そんな中だったので、チェスのプログラムを書いて、Rust も詳しくなるのは絶好のチャンスじゃん!一石二鳥やんとなったわけです。
Rust の魅力
別にチェスのエンジンを開発するときに Rust の良さがどこまで生きてくるかどうかわかりませんが、chess.com と並んで、人気を誇る lichess.org と言うオープンソースのチェスのサイトは、結構 Rust で書かれています。これ がリポジトリ。Rust は書けるやつが少ないから保守性が悪くて敬遠されがちですが、だからこそ魅力がある。ロマンのチェスとロマンの Rust でプログラムを書いたら、きっと面白いものができるはず。。
Rust の良さはやっぱり型安全性
並列処理で走らせるプログラミングなんかは思わぬところで、メモリリークが発生したり、エラーが起きたりして危険だと聞くことがあります(超ざっくり)。そうした点をカバーできる Rust は強い。Rust とチェスの型安全性や並行処理の得意さ、Rust による WASM のポテンシャルを考えると結構相性が良いんじゃないかと思います。(WASM はいわゆるアレです。プログラムを javaScript に変換して web で走るようにできるやつ)
まぁあんまり難しいことは考えずに、とりあえずダイブしてから考えるのが一番です。と言うわけで始めました。
これがリポジトリです。サボってそうだったら、コメントして起こしてください。
方針
1. github 使う
git の勉強がてら、割ときちんと git で issue とか作って、開発していこうかと思います。ブランチ戦略とか PR もそれなりにやってみて、感覚を確かなものにしたい。
2. 毎日1行以上
楽に目標設定するのがコツ。特に自分は燃え尽きやすいタイプなので、毎日一行以上のコードが書けたら大成功です。
3. AI 使うけど、自分で反芻してもう一度コードを再現してみる
AI 超便利。前の記事 でも書いたけど。ただ、便利すぎて頼りすぎると頭が悪くなるので、AI が出してきたコードをまず理解して、疑問に感じた部分を徹底的に質問する。特に Rust は謎が多いから。
-> 質問していくうちにわかってきて、質問の数が減ってコードが見れるようになる。
-> 自分で、AI が出したコードを理解したら、fn とかを再現してみる。
やったこと
2026/1/13
例えば、get_piece と言う関数が Board と言う impl (クラスみたいなやつ、Rust の中では固有実装と呼ばれている)の中に存在します。
この get_piece の役割は、名前から察する通り、あるボードが与えられたときに、あるマスに存在する駒を取得するものです。
もちろん、その駒が存在しない可能性があるので、get_piece が返す戻り値は Piece ではなく Option<Piece> です。
この Option<T> は、与えられた型の値が存在する時は Some を返して、ない時は None を返します。難しいですが、要は駒があるかないか分からないときには Option<Piece> を返すようにしとけばいいと言うことです。
実際に、get_piece の関数の正解は以下のようになります。
pub struct Board {
squares: [[Option<Piece>; 8]; 8],
side_to_move: Color,
}
impl Board {
...
pub fn get_piece(&self, square: Square) -> Option<Piece> {
let (row, col) = square;
if (row < 8 && col < 8) {
// 盤内:戻り値として Some(Piece) か None が返る
self.squares[row as usize][col as usize]
} else {
// 盤外:駒はない
None
}
}
...
}
コードの説明ですが、仕組みさえわかってしまえば簡単です。
最初の部分
上の pub struct Board で Board の型を宣言してます。
-
pubは外のファイルからでも使えるようにしてる -
structは Rust では、固有の型を持たせるときに使う(一般的なものを使う場合は、Type)
だから、Board という独自の型を作りますよ〜といっているわけです。
impl Board
get_piece の引数に &self がありますが、self: &Self の省略形です。実際には Board が渡されているので、impl の外で、引数に Board を持たせる形で board: &Board としても正しいプログラムですが、モジュール内で定義した方がいちいち中で使っている値を pub にする必要がないので利便性が高く、そうなっています(オプジェクト指向)。
&(アンド)マークが先頭についています。これがいわゆる借用というやつです。(アンドは共有みたいな感じがするからそのイメージで)
get_piece の関数の呼び出しもとである Board の値の所有権を借用しているのです。
-
所有権とか借用とか難しい単語ですが、所有権は、その値の持てる権利(なぜ重要かというとメモリが割り当てられているから、土地を持てる権利みたいなもんです)で、借用はその土地を他の人に貸してあげることができる仕組みです。貸せないと一人しかその値を持てないので、同時にいろんなところで使うことができずに、処理が超スタックします。それを解消するための仕組みとして、借用が導入されています。
-
この借りた値を変えようとするとコンパイル時にエラーが起こります。(なぜなら他の人も同時に使っている可能性があり、整合性が合わなくなるから)この仕組みによって堅安全性を保っているわけです。
-
だったら、どうやって値を変えればいいねん!っていう話ですが、これをするためには
&mutを頭につける必要があります。これをつけると、「今からこの値変えますよ〜」という宣言になるので、他のやつがまだその値を持っていた場合はコンパイルエラーになります。変える際には、そいつしか持ってはならないというのが型安全が担保される仕組みの根幹にあります。
-> の後に来るものは戻り値です。今回は Option<Piece> が返ってくることがわかります。Piece が確実に入っているかどうか分からないので、Option がついているのは先ほど説明した通りです。
let (row, col) = Square は引数で受け取った Square を代入しています。Square は型が Type なので借用気にすることなく、代入できます。Type は Struct にする必要がない Square (例:(3, 5))のようなポインタを持たない単純な値の管理に使われます。こちらはコピーしても型安全が担保されます。一方で、ポインタを持った値は所有権により管理しないと、自由にコピーしているとダングリングポインタ(余剰なポインタ)が発生して、二重解放しようとしてクラッシュする可能性があります。
if (row < 8 && col < 8) {
// 盤内:戻り値として Some(Piece) か None が返る
self.squares[row as usize][col as usize]
} else {
// 盤外:駒はない
None
}
の部分ですが、盤の内側であれば self(board)の sqaures に [row の番号] と [col の番号] を渡すことで Option<Piece> が返り値になります(usize は配列のインデックスを示すための整数です)。駒があれば、Some(Piece) が、駒がなければ None が返るようになっています。
ちなみに上のコードスニペットは今回一切見直さずに、思い出して、再現できました。何度か反復練習したおかげだと思います😊
将来はゲームにしたい
ローグライク系のゲームがここ5年くらい流行ってる思います。自分もたまにヴァンサバとか Balatro とかプレイするんですが、ローグライク面白いなぁと思って、
チェスをテーマにしたローグライク系のゲームがあったら結構面白いんじゃないでしょうか。
まぁ多分すでにそういったゲームって世の中に存在しちゃってると思ったのですが、それを見るとやる気が削がれてしまうので、あえて調べてません。
世の中がまだ知らない世紀のアイデアを見つけたくらいの勢いで頑張って作ってみたいです。