/* This file is part of Takwrap. Takwrap is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Takwrap is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Takwrap. If not, see . */ use fltk::{app::*, button::*, draw::*}; use std::rc::*; use crate::game::*; // All of theses properties are not expected to change under window // resizing, so we don't need more than an Rc<...> to one of these // when rendering in button.draw2(...) later. We do, however, need to // use compute_square_size because the widget may be resized. struct BoardRenderer { even_square_colour: Color, odd_square_colour: Color, black_colour: Color, white_colour: Color, stone_size: f32, game_size: i32, frame_pad: i32, xorg: i32, yorg: i32, } impl BoardRenderer { fn default() -> BoardRenderer { BoardRenderer { // Sandy brown odd_square_colour: Color::from_rgb(244, 164, 96), // Moccasin even_square_colour: Color::from_rgb(255, 228, 181), // Colours for pieces black_colour: Color::from_rgb(178, 34, 34), white_colour: Color::from_rgb(250, 235, 215), // Gap between stones and edged of square stone_size: 0.8, // Defaults game_size: 5, frame_pad: 5, xorg: 0, yorg: 0, } } fn with_size(mut self, size: i32) -> BoardRenderer { self.game_size = size; return self; } fn with_org(mut self, button: &Button) -> BoardRenderer { let x0 = button.x(); let y0 = button.y(); self.xorg = x0 + self.frame_pad; self.yorg = y0 + self.frame_pad; return self; } fn compute_square_size(&self, button: &Button) -> i32 { let board_size = i32::min(button.width(), button.height()) - self.frame_pad * 2; return board_size / self.game_size; } fn render_stone( &self, square_size: i32, player: Player, stone: Stone, row: i32, col: i32, shift: i32, ) { set_draw_color(match player { Player::Black => self.black_colour, Player::White => self.white_colour, }); // Gap scales with square size let stone_gap = (square_size as f32 * (1.00 - self.stone_size)).round() as i32; let d = square_size - stone_gap * 2; let shift = (shift * d) / 8; let x1 = self.xorg + col * square_size + stone_gap + shift; let y1 = self.yorg + row * square_size + stone_gap - shift; // Outline with thickness varying by stone size let setup_border_pen = || { set_line_style(LineStyle::Solid, i32::max(1, d / 24)); set_draw_color(Color::Black); }; match stone { Stone::Capstone => { draw_pie(x1, y1, d, d, 0.0, 360.0); setup_border_pen(); let r = (d as f64) / 2.0; draw_circle(r + x1 as f64, r + y1 as f64, r); } Stone::Flat => { draw_rectf(x1, y1, d, d); setup_border_pen(); draw_rect_with_color(x1, y1, d, d, Color::Black); } Stone::Standing => { // These look bad shifted so we shift it back if was let df = d as f64; // and we have to fudge it a little? Some rounding things presumably let ds = if shift > 0 { df / 8.0 - 0.5 } else { 0.0 }; let x1 = x1 as f64 - ds; let y1 = y1 as f64 + ds; let verts = [ (x1 + df * 0.3, y1 + df * 0.075), (x1 + df * 0.925, y1 + df * 0.7), (x1 + df * 0.7, y1 + df * 0.925), (x1 + df * 0.075, y1 + df * 0.3), ]; begin_polygon(); for (x, y) in verts.iter() { vertex(*x, *y) } end_polygon(); setup_border_pen(); begin_loop(); for (x, y) in verts.iter() { vertex(*x, *y); } end_loop(); } } set_line_style(LineStyle::Solid, 1); } fn render_icons(&self, square_size: i32, player: Player, row: i32, col: i32, shift: i32) { let stone_gap = square_size as f32 * (1.00 - self.stone_size); let icon_gap = (stone_gap * 0.15).round() as i32; let icon_width = (stone_gap * 0.7).round() as i32; let icon_height = icon_width / 2; let x1 = self.xorg + col * square_size + icon_gap; let y1 = self.yorg + (row + 1) * square_size - icon_gap; set_line_style(LineStyle::Solid, i32::max(1, icon_width / 8)); set_draw_color(Color::Black); set_draw_color(match player { Player::Black => self.black_colour, Player::White => self.white_colour, }); let y2 = y1 - icon_height * (1 + shift as i32); draw_rectf(x1, y2, icon_width, icon_height); draw_rect_with_color(x1, y2, icon_width, icon_height, Color::Black); set_line_style(LineStyle::Solid, 1); } fn render_stack(&self, square_size: i32, row: i32, col: i32, stack: &Stack) { // Is there a better way than usize::min((...), i32::MAX as usize) as i32 ? let cutoff = stack.len() as i32 - self.game_size; for (shift, piece) in stack.iter().enumerate() { let shift = shift as i32; // TODO: please don't overflow if shift < cutoff { self.render_icons(square_size, piece.player, row, col, shift); } else { self.render_stone( square_size, piece.player, piece.stone, row, col, shift - i32::max(0, cutoff), ); } } } fn render_board(&self, square_size: i32, board: &Vec) { let mut x = 0; let mut y = 0; for stack in board.iter() { self.render_stack(square_size, x, y, stack); x += 1; if x + 1 >= self.game_size { x = 0; y += 1; if y + 1 >= self.game_size { panic!("Game size mismatch between renderer and internal state."); } } } } fn render_background(&self, square_size: i32) { for x in 0..self.game_size { for y in 0..self.game_size { if (x + y) % 2 == 0 { set_draw_color(self.even_square_colour); } else { set_draw_color(self.odd_square_colour); } draw_rectf( self.xorg + x * square_size, self.yorg + y * square_size, square_size, square_size, ); } } } } pub struct BoardWidget { button: Button, renderer: Rc, } impl BoardWidget { pub fn new(button: Button, size: i32) -> BoardWidget { let renderer = Rc::new(BoardRenderer::default().with_size(size).with_org(&button)); let mut g = BoardWidget { button, renderer }; g.button.draw2(g.generate_draw(&Vec::new())); return g; } pub fn to_square(&self, x: i32, y: i32) -> (i32, i32) { let square_size = self.renderer.compute_square_size(&self.button); let x = x - self.renderer.xorg; let y = y - self.renderer.yorg; (x / square_size, y / square_size) } pub fn generate_draw(&self, board: &Vec) -> impl FnMut(&mut Button) { let renderer = self.renderer.clone(); let board = board.clone(); move |button: &mut Button| { let square_size = renderer.compute_square_size(&button); renderer.render_background(square_size); renderer.render_board(square_size, &board); // // Test code for now // renderer.render_stack(square_size, 1, 4, &stack); // renderer.render_stone(square_size, Player::Black, Stone::Capstone, 1, 2, 0); // renderer.render_stone(square_size, Player::Black, Stone::Standing, 2, 2, 0); } } }