diff --git a/gui/assets/ataxx-board-border.png b/gui/assets/ataxx-border.png similarity index 100% rename from gui/assets/ataxx-board-border.png rename to gui/assets/ataxx-border.png diff --git a/gui/assets/ataxx-squares.png b/gui/assets/ataxx-square.png similarity index 100% rename from gui/assets/ataxx-squares.png rename to gui/assets/ataxx-square.png diff --git a/gui/src/gui.rs b/gui/src/gui.rs new file mode 100644 index 0000000..b1894fc --- /dev/null +++ b/gui/src/gui.rs @@ -0,0 +1,197 @@ +use std::str::FromStr; + +use ataxx::{Board, File, Rank, Square}; +use raylib::prelude::*; + +use strum::IntoEnumIterator; + +pub struct GuiBuilder { + title: String, + fen: String, + width: i32, + height: i32, + scale: f32, +} + +impl GuiBuilder { + pub fn new() -> GuiBuilder { + GuiBuilder { + title: "Ataxx GUI".to_string(), + fen: "x5o/7/7/7/7/7/o5x x 0 1".to_string(), + width: 1280, + height: 720, + scale: 3.0, + } + } + + pub fn build(&self) -> Gui { + let (mut raylib, thread) = raylib::init() + .size(self.width, self.height) + .title(&self.title) + .build(); + let textures = Textures::load(&mut raylib, &thread); + let board = Board::from_str(&self.fen).unwrap(); + + Gui { + raylib, + thread, + options: GuiOptions { + width: self.width, + height: self.height, + scale: self.scale, + board, + textures, + }, + } + } +} + +pub struct Gui { + raylib: RaylibHandle, + thread: RaylibThread, + + options: GuiOptions, +} + +impl Gui { + pub fn new_drawer(&mut self) -> Drawer { + Drawer { + gui: &self.options, + drawer: self.raylib.begin_drawing(&self.thread), + } + } + + pub fn should_quit(&self) -> bool { + self.raylib.window_should_close() + } +} + +pub struct GuiOptions { + width: i32, + height: i32, + + scale: f32, + + board: ataxx::Board, + + textures: Textures, +} + +pub struct Drawer<'a> { + gui: &'a GuiOptions, + drawer: RaylibDrawHandle<'a>, +} + +const SQUARE_SIZE: f32 = 27.0; + +impl<'a> Drawer<'a> { + pub fn draw(&mut self) { + self.draw_board(); + } + + pub fn draw_board(&mut self) { + self.drawer.clear_background(Color::BLACK); + + for rank in Rank::iter().rev() { + for file in File::iter() { + self.draw_square(Square::new(file, rank)); + } + } + + let src_rec = Rectangle::new( + 0.0, + 0.0, + self.gui.textures.border.width() as f32, + self.gui.textures.border.height() as f32, + ); + let dst_rec = Rectangle::new( + self.gui.width as f32 / 2.0 + - self.gui.textures.border.width() as f32 * self.gui.scale / 2.0, + self.gui.height as f32 / 2.0 + - self.gui.textures.border.height() as f32 * self.gui.scale / 2.0, + self.gui.textures.border.width() as f32 * self.gui.scale, + self.gui.textures.border.height() as f32 * self.gui.scale, + ); + + self.drawer.draw_texture_pro( + &self.gui.textures.border, + src_rec, + dst_rec, + Vector2::zero(), + 0.0, + Color::WHITE, + ); + } + + pub fn draw_square(&mut self, sq: Square) { + let pc = self.gui.board.at(sq); + let a = match pc { + ataxx::Color::White => 0.0, + ataxx::Color::Black => 1.0, + ataxx::Color::None => 0.0, // unused + }; + + let top_left = Vector2::new( + self.gui.width as f32 / 2.0 - 183.0 * self.gui.scale / 2.0 + + sq.file() as u32 as f32 * (SQUARE_SIZE - 1.0) * self.gui.scale, + self.gui.height as f32 / 2.0 - 183.0 * self.gui.scale / 2.0 + + sq.rank() as u32 as f32 * (SQUARE_SIZE - 1.0) * self.gui.scale, + ); + + let sq_src_rec = Rectangle::new(0.0, 0.0, SQUARE_SIZE, SQUARE_SIZE); + let pc_src_rec = Rectangle::new(a * SQUARE_SIZE, 0.0, SQUARE_SIZE, SQUARE_SIZE); + + let dst_rec = Rectangle::new( + top_left.x, + top_left.y, + SQUARE_SIZE * self.gui.scale, + SQUARE_SIZE * self.gui.scale, + ); + + self.drawer.draw_texture_pro( + &self.gui.textures.square, + sq_src_rec, + dst_rec, + Vector2::new(0.0, 0.0), + 0.0, + Color::WHITE, + ); + + if pc == ataxx::Color::None { + return; + } + + self.drawer.draw_texture_pro( + &self.gui.textures.pieces, + pc_src_rec, + dst_rec, + Vector2::new(0.0, 0.0), + 0.0, + Color::WHITE, + ); + } +} + +pub struct Textures { + pieces: Texture2D, + square: Texture2D, + border: Texture2D, +} + +macro_rules! load_png_texture { + ($rl:expr, $thread:expr, $file:expr) => {{ + let raw = include_bytes!($file); + let img = Image::load_image_from_mem(".png", &raw.to_vec(), raw.len() as i32).unwrap(); + $rl.load_texture_from_image($thread, &img).unwrap() + }}; +} + +impl Textures { + pub fn load(rl: &mut RaylibHandle, thread: &RaylibThread) -> Textures { + Textures { + pieces: load_png_texture!(rl, thread, "../assets/ataxx-pieces.png"), + square: load_png_texture!(rl, thread, "../assets/ataxx-square.png"), + border: load_png_texture!(rl, thread, "../assets/ataxx-border.png"), + } + } +} diff --git a/gui/src/main.rs b/gui/src/main.rs index e509116..6347079 100644 --- a/gui/src/main.rs +++ b/gui/src/main.rs @@ -1,144 +1,11 @@ -use std::path::Path; -use std::str::FromStr; - -use strum::IntoEnumIterator; - -use ataxx::{Board, File, Rank, Square}; - -use raylib::prelude::*; - -const SCALE: f32 = 3.0; -const SQUARE_SIZE: f32 = 27.0; - -const WIDTH: i32 = 1280; -const HEIGHT: i32 = 720; +mod gui; +use gui::*; fn main() { - let board = Board::from_str("x5o/7/7/7/7/7/o5x x 0 1").unwrap(); - let (mut rl, thread) = raylib::init() - .size(WIDTH, HEIGHT) - .title("Ataxx GUI") - .build(); - - while !rl.window_should_close() { - draw_board(&mut rl, &thread, &board).unwrap(); - } -} - -fn draw_board(rl: &mut RaylibHandle, thread: &RaylibThread, board: &Board) -> Result<(), String> { - let squares_txt = load_texture(rl, thread, "ataxx-squares.png")?; - let pieces_txt = load_texture(rl, thread, "ataxx-pieces.png")?; - let border_txt = load_texture(rl, thread, "ataxx-board-border.png")?; - - let mut d = rl.begin_drawing(thread); - d.clear_background(Color::BLACK); - - let top_left = Vector2::new( - WIDTH as f32 / 2.0 - 183.0 * SCALE / 2.0, - HEIGHT as f32 / 2.0 - 183.0 * SCALE / 2.0, - ); - for rank in Rank::iter().rev() { - for file in File::iter() { - let square = Square::new(file, rank); - draw_square( - &mut d, - &squares_txt, - &pieces_txt, - Vector2::new( - top_left.x + file as u32 as f32 * (SQUARE_SIZE - 1.0) * SCALE, - top_left.y + rank as u32 as f32 * (SQUARE_SIZE - 1.0) * SCALE, - ), - board.at(square), - )?; - } - } + let mut gui = GuiBuilder::new().build(); - let src_rec = Rectangle::new( - 0.0, - 0.0, - border_txt.width() as f32, - border_txt.height() as f32, - ); - let dst_rec = Rectangle::new( - WIDTH as f32 / 2.0 - border_txt.width() as f32 * SCALE / 2.0, - HEIGHT as f32 / 2.0 - border_txt.height() as f32 * SCALE / 2.0, - border_txt.width() as f32 * SCALE, - border_txt.height() as f32 * SCALE, - ); - - d.draw_texture_pro( - &border_txt, - src_rec, - dst_rec, - Vector2::zero(), - 0.0, - Color::WHITE, - ); - - Ok(()) -} - -fn draw_square( - d: &mut RaylibDrawHandle, - sq_txt: &Texture2D, - pc_txt: &Texture2D, - top_left: Vector2, - pc: ataxx::Color, -) -> Result<(), String> { - let a = match pc { - ataxx::Color::White => 0.0, - ataxx::Color::Black => 1.0, - ataxx::Color::None => 0.0, // unused - }; - - let sq_src_rec = Rectangle::new(0.0, 0.0, SQUARE_SIZE, SQUARE_SIZE); - let pc_src_rec = Rectangle::new(a * SQUARE_SIZE, 0.0, SQUARE_SIZE, SQUARE_SIZE); - - let dst_rec = Rectangle::new( - top_left.x, - top_left.y, - SQUARE_SIZE * SCALE, - SQUARE_SIZE * SCALE, - ); - - d.draw_texture_pro( - sq_txt, - sq_src_rec, - dst_rec, - Vector2::new(0.0, 0.0), - 0.0, - Color::WHITE, - ); - - if pc == ataxx::Color::None { - return Ok(()); + while !gui.should_quit() { + let mut drawer = gui.new_drawer(); + drawer.draw(); } - - d.draw_texture_pro( - pc_txt, - pc_src_rec, - dst_rec, - Vector2::new(0.0, 0.0), - 0.0, - Color::WHITE, - ); - - Ok(()) -} - -fn load_texture( - rl: &mut RaylibHandle, - thread: &RaylibThread, - name: &str, -) -> Result { - let path = Path::new(file!()) - .parent() - .unwrap() - .parent() - .unwrap() - .to_str() - .unwrap() - .to_owned(); - let img = Image::load_image(&(path + "/assets/" + name))?; - rl.load_texture_from_image(thread, &img) }