====== 三並べ (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"