fog-of-chess

[toy] Chess, but with the fog of war.
Log | Files | Refs

commit 1ad1bb8655ba136e66a1b8102d8b985081e1fec3
parent 58e52d885c45f470dcd82a034ac64a890a422ef8
Author: Jack Mordaunt <jackmordaunt@gmail.com>
Date:   Sat, 28 Mar 2020 17:42:45 +0800

Feat: fog-toggle (debug mode).

Diffstat:
Msrc/main.rs | 311++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
1 file changed, 188 insertions(+), 123 deletions(-)

diff --git a/src/main.rs b/src/main.rs @@ -3,9 +3,8 @@ use clap::{App, Arg, SubCommand}; use derive_builder::*; use ggez::event::{self, EventHandler}; -use ggez::graphics; -use ggez::graphics::{Color, DrawParam, Font, MeshBuilder, Text}; -use ggez::input::keyboard::{is_key_pressed, KeyCode}; +use ggez::graphics::{self, Color, DrawParam, Font, MeshBuilder, Rect, Text}; +use ggez::input::keyboard::{is_key_pressed, KeyCode, KeyMods}; use ggez::input::mouse::MouseButton; use ggez::{conf::WindowMode, conf::WindowSetup}; use ggez::{Context, ContextBuilder, GameResult}; @@ -58,7 +57,11 @@ fn main() { None => (Board::new(), false), }; let (mut ctx, mut event_loop) = ContextBuilder::new("Fog of War", "Jack Mordaunt") - .window_mode(WindowMode::default().dimensions(800.0, 600.0)) + .window_mode( + WindowMode::default() + .dimensions(800.0, 600.0) + .resizable(true), + ) .window_setup(WindowSetup::default().title("Fog of Chess")) .build() .expect("creating game loop"); @@ -85,8 +88,19 @@ impl EventHandler for Game { Ok(()) } + fn key_up_event(&mut self, _ctx: &mut Context, kc: KeyCode, _keymods: KeyMods) { + // Toggle fog of war. + // Hardcoded to "F" key for now. + // TODO: Generalise pattern for debug-only operations. + if cfg!(debug_assertions) { + if let KeyCode::F = kc { + self.fog = !self.fog; + } + } + } + fn mouse_button_up_event(&mut self, ctx: &mut Context, _b: MouseButton, x: f32, y: f32) { - let (w, h) = (800.0, 600.0); + let Rect { w, h, .. } = graphics::screen_coordinates(ctx); let (w_size, h_size) = (w / 8.0, h / 8.0); let (col, row) = ((x / w_size).floor() as i32, (y / h_size).floor() as i32); if is_key_pressed(ctx, KeyCode::LShift) { @@ -130,125 +144,10 @@ impl EventHandler for Game { } fn draw(&mut self, ctx: &mut Context) -> GameResult<()> { - graphics::clear(ctx, graphics::BLACK); - // TODO: Get actual size of window instead of hardcoding. - let (w, h) = (800.0, 600.0); - let (w_size, h_size) = (w / 8.0, h / 8.0); - let mut mb = MeshBuilder::new(); - // Draw pieces. - for (y, row) in self.board.0.iter().enumerate() { - for (x, cell) in row.iter().enumerate() { - if let Some(Piece { player, unit, .. }) = cell { - if *player != self.turn { - continue; - } - // Draw the current piece. - if *player == self.turn { - // Chess pieces are part of unicode. - // All we need is a font that provides these. - // let font = graphics::Font::default(); - let text = match unit { - Unit::Pawn => '\u{265F}', - Unit::King => '\u{265A}', - Unit::Queen => '\u{265B}', - Unit::Bishop => '\u{265D}', - Unit::Knight => '\u{265E}', - Unit::Rook => '\u{265C}', - }; - let color = match player { - Player::White => graphics::WHITE, - Player::Black => graphics::BLACK, - }; - let fragment: graphics::TextFragment = (text, self.font, 80.0).into(); - graphics::queue_text( - ctx, - &Text::new(fragment), - // TODO: Center dynamically instead of hardcoded padding. - [x as f32 * w_size + 25.0, y as f32 * h_size - 10.0], - Some(color), - ); - // Draw everything that is in line of sight of the - // current piece. - // TOOD: Given that all allied pieces will be drawn by - // previous code we can tighten this to "draw all - // enemies in line of sight". - for (x, y) in self - .line_of_sight((x as i32, y as i32)) - .into_iter() - .chain(vec![(x as i32, y as i32)]) - { - // Draw cell. - let color = if x % 2 == 0 && y % 2 == 0 { - SOARING_EAGLE - } else if x % 2 != 0 && y % 2 != 0 { - SOARING_EAGLE - } else { - WIZARD_GREY - }; - mb.rectangle( - graphics::DrawMode::fill(), - graphics::Rect::new_i32( - x as i32 * w_size as i32, - y as i32 * h_size as i32, - w_size as i32, - h_size as i32, - ), - color, - ); - // Highlight selected pieces. - if self.selected.contains(&(x as i32, y as i32)) { - mb.rectangle( - graphics::DrawMode::stroke(2.0), - graphics::Rect::new_i32( - x as i32 * w_size as i32 + 1, - y as i32 * h_size as i32 + 1, - w_size as i32 - 2, - h_size as i32 - 2, - ), - PURE_APPLE, - ); - }; - if let Some(Piece { player, unit, .. }) = - self.board.get((x as i32, y as i32)) - { - if *player != self.turn { - let text = match unit { - Unit::Pawn => '\u{265F}', - Unit::King => '\u{265A}', - Unit::Queen => '\u{265B}', - Unit::Bishop => '\u{265D}', - Unit::Knight => '\u{265E}', - Unit::Rook => '\u{265C}', - }; - let color = match player { - Player::White => graphics::WHITE, - Player::Black => graphics::BLACK, - }; - let fragment: graphics::TextFragment = - (text, self.font, 80.0).into(); - graphics::queue_text( - ctx, - &Text::new(fragment), - // TODO: Center dynamically instead of hardcoded padding. - [x as f32 * w_size + 25.0, y as f32 * h_size - 10.0], - Some(color), - ); - } - } - } - } - } - } + match self.fog { + true => self.draw_fog(ctx), + false => self.draw_no_fog(ctx), } - let mut mesh = mb.build(ctx)?; - graphics::draw(ctx, &mut mesh, DrawParam::default())?; - graphics::draw_queued_text( - ctx, - DrawParam::default(), - None, - graphics::FilterMode::Linear, - )?; - graphics::present(ctx) } } @@ -538,6 +437,172 @@ impl Game { } } } + // Draw without fog of war. + fn draw_no_fog(&mut self, ctx: &mut Context) -> GameResult<()> { + graphics::clear(ctx, graphics::BLACK); + // TODO: Get actual size of window instead of hardcoding. + let Rect { w, h, .. } = graphics::screen_coordinates(ctx); + let (w_size, h_size) = (w / 8.0, h / 8.0); + let mut mb = MeshBuilder::new(); + // Draw pieces. + for (y, row) in self.board.0.iter().enumerate() { + for (x, _cell) in row.iter().enumerate() { + // Draw cell. + // Checkered colors. + { + let color = if x % 2 == 0 && y % 2 == 0 { + SOARING_EAGLE + } else if x % 2 != 0 && y % 2 != 0 { + SOARING_EAGLE + } else { + WIZARD_GREY + }; + mb.rectangle( + graphics::DrawMode::fill(), + graphics::Rect::new_i32( + x as i32 * w_size as i32, + y as i32 * h_size as i32, + w_size as i32, + h_size as i32, + ), + color, + ); + } + // Highlight if selected. + { + if self.selected.contains(&(x as i32, y as i32)) { + mb.rectangle( + graphics::DrawMode::stroke(2.0), + graphics::Rect::new_i32( + x as i32 * w_size as i32 + 1, + y as i32 * h_size as i32 + 1, + w_size as i32 - 2, + h_size as i32 - 2, + ), + PURE_APPLE, + ); + }; + } + self.draw_piece(ctx, (x as i32, y as i32)); + } + } + let mut mesh = mb.build(ctx)?; + graphics::draw(ctx, &mut mesh, DrawParam::default())?; + graphics::draw_queued_text( + ctx, + DrawParam::default(), + None, + graphics::FilterMode::Linear, + )?; + graphics::present(ctx) + } + // Draw with fog of war. + fn draw_fog(&mut self, ctx: &mut Context) -> GameResult<()> { + graphics::clear(ctx, graphics::BLACK); + let Rect { w, h, .. } = graphics::screen_coordinates(ctx); + let (w_size, h_size) = (w / 8.0, h / 8.0); + let mut mb = MeshBuilder::new(); + for (y, row) in self.board.0.iter().enumerate() { + for (x, cell) in row.iter().enumerate() { + if let Some(Piece { player, .. }) = cell { + if self.fog && *player != self.turn { + continue; + } + // Draw the current piece. + if *player == self.turn { + self.draw_piece(ctx, (x as i32, y as i32)); + // Draw everything that is in line of sight of the + // current piece. + for (x, y) in self + .line_of_sight((x as i32, y as i32)) + .into_iter() + .chain(vec![(x as i32, y as i32)]) + { + // Draw cell. + // Checkered colors. + { + let color = if x % 2 == 0 && y % 2 == 0 { + SOARING_EAGLE + } else if x % 2 != 0 && y % 2 != 0 { + SOARING_EAGLE + } else { + WIZARD_GREY + }; + mb.rectangle( + graphics::DrawMode::fill(), + graphics::Rect::new_i32( + x as i32 * w_size as i32, + y as i32 * h_size as i32, + w_size as i32, + h_size as i32, + ), + color, + ); + } + // Highlight selected pieces. + { + if self.selected.contains(&(x as i32, y as i32)) { + mb.rectangle( + graphics::DrawMode::stroke(2.0), + graphics::Rect::new_i32( + x as i32 * w_size as i32 + 1, + y as i32 * h_size as i32 + 1, + w_size as i32 - 2, + h_size as i32 - 2, + ), + PURE_APPLE, + ); + }; + } + // Draw the enemy piece. + if self.turn != *player { + self.draw_piece(ctx, (x as i32, y as i32)); + } + } + } + } + } + } + let mut mesh = mb.build(ctx)?; + graphics::draw(ctx, &mut mesh, DrawParam::default())?; + graphics::draw_queued_text( + ctx, + DrawParam::default(), + None, + graphics::FilterMode::Linear, + )?; + graphics::present(ctx) + } + // Draw a single piece based on location. + fn draw_piece(&self, ctx: &mut Context, pos: (i32, i32)) { + let (x, y) = pos; + let Rect { w, h, .. } = graphics::screen_coordinates(ctx); + let (w_size, h_size) = (w / 8.0, h / 8.0); + if let Some(Piece { player, unit, .. }) = self.board.get((x, y)) { + // Chess pieces are part of unicode. + // All we need is a font that provides these. + let text = match unit { + Unit::Pawn => '\u{265F}', + Unit::King => '\u{265A}', + Unit::Queen => '\u{265B}', + Unit::Bishop => '\u{265D}', + Unit::Knight => '\u{265E}', + Unit::Rook => '\u{265C}', + }; + let color = match player { + Player::White => graphics::WHITE, + Player::Black => graphics::BLACK, + }; + let fragment: graphics::TextFragment = (text, self.font, 80.0).into(); + graphics::queue_text( + ctx, + &Text::new(fragment), + // TODO: Center dynamically instead of hardcoded padding. + [x as f32 * w_size + 25.0, y as f32 * h_size - 10.0], + Some(color), + ); + } + } } impl Board {