rust触ってみる

Rust触ってみる

2026/02/02
2026/02/02
関連ワード:rustゲーム

rustの勉強がてらライフゲームを作ったので軽く紹介

ソースコードは↓

https://github.com/yamu77/rust-life-game

ライフゲームについては説明しないので各々で調べてもらえると

一部コード説明

rust
1#[derive(Clone, Copy, PartialEq)]
2enum GameState {
3    Menu,       // メニュー選択
4    Editing,    // 編集モード
5    Running,    // 実行中
6    Paused,     // 一時停止
7}

状態は

  • メニュー選択画面(初期状態)
  • 編集時の画面
  • ライフゲーム進行中
  • 一時停止

の4つで保持

rust
1fn count_neighbors(&self, x: usize, y: usize) -> usize {
2    let mut count = 0;
3    for dy in -1..=1 {
4        for dx in -1..=1 {
5            if dx == 0 && dy == 0 {
6                continue;
7            }
8            let nx = x as i32 + dx;
9            let ny = y as i32 + dy;
10            if nx >= 0 && nx < BOARD_SIZE as i32 && ny >= 0 && ny < BOARD_SIZE as i32 {
11                if self.board[ny as usize][nx as usize] {
12                    count += 1;
13                }
14            }
15        }
16    }
17    count
18}
19
20fn next_generation(&mut self) {
21    let mut new_board = [[false; BOARD_SIZE]; BOARD_SIZE];
22    for y in 0..BOARD_SIZE {
23        for x in 0..BOARD_SIZE {
24            let neighbors = self.count_neighbors(x, y);
25            let alive = self.board[y][x];
26            new_board[y][x] = match (alive, neighbors) {
27                (true, 2) | (true, 3) => true,  // 生存
28                (false, 3) => true,             // 誕生
29                _ => false,                     // 死亡または維持
30            };
31        }
32    }
33    self.board = new_board;
34}

ライフゲームは自分の周囲8マスの生死の状態を見て自分の状態が決まるので

単純に2重ループ回して生存数をカウントして次の状態を決定

rust
1fn render_menu(&self, stdout: &mut io::Stdout) -> Result<()> {
2    let board_width = BOARD_SIZE * CELL_SIZE;
3    let menu_x = (board_width + 5) as u16; // 盤面の右側にメニューを配置
4    let menu_y = 5u16;
5
6    // タイトル
7    execute!(
8        stdout,
9        MoveTo(menu_x, menu_y - 2),
10        SetForegroundColor(Color::Cyan),
11        Print("メニュー")
12    )?;
13
14    // メニュー項目
15    let menu_items = [
16        ("編集", MenuOption::Edit),
17        ("スタート", MenuOption::Start),
18        ("終了", MenuOption::Exit),
19    ];
20
21    for (i, (text, option)) in menu_items.iter().enumerate() {
22        let y = menu_y + i as u16;
23        
24        if self.menu_selection == *option {
25            execute!(
26                stdout,
27                MoveTo(menu_x, y),
28                SetBackgroundColor(Color::Yellow),
29                SetForegroundColor(Color::Black),
30                Print(format!("> {} <", text))
31            )?;
32        } else {
33            execute!(
34                stdout,
35                MoveTo(menu_x, y),
36                SetBackgroundColor(Color::Reset),
37                SetForegroundColor(Color::Reset),
38                Print(format!("  {}  ", text))
39            )?;
40        }
41    }
42
43    Ok(())
44}

ターミナルで動かすのでcrosstermクレートのMoveToでカーソルを動かし、SetBackgroundColorとSetForegroundColorで背景色やら文字色やらを変えながら画面を表示

rust
1fn main() -> Result<()> {
2    enable_raw_mode()?;
3    let mut stdout = stdout();
4    execute!(stdout, Hide)?;
5
6    let mut game = Game::new();
7    let mut last_update = Instant::now();
8    let update_interval = Duration::from_millis(200); // 世代更新の間隔
9
10    loop {
11        // キー入力のポーリング(非ブロッキング)
12        if event::poll(Duration::from_millis(10))? {
13            if let Event::Key(KeyEvent {
14                code,
15                kind,
16                ..
17            }) = event::read()? {
18                // PressとRepeatの両方を処理
19                if kind != KeyEventKind::Release {
20                match code {
21                    KeyCode::Up => {
22                        match game.state {
23                            GameState::Menu => {
24                                game.move_menu_selection(true);
25                            }
26                            GameState::Editing | GameState::Paused => {
27                                if game.cursor_y > 0 {
28                                    game.move_cursor(0, -1);
29                                }
30                            }
31                            _ => {}
32                        }
33                    }
34                    KeyCode::Down => {
35                        match game.state {
36                            GameState::Menu => {
37                                game.move_menu_selection(false);
38                            }
39                            GameState::Editing | GameState::Paused => {
40                                if game.cursor_y < BOARD_SIZE - 1 {
41                                    game.move_cursor(0, 1);
42                                }
43                            }
44                            _ => {}
45                        }
46                    }
47                    KeyCode::Left => {
48                        if game.state == GameState::Editing || game.state == GameState::Paused {
49                            if game.cursor_x > 0 {
50                                game.move_cursor(-1, 0);
51                            }
52                        }
53                    }
54                    KeyCode::Right => {
55                        if game.state == GameState::Editing || game.state == GameState::Paused {
56                            if game.cursor_x < BOARD_SIZE - 1 {
57                                game.move_cursor(1, 0);
58                            }
59                        }
60                    }
61                    KeyCode::Enter => {
62                        match game.state {
63                            GameState::Menu => {
64                                match game.menu_selection {
65                                    MenuOption::Edit => {
66                                        game.enter_editing();
67                                    }
68                                    MenuOption::Start => {
69                                        game.start_game();
70                                    }
71                                    MenuOption::Exit => {
72                                        // 終了処理
73                                        execute!(stdout, Clear(ClearType::All))?;
74                                        execute!(stdout, MoveTo(0, 0))?; // カーソルを一番左に移動
75                                        execute!(stdout, Show)?;
76                                        disable_raw_mode()?;
77                                        return Ok(());
78                                    }
79                                }
80                            }
81                            GameState::Editing => {
82                                if game.cursor_y < BOARD_SIZE {
83                                    game.toggle_cell();
84                                }
85                            }
86                            GameState::Running | GameState::Paused => {
87                                game.enter_editing_from_running();
88                            }
89                        }
90                    }
91                    KeyCode::Char(' ') => {
92                        match game.state {
93                            GameState::Running => {
94                                game.pause();
95                            }
96                            GameState::Paused => {
97                                game.resume();
98                            }
99                            _ => {}
100                        }
101                    }
102                    KeyCode::Esc => {
103                        match game.state {
104                            GameState::Editing | GameState::Running | GameState::Paused => {
105                                game.return_to_menu();
106                            }
107                            _ => {}
108                        }
109                    }
110                    _ => {}
111                }
112                }
113            }
114        }
115
116        // 実行中は一定間隔で世代更新
117        if game.state == GameState::Running {
118            if last_update.elapsed() >= update_interval {
119                game.next_generation();
120                last_update = Instant::now();
121            }
122        }
123
124        // 画面を描画
125        game.render(&mut stdout)?;
126    }
127}

あとはキー入力を受け付けて状態遷移やセルの生死切り替えをできるようにして、無限ループで画面を更新

rustを触ってみて

所有権だったりトレイトだったりほかの言語だと聞かない用語が多くてハードルは高く感じたけど、軽く何かを作る分には少し慣れてきた程度でなんだかんだ作れる。

理解があやふやなだったり今回はあまり気にしなくていい概念とかrustの機能もあるだろうし、時間があるときにもう少し重めの開発もやってみたい