====== 三並べ (Tic-Tac-Toe) ====== 作成日: 2022-12-14 (水) {{:youtube:egui-tictactoe.png?400|}} ===== src/main.rs ===== 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, 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 { // 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 { 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 } ===== Cargo.toml ===== [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"