ユーザ用ツール

サイト用ツール


youtube:egui-tictactoe

差分

このページの2つのバージョン間の差分を表示します。

この比較画面へのリンク

両方とも前のリビジョン前のリビジョン
次のリビジョン
前のリビジョン
youtube:egui-tictactoe [2024/02/28 18:26] – 削除 - 外部編集 (不明な日付) 127.0.0.1youtube:egui-tictactoe [2024/02/29 11:49] (現在) freemikan
行 1: 行 1:
 +====== 三並べ (Tic-Tac-Toe) ======
  
 +作成日: 2022-12-14 (水)
 +
 +{{:youtube:egui-tictactoe.png?400|}}
 +
 +===== src/main.rs =====
 +
 +
 +<file rust>
 +use eframe::egui;
 +
 +fn main() {
 +    let options = eframe::NativeOptions {
 +        initial_window_size: Some(egui::vec2(500.0, 500.0)),
 +        ..Default::default()
 +    };
 +    eframe::run_native(
 +        "Tic-Tac-Toe",
 +        options,
 +        Box::new(|_cc| Box::new(MyApp::default())),
 +    );
 +}
 +
 +struct MyApp {
 +    turn: Player,
 +    pieces: Vec<Piece>,
 +    square_size: f32,
 +}
 +
 +impl Default for MyApp {
 +    fn default() -> Self {
 +        Self {
 +            turn: Player::O,
 +            pieces: Vec::new(),
 +            square_size: 100.0,
 +        }
 +    }
 +}
 +
 +impl eframe::App for MyApp {
 +    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
 +        let winner = winner(&self.pieces);
 +        let draw_match = self.pieces.len() == 9 && winner == None;
 +
 +        if winner == None && !draw_match {
 +            // step the game
 +            if let Some(pos) = self.clicked_position(ctx) {
 +                let board_pos = egui::pos2(
 +                    (pos.x / self.square_size).floor(),
 +                    (pos.y / self.square_size).floor(),
 +                );
 +
 +                let open_square = self.pieces.iter().all(|p| {
 +                    p.place.x as i32 != board_pos.x as i32 || p.place.y as i32 != board_pos.y as i32
 +                });
 +                let on_board = board_pos.x >= 0.0
 +                    && board_pos.x <= 2.0
 +                    && board_pos.y >= 0.0
 +                    && board_pos.y <= 2.0;
 +
 +                if open_square && on_board {
 +                    self.pieces.push(Piece {
 +                        player: self.turn,
 +                        place: board_pos,
 +                    });
 +
 +                    // next turn
 +                    self.turn = if self.turn == Player::O {
 +                        Player::X
 +                    } else {
 +                        Player::O
 +                    };
 +                }
 +            }
 +        } else {
 +            // wait for right button clicked to restart the game
 +            if ctx.input().pointer.secondary_clicked() {
 +                *self = Default::default();
 +            }
 +        }
 +
 +        egui::CentralPanel::default().show(ctx, |ui| {
 +            self.draw_board(ui.painter(), self.square_size);
 +            self.draw_pieces(ui.painter(), self.square_size);
 +
 +            if let Some(player_wins) = winner {
 +                self.draw_winner(ui.painter(), player_wins);
 +            }
 +
 +            if draw_match {
 +                self.draw_draw_match(ui.painter());
 +            }
 +        });
 +    }
 +}
 +
 +impl MyApp {
 +    fn clicked_position(&self, ctx: &egui::Context) -> Option<egui::Pos2> {
 +        // avoid dead lock
 +        // [[https://docs.rs/egui/latest/egui/struct.Context.html#method.input]]
 +        if let Some(pos) = { ctx.input().pointer.hover_pos() } {
 +            if ctx.input().pointer.any_click() {
 +                return Some(pos);
 +            }
 +        }
 +        None
 +    }
 +
 +    fn draw_board(&self, painter: &egui::Painter, square_size: f32) {
 +        for i in 0..3 {
 +            for j in 0..3 {
 +                let rect = egui::Rect::from_min_size(
 +                    egui::Pos2::new(j as f32 * square_size, i as f32 * square_size),
 +                    egui::vec2(square_size, square_size),
 +                );
 +                let rounding = 0.0;
 +                let color = egui::Color32::GREEN;
 +                let stroke = egui::Stroke::new(2.0, egui::Color32::LIGHT_GREEN);
 +                painter.rect(rect, rounding, color, stroke);
 +            }
 +        }
 +    }
 +
 +    fn draw_pieces(&self, painter: &egui::Painter, square_size: f32) {
 +        for piece in &self.pieces {
 +            piece.draw(painter, square_size);
 +        }
 +    }
 +
 +    fn draw_winner(&self, painter: &egui::Painter, winner: Player) {
 +        painter.text(
 +            painter.clip_rect().center(),
 +            egui::Align2::CENTER_CENTER,
 +            format!(
 +                "Player {} wins!",
 +                if winner == Player::O { "O" } else { "X" }
 +            ),
 +            egui::FontId::proportional(60.0),
 +            egui::Color32::RED,
 +        );
 +    }
 +
 +    fn draw_draw_match(&self, painter: &egui::Painter) {
 +        painter.text(
 +            painter.clip_rect().center(),
 +            egui::Align2::CENTER_CENTER,
 +            "Draw..",
 +            egui::FontId::proportional(60.0),
 +            egui::Color32::BLUE,
 +        );
 +    }
 +}
 +
 +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
 +enum Player {
 +    O,
 +    X,
 +}
 +
 +struct Piece {
 +    player: Player,
 +    place: egui::Pos2,
 +}
 +
 +impl Piece {
 +    fn draw(&self, painter: &egui::Painter, square_size: f32) {
 +        let p = egui::pos2(self.place.x * square_size, self.place.y * square_size);
 +        match self.player {
 +            Player::O => {
 +                painter.circle_stroke(
 +                    egui::pos2(p.x + square_size / 2.0, p.y + square_size / 2.0),
 +                    square_size * 0.3,
 +                    (3.0, egui::Color32::WHITE),
 +                );
 +            }
 +            Player::X => {
 +                painter.line_segment(
 +                    [
 +                        egui::pos2(p.x + square_size * 0.2, p.y + square_size * 0.2),
 +                        egui::pos2(p.x + square_size * 0.8, p.y + square_size * 0.8),
 +                    ],
 +                    (3.0, egui::Color32::BLACK),
 +                );
 +                painter.line_segment(
 +                    [
 +                        egui::pos2(p.x + square_size * 0.2, p.y + square_size * 0.8),
 +                        egui::pos2(p.x + square_size * 0.8, p.y + square_size * 0.2),
 +                    ],
 +                    (3.0, egui::Color32::BLACK),
 +                );
 +            }
 +        }
 +    }
 +}
 +
 +fn winner(pieces: &[Piece]) -> Option<Player> {
 +    for player in [Player::O, Player::X] {
 +        for i in 0..3 {
 +            if 3 == pieces
 +                .iter()
 +                .filter(|p| p.player == player)
 +                .filter(|p| p.place.x as i32 == i)
 +                .count()
 +            {
 +                return Some(player);
 +            }
 +        }
 +
 +        for i in 0..3 {
 +            if 3 == pieces
 +                .iter()
 +                .filter(|p| p.player == player)
 +                .filter(|p| p.place.y as i32 == i)
 +                .count()
 +            {
 +                return Some(player);
 +            }
 +        }
 +
 +        if 3 == pieces
 +            .iter()
 +            .filter(|p| p.player == player)
 +            .filter(|p| p.place.x as i32 == p.place.y as i32)
 +            .count()
 +        {
 +            return Some(player);
 +        }
 +
 +        if 3 == pieces
 +            .iter()
 +            .filter(|p| p.player == player)
 +            .filter(|p| 2 - p.place.x as i32 == p.place.y as i32)
 +            .count()
 +        {
 +            return Some(player);
 +        }
 +    }
 +
 +    None
 +}
 +</file>
 +
 +===== Cargo.toml =====
 +
 +
 +<file>
 +[package]
 +name = "egui_tictactoe"
 +version = "0.1.0"
 +edition = "2021"
 +
 +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 +
 +[dependencies]
 +eframe = "0.20.1"
 +</file>

特に明示されていない限り、本Wikiの内容は次のライセンスに従います: CC0 1.0 Universal
CC0 1.0 Universal Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki