Skip to content

Instantly share code, notes, and snippets.

@kujirahand
Created November 30, 2023 07:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kujirahand/30a59131b2d949fe556c5050f7340240 to your computer and use it in GitHub Desktop.
Save kujirahand/30a59131b2d949fe556c5050f7340240 to your computer and use it in GitHub Desktop.
Rustで作ったターミナル迷路ゲーム
extern crate rand;
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::io::{stdout, Result};
use crossterm::{
cursor, execute, ExecutableCommand, terminal,
style::{Color, Print, ResetColor, SetBackgroundColor, SetForegroundColor},
event::{read, Event, KeyCode},
};
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Cell {
Wall,
Empty,
}
pub struct Maze {
pub cols: isize,
pub rows: isize,
pub data: Vec<Cell>,
}
impl Maze {
// 空の迷路を生成する
pub fn new(cols: isize, rows: isize) -> Maze {
let mut data = vec![];
for _ in 0..rows * cols {
data.push(Cell::Wall);
}
Maze { cols, rows, data }
}
// (x, y)が迷路の範囲内かどうかを判定
fn in_range(&self, x: isize, y: isize) -> bool {
(x >= 0 && x < self.cols as isize) && (y >= 0 && y < self.rows as isize)
}
// (x, y)のセルを設定する
fn set(&mut self, x: isize, y: isize, cell: Cell) {
if !self.in_range(x, y) { return; }
self.data[(y * self.cols + x) as usize] = cell;
}
// (x, y)のセルを取得する
fn get(&self, x: isize, y: isize) -> Cell {
if self.in_range(x, y) {
self.data[(y * self.cols + x) as usize]
} else {
Cell::Wall
}
}
// 再帰的に迷路を生成
fn generate(&mut self, x: isize, y: isize) {
self.set(x, y, Cell::Empty);
let mut dirs = vec![(1, 0), (-1, 0), (0, 1), (0, -1)];
let mut rng = thread_rng(); // ランダムジェネレータを作成
dirs.shuffle(&mut rng); // 進行方向を選ぶ
for (dx, dy) in dirs {
// 2マス先が範囲外か通路ならスキップ
let (nx, ny) = (x + dx * 2, y + dy * 2);
if !self.in_range(nx, ny) || self.get(nx, ny) == Cell::Empty { continue; }
// 進行方向を掘って、再帰的に迷路を生成
self.set(x + dx, y + dy, Cell::Empty);
self.generate(nx, ny);
}
}
}
// 迷路を表示する
fn display(maze: &Maze, px: isize, py: isize) -> Result<()> {
let put_ch = |x, y, fg, bg, ch| -> Result<()> {
execute!(
stdout(),
cursor::MoveTo(x as u16, y as u16),
SetForegroundColor(fg),
SetBackgroundColor(bg),
Print(ch),
)
};
for y in 0..maze.rows {
for x in 0..maze.cols {
// プレイヤーを描画
if x == px && y == py {
put_ch(x, y, Color::Yellow, Color::Black, "@")?;
continue;
}
// マップを描画
match maze.get(x, y) {
Cell::Wall => put_ch(x, y, Color::Black, Color::DarkMagenta, "#")?,
Cell::Empty => put_ch(x, y, Color::Black, Color::Black, " ")?,
}
}
execute!(stdout(), Print("\r\n"), ResetColor)?;
}
Ok(())
}
fn maze_main() -> Result<()> {
// ターミナルのサイズを取得
let (cols, rows) = terminal::size()?;
// 画面サイズ内で最大の奇数にする
let cols = (cols as isize) / 2 * 2 - 1;
let rows = (rows as isize) / 2 * 2 - 1;
// 迷路を生成
let mut maze = Maze::new(cols as isize, rows as isize);
maze.generate(1, 1);
// 画面クリア
stdout().execute(terminal::Clear(terminal::ClearType::All))?;
// 画面表示
terminal::enable_raw_mode()?;
let mut px = 1; // プレイヤーの初期座標
let mut py = 1;
loop {
// 画面描画
display(&maze, px, py)?;
let mut tx = px;
let mut ty = py;
// プレイヤーの移動
let event = read()?;
match event {
Event::Key(e) => {
match e.code {
KeyCode::Esc => break,
KeyCode::Char('q') => break,
// vi キーバインド
KeyCode::Char('h') => tx -= 1,
KeyCode::Char('l') => tx += 1,
KeyCode::Char('k') => ty -= 1,
KeyCode::Char('j') => ty += 1,
// カーソルキー
KeyCode::Left => tx -= 1,
KeyCode::Right => tx += 1,
KeyCode::Up => ty -= 1,
KeyCode::Down => ty += 1,
_ => {}
}
},
_ => {}
}
if maze.in_range(tx, ty) && maze.get(tx, ty) == Cell::Empty {
px = tx;
py = ty;
// ゲームクリア?
if px == maze.cols - 2 && py == maze.rows - 2 {
execute!(stdout(), Print("\r\n"))?;
println!("ゲームクリア!!");
break;
}
}
}
terminal::disable_raw_mode()?;
Ok(())
}
#[allow(dead_code)]
fn test_show() -> Result<()> {
// execute! マクロの中で処理を記述
execute!(
stdout(),
// 画面クリア
terminal::Clear(terminal::ClearType::All),
// カーソル移動
cursor::MoveTo(2, 2),
// 色を赤色に
SetForegroundColor(Color::Red),
// 文字を表示
Print("Hello, World!\n\n\n")
)?;
Ok(())
}
#[allow(dead_code)]
fn test_raw_mode() -> Result<()> {
// Rawモードに変更
terminal::enable_raw_mode()?;
// 無限ループの中でキーを取得する
loop {
// イベントの取得
let event = read()?;
match event {
Event::Key(e) => {
match e.code {
// キーに合わせて処理
KeyCode::Esc => break,
KeyCode::Char('q') => break,
KeyCode::Char('h') => {
execute!(stdout(), cursor::MoveToColumn(0))?;
println!("[push h key]");
}
_ => {}
}
},
_ => {}
}
}
terminal::disable_raw_mode()?;
Ok(())
}
fn main() -> Result<()> {
maze_main()
// test_show()
// test_raw_mode()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment