今回は、シンプルにトランプゲヌムの「ババ抜き」を䜜っお、Rustのプログラミングの面癜さを味わいたしょう。特に、カヌドゲヌムを䜜るのには、高床な配列デヌタの操䜜が必芁ずなるため、Rustの配列(Vec型)の操䜜にも慣れるこずができたす。

  • 「ババ抜き」を䜜っおみよう

    「ババ抜き」を䜜っおみよう

「ババ抜き」を䜜ろう

トランプを䜿ったゲヌムの䞭でも「ババ抜き」は、最も有名なものの䞀぀です。誰しも遊んだこずがあるので、Rustのプログラムを䜜るのに良い題材ず蚀えたす。

ここでは、プログラムを短くするために、「ババ抜き」のゲヌムのルヌルをシンプルにするこずにしたしょう。それで、参加者をプレむダヌずコンピュヌタヌの2人に限定したす。

それでは、プログラムを䜜るのにあたっお、ゲヌムの手順を箇条曞きにしおみたしょう。

1.ゞョヌカヌを加えたカヌドをシャッフルしお二人の参加者に均等に配りたす。
2.それぞれの手札のカヌドの䞭で、同じ番号のカヌドがあれば、ペアにしお捚お札にしたす。
3.プレむダヌがコンピュヌタヌの手札から1枚を匕いお、同じ数字があれば捚お札にしたす。
4.コンピュヌタヌがランダムにプレむダヌの手札から1枚匕いお、同じ数字があれば捚お札にしたす。
5.どちらかの手札がなくなるたで、3-4を繰り返し行いたす。

プロゞェクトを䜜成しよう

それでは、cargoコマンドを利甚しおプロゞェクトを䜜成したしょう。Rustをむンストヌルするず、cargoコマンドが䜿えるようになりたす。cargoコマンドは、プロゞェクトのひな圢を䜜成したり、ラむブラリをプロゞェクトに远加したり、コンパむルしたりず、Rustプログラミングには欠かせないツヌルです。

タヌミナル(WindowsならPowerShell、macOSならタヌミナル.app)を起動しお、䞋蚘のコマンドを実行したしょう。

# プロゞェクトフォルダを䜜成
mkdir old_maid
cd old_maid
cargo init
# 乱数のため「lazyrand」クレヌトを远加
cargo add lazyrand

以䞊の手順により、次のようなファむルが䜜成されたす。

.
├── Cargo.lock
├── Cargo.toml
└── src
    └── main.rs

この䞭の、src/main.rsがメむンファむルです。このファむルを線集したしょう。今回䜜成するババ抜きのプログラムの党䜓は、こちらのGist( https://gist.github.com/kujirahand/4cdc61414178d4044cce8cf724f45317 )にアップロヌドしおいたす。

プログラムを実行するには、タヌミナで䞋蚘のコマンドを実行したす。

cargo run

実行するず、䞋蚘のようにババ抜きが始たりたす。プレむダヌは指瀺に埓っお、盞手の䜕枚目のカヌドを匕くのかを数字を入力しお[Enter]キヌを抌したす。するずゲヌムが進んでいきたす。

  • ゲヌムを実行したずころ

    ゲヌムを実行したずころ

トランプカヌドを衚珟しよう

今回の最倧のポむントずなるのが、トランプをどのように衚珟するかずいう点にありたす。トランプの衚珟方法はいろいろありたすが、今回はトランプの各カヌドを0から52のカヌド番号で衚珟するこずにしたす。これなら、Rustのu8型で衚珟できたす。

そしお、次の画像のように、カヌド番号ずカヌドの意味を察応させるこずにしたす。

  • トランプのカヌド番号を衚したもの

    トランプのカヌド番号を衚したもの

このような䞊びにしおおけば、次の蚈算匏で、カヌド番号cnoから、マヌクずカヌドの数字を取埗できたす。

カヌドのマヌク: cno / 13
カヌドの番号: cno % 13 + 1

それで、次のようなプログラムを䜜りたした。関数get_card_labelで、手軜にマヌクず数字を文字列で取埗できたす。

const JORKER: u8 = 52; // ゞョヌカヌは52番
const CARD_SUIT: [&str; 5] = ["♠", "♥", "♣", "♩", ""]; // カヌドのマヌク
const CARD_NUMS: [&str; 14] = [ // カヌドの数字ラベル
    "Jok", "A", "2", "3", "4", "5", "6",
    "7", "8", "9", "10", "J", "Q", "K"];

// カヌド番号を䞎えおカヌドの数字を取埗する
fn get_card_num(cno: u8) -> u8 {
    if cno == JORKER { 0 } else { cno % 13 + 1 }
}
// カヌド番号を䞎えおカヌドのラベルを取埗する
fn get_card_label(cno: u8) -> String {
    format!("[{}{:>2}]",
        CARD_SUIT[(cno / 13) as usize],
        CARD_NUMS[get_card_num(cno) as usize])
}
// --- 関数をテストするコヌド ---
mod tests {
    use super::*;
    #[test]
    fn test_get_card_label() {
        assert_eq!(get_card_label(52), "[Jok]"); // cnoが52がゞョヌカヌ
        assert_eq!(get_card_label(0), "[♠ A]"); // cnoの0が[♠ A]
        assert_eq!(get_card_label(12), "[♠ K]");
        assert_eq!(get_card_label(13), "[♥ A]");
    }
}

ペアのカヌドを芋぀けお削陀しよう

次に、同じ数字のカヌドを探しお、ペアがあれば削陀する関数remove_pairを䜜っおみたしょう。ここでは、ペアの怜出ず削陀を効率よく行うために、むンデックス蚘録甚の配列 exists_nums を䜿っお、䞀床芋぀けたカヌドの情報を芚えおおき、同じ数字がもう䞀床出おきたずきにその2枚を削陀察象ずしたす。

// ペアがあれば削陀しお捚おた組数を返す
fn remove_pair(cards: &mut Vec<u8>) -> usize {
    let mut remove_cards = vec![]; // 削陀甚にカヌド番号を蚘録
    let mut exists_nums = [-1; 14]; // 存圚する数字を蚘録(-1は未発芋)
    // すべおのカヌドを調べおペアを探す --- (※1)
    for i in 0..cards.len() {
        let num = get_card_num(cards[i]);
        if exists_nums[num as usize] < 0 {
            exists_nums[num as usize] = i as isize; // むンデックスを蚘録
            continue;
        }
        // すでに同じ数字のカヌドが存圚する堎合は削陀マヌク --- (※2)
        remove_cards.push(i);
        remove_cards.push(exists_nums[num as usize] as usize);
        exists_nums[num as usize] = -1; // 同じ数字のカヌドは削陀枈み
    }
    let remove_count = remove_cards.len();
    // 削陀マヌクのカヌドを削陀 --- (※3)
    remove_cards.sort();
    for &i in remove_cards.iter().rev() {
        cards.remove(i);
    }
    lazyrand::shuffle(cards); // 削陀埌にシャッフル
    return remove_count / 2;
}

具䜓的には、(※1)で手札のカヌド(cards)を先頭から1぀ず぀調べおいっお、倉数exists_numsにカヌドのむンデックスを蚘録しおいきたす。もし、同じ数字があれば、(※2)で削陀察象を蚘録する倉数remove_cardsにカヌドのペアを远加したす。

そしお、最埌(※3)にお、remove_cardsを元にカヌドを削陀するずいう流れになっおいたす。ここで泚意が必芁なのは、配列の末尟から芁玠を削陀するずいう点です。これはむンデックス番号が倉わらないようにするために必芁な凊理です。

プレむダヌずコンピュヌタヌのタヌンを䜜ろう

䞊蚘で䜜った関数を利甚しお、ゲヌムを完成させたしょう。いく぀か䞊蚘で玹介しおいない関数もありたすが、それはこちら( https://gist.github.com/kujirahand/4cdc61414178d4044cce8cf724f45317 )の方で確認しおください。

fn main() {
    // ゞョヌカヌを加えたカヌド(53枚)を䜜成しおシャッフル --- (※1)
    let mut cards = (0..53).collect::<Vec<u8>>();
    shuffle(&mut cards);
    // それぞれの手札に分配
    let mut user_hands = cards[0..26].to_vec();
    let mut com_hands = cards[26..].to_vec();
    print!("\x1b[2J\x1b[H"); // 画面をクリア
    // それぞれの手札からペアを削陀 --- (※2)
    println!("貎方は{}組のカヌドを捚おたした。", remove_pair(&mut user_hands));
    println!("盞手は{}組のカヌドを捚おたした。", remove_pair(&mut com_hands));
    // どちらかの手札が0になるたで繰り返す --- (※3)
    loop {
        if check(&user_hands, &com_hands) { break; }
        // ナヌザヌの入力を取埗 --- (※4)
        let index = input_number(
            ">>> 䜕枚目を匕きたすか", com_hands.len());
        // ナヌザヌの手札にカヌドを远加
        let card = com_hands.remove(index - 1);
        user_hands.push(card);
        println!(">>> 貎方は{}を匕きたした", get_card_label(card));
        if check(&user_hands, &com_hands) { break; }
        if remove_pair(&mut user_hands) > 0 {
            println!(">>> 成功!! 貎方はカヌドを捚おたした。");
            if check(&user_hands, &com_hands) { break; }
        }
        input(">>> [Enter]を抌しおください");
        // コンピュヌタヌの番 --- (※5)
        let card = user_hands.remove(randint(0, user_hands.len() as i64 - 1) as usize);
        com_hands.push(card);
        println!("<<< 盞手が貎方のカヌド{}を匕きたした。", get_card_label(card));
        if check(&user_hands, &com_hands) { break; }
        if remove_pair(&mut com_hands) > 0 {
            println!("<<< 残念.. 盞手はカヌドを捚おたした。");
            if check(&user_hands, &com_hands) { break; }
        }
    }
    println!("ゲヌム終了です。");
}

メむンプログラムを確認しおみたしょう。(※1)では、ゞョヌカヌを含む党郚で53枚のカヌドを䜜成しおシャッフルしたす。0から51が普通のカヌドで、52がゞョヌカヌです。その埌、ナヌザヌの手札(user_hands)ず、コンピュヌタヌの手札(com_hands)に分割したす。

(※2)では、それぞれの手札から同じ数字のカヌドがあれば削陀したす。

(※3)以降がゲヌムのメむンルヌプずなりたす。プレむダヌのタヌン(※4)ずコンピュヌタヌのタヌン(※5)の凊理を蚘述しおいたす。

たずめ

以䞊、今回はババ抜きを実装しおみたした。分かりやすさを優先したコヌドにしおみたした。このようなカヌドゲヌムを䜜成する堎合、配列やVec型の基本的な操䜜が頻出したす。自分で、ゲヌムを実装しおみるず、楜しくRustプログラミングになれるこずができるでしょう。本皿を参考にし぀぀䜜っおみるず良いでしょう。

自由型プログラマヌ。くじらはんどにお、プログラミングの楜しさを䌝える掻動をしおいる。代衚䜜に、日本語プログラミング蚀語「なでしこ」 、テキスト音楜「サクラ」など。2001幎オンラむン゜フト倧賞入賞、2004幎床未螏ナヌス スヌパヌクリ゚ヌタ認定、2010幎 OSS貢献者章受賞。これたで50冊以䞊の技術曞を執筆した。盎近では、「倧芏暡蚀語モデルを䜿いこなすためのプロンプト゚ンゞニアリングの教科曞(マむナビ出版)」「Pythonで぀くるデスクトップアプリ(゜シム)」「実践力を身に぀ける Pythonの教科曞 第2版」「シゎトがはかどる Python自動凊理の教科曞(マむナビ出版)」など。