fog-of-chess

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

main.rs (34898B)


      1 use clap::{App, Arg, SubCommand};
      2 use derive_builder::*;
      3 use ggez::event::{self, EventHandler};
      4 use ggez::graphics::{self, Color, DrawMode, DrawParam, Font, MeshBuilder, Rect, Text};
      5 use ggez::input::keyboard::{is_key_pressed, KeyCode, KeyMods};
      6 use ggez::input::mouse::MouseButton;
      7 use ggez::{conf::WindowMode, conf::WindowSetup};
      8 use ggez::{Context, ContextBuilder, GameResult};
      9 use std::collections::HashSet;
     10 
     11 const PURE_APPLE: Color = Color {
     12     r: 106.0 / 256.0,
     13     g: 176.0 / 256.0,
     14     b: 76.0 / 256.0,
     15     a: 1.0,
     16 };
     17 
     18 const SOARING_EAGLE: Color = Color {
     19     r: 149.0 / 256.0,
     20     g: 175.0 / 256.0,
     21     b: 192.0 / 256.0,
     22     a: 1.0,
     23 };
     24 
     25 const WIZARD_GREY: Color = Color {
     26     r: 83.0 / 256.0,
     27     g: 92.0 / 256.0,
     28     b: 104.0 / 256.0,
     29     a: 1.0,
     30 };
     31 
     32 fn main() {
     33     let app = App::new("Fog Of Chess")
     34         .arg(
     35             Arg::with_name("no-fog")
     36                 .takes_value(false)
     37                 .long("no-fog")
     38                 .help("Turn off the fog of war."),
     39         )
     40         .arg(
     41             Arg::with_name("debug-stats")
     42                 .takes_value(false)
     43                 .long("debug-stats")
     44                 .help("Show useful information for debugging."),
     45         )
     46         .subcommand(
     47             SubCommand::with_name("test").arg(
     48                 Arg::with_name("scenario")
     49                     .required(true)
     50                     .help("Name of scenario to test."),
     51             ),
     52         )
     53         .get_matches();
     54     let (board, single_player) = match app.subcommand_matches("test") {
     55         Some(test) => match Board::scenario(
     56             test.value_of("scenario")
     57                 .expect("scenario argument missing"),
     58         ) {
     59             Some(board) => (board, true),
     60             None => panic!("scenario does not exist"),
     61         },
     62         None => (Board::new(), false),
     63     };
     64     let (width, height) = (800.0, 800.0);
     65     let (mut ctx, mut event_loop) = ContextBuilder::new("Fog of War", "Jack Mordaunt")
     66         .window_mode(
     67             WindowMode::default()
     68                 .dimensions(width, height)
     69                 .resizable(true),
     70         )
     71         .window_setup(WindowSetup::default().title("Fog of Chess"))
     72         .build()
     73         .expect("creating game loop");
     74     let state = StateBuilder::default()
     75         .board(board)
     76         .single_player(single_player)
     77         .fog(!app.is_present("no-fog"))
     78         .selected(HashSet::new())
     79         .turn(Player::White)
     80         .font(
     81             Font::new_glyph_font_bytes(&mut ctx, include_bytes!("../res/DejaVuSansMono.ttf"))
     82                 .expect("loading font"),
     83         )
     84         .debug_stats(app.is_present("debug-stats"))
     85         .build()
     86         .expect("building game object");
     87     event::run(
     88         ctx,
     89         event_loop,
     90         Game {
     91             state: state.clone(),
     92             initial: state.clone(),
     93         },
     94     )
     95 }
     96 
     97 impl EventHandler for Game {
     98     fn update(&mut self, _ctx: &mut Context) -> GameResult<()> {
     99         Ok(())
    100     }
    101 
    102     fn key_up_event(&mut self, _ctx: &mut Context, kc: KeyCode, _keymods: KeyMods) {
    103         if cfg!(debug_assertions) {
    104             match kc {
    105                 KeyCode::F => self.state.fog = !self.state.fog,
    106                 KeyCode::F3 => self.state.debug_stats = !self.state.debug_stats,
    107                 KeyCode::R => self.state = self.initial.clone(),
    108                 _ => {}
    109             };
    110         }
    111     }
    112 
    113     fn mouse_button_up_event(&mut self, ctx: &mut Context, _b: MouseButton, x: f32, y: f32) {
    114         let (col, row) = self.pixels_to_grid(ctx, (x, y));
    115         if is_key_pressed(ctx, KeyCode::LShift) {
    116             if self.contains_ally((col, row)) {
    117                 // BUG: Avoid duplicates.
    118                 self.state.selected.insert((col, row));
    119             }
    120         } else {
    121             match self.state.board.get((col, row)) {
    122                 None => {
    123                     // Multi selection is a potential compound move.
    124                     // Given the only compound move in standard chess is the
    125                     // "castle", we directly call into it.
    126                     if self.state.selected.len() > 1 {
    127                         self.castle_move();
    128                     } else {
    129                         if let Some((x, y)) = self.state.selected.iter().next().cloned() {
    130                             if self.moves((x, y)).contains(&(col, row)) {
    131                                 self.move_turn((x, y), (col, row));
    132                             }
    133                         }
    134                     }
    135                 }
    136                 Some(Piece { player, .. }) => {
    137                     if self.is_enemy(player) && self.state.selected.len() == 1 {
    138                         if let Some((x, y)) = self.state.selected.iter().next().cloned() {
    139                             self.attack_move((x, y), (col, row));
    140                         }
    141                     } else {
    142                         if self.contains_ally((col, row)) {
    143                             self.state.selected.clear();
    144                             self.state.selected.insert((col, row));
    145                         }
    146                     }
    147                 }
    148             };
    149         }
    150     }
    151 
    152     fn draw(&mut self, ctx: &mut Context) -> GameResult<()> {
    153         self.draw_board(ctx)?;
    154         self.draw_pieces(ctx)?;
    155         self.draw_highlights(ctx)?;
    156         if self.state.fog {
    157             self.draw_fog(ctx)?;
    158         }
    159         if self.state.debug_stats {
    160             self.draw_debug_stats(ctx)?;
    161         }
    162         graphics::draw_queued_text(
    163             ctx,
    164             DrawParam::default(),
    165             None,
    166             graphics::FilterMode::Linear,
    167         )?;
    168         graphics::present(ctx)
    169     }
    170 
    171     fn resize_event(&mut self, ctx: &mut Context, width: f32, height: f32) {
    172         graphics::set_screen_coordinates(ctx, Rect::new(0.0, 0.0, width, height))
    173             .expect("graphics::set_screen_coordinates");
    174     }
    175 }
    176 
    177 /// Unique chess units.
    178 #[derive(Clone, Debug)]
    179 pub enum Unit {
    180     Pawn,
    181     Rook,
    182     Knight,
    183     Bishop,
    184     Queen,
    185     King,
    186 }
    187 
    188 /// Player denotes the two unique players that can own units.
    189 #[derive(Clone, Debug, Eq, PartialEq)]
    190 pub enum Player {
    191     White,
    192     Black,
    193 }
    194 
    195 /// Piece is a Unit-Player pair that represents a piece on the board.
    196 #[derive(Clone, Debug)]
    197 pub struct Piece {
    198     pub unit: Unit,
    199     pub player: Player,
    200     // Track number of times this piece has been moved.
    201     pub moved: u32,
    202 }
    203 
    204 /// Board contains the location information of each piece.
    205 #[derive(Clone, Default)]
    206 pub struct Board([[Option<Piece>; 8]; 8]);
    207 
    208 /// Game contains meta information.
    209 #[derive(Clone)]
    210 pub struct Game {
    211     pub initial: State,
    212     pub state: State,
    213 }
    214 
    215 #[derive(Clone, Builder)]
    216 pub struct State {
    217     pub board: Board,
    218     pub turn: Player,
    219     // TODO: Use a set to avoid duplicates.
    220     pub selected: HashSet<(i32, i32)>,
    221     pub font: graphics::Font,
    222     pub fog: bool,
    223     pub single_player: bool,
    224     pub debug_stats: bool,
    225 }
    226 
    227 impl Game {
    228     /// Moves calculates all valid moves for the currently selected piece.
    229     pub fn moves(&self, pos: (i32, i32)) -> Vec<(i32, i32)> {
    230         let (x, y) = pos;
    231         use Unit::*;
    232         match self.state.board.get((x, y)) {
    233             Some(Piece {
    234                 unit,
    235                 player,
    236                 moved,
    237             }) => match unit {
    238                 // Pawn can move in the direction of the player by 1 square.
    239                 // For the first move, a pawn can move up to 2 squares.
    240                 // Pawns can only attack diagonally in the direction of the
    241                 // player.
    242                 // Cannot attack straight ahead.
    243                 Pawn => {
    244                     let mut moves = vec![];
    245                     match player {
    246                         // Clean: The only difference between these two
    247                         // blocks is the direction.
    248                         Player::White => {
    249                             if self.contains_enemy((x - 1, y + 1)) {
    250                                 moves.push((x - 1, y + 1));
    251                             }
    252                             if self.contains_enemy((x + 1, y + 1)) {
    253                                 moves.push((x + 1, y + 1));
    254                             }
    255                             if self.state.board.0[y as usize + 1][x as usize].is_none() {
    256                                 moves.push((x, y + 1));
    257                                 if *moved == 0
    258                                     && self.state.board.0[y as usize + 2][x as usize].is_none()
    259                                 {
    260                                     moves.push((x, y + 2));
    261                                 }
    262                             }
    263                         }
    264                         Player::Black => {
    265                             if self.contains_enemy((x - 1, y - 1)) {
    266                                 moves.push((x - 1, y - 1));
    267                             }
    268                             if self.contains_enemy((x + 1, y - 1)) {
    269                                 moves.push((x + 1, y - 1));
    270                             }
    271                             if self.state.board.0[y as usize - 1][x as usize].is_none() {
    272                                 moves.push((x, y - 1));
    273                                 if *moved == 0
    274                                     && self.state.board.0[y as usize - 2][x as usize].is_none()
    275                                 {
    276                                     moves.push((x, y - 2));
    277                                 }
    278                             }
    279                         }
    280                     };
    281                     moves
    282                 }
    283                 // Knight moves in an L shape: two out, one across.
    284                 Knight => vec![
    285                     (x + 2, y - 1),
    286                     (x + 2, y + 1),
    287                     (x - 2, y - 1),
    288                     (x - 2, y + 1),
    289                     (x + 1, y + 2),
    290                     (x - 1, y + 2),
    291                     (x + 1, y - 2),
    292                     (x - 1, y - 2),
    293                 ],
    294                 // Rook moves in all non diagonal directions.
    295                 Rook => vec![]
    296                     .into_iter()
    297                     .chain(LineOfSight::new(
    298                         (1..8).map(|ii| (x + ii, y)),
    299                         &self.state.board,
    300                     ))
    301                     .chain(LineOfSight::new(
    302                         (1..8).map(|ii| (x - ii, y)),
    303                         &self.state.board,
    304                     ))
    305                     .chain(LineOfSight::new(
    306                         (1..8).map(|ii| (x, y + ii)),
    307                         &self.state.board,
    308                     ))
    309                     .chain(LineOfSight::new(
    310                         (1..8).map(|ii| (x, y - ii)),
    311                         &self.state.board,
    312                     ))
    313                     .collect(),
    314                 // Bishop moves all diagonal directions.
    315                 Bishop => vec![]
    316                     .into_iter()
    317                     .chain(LineOfSight::new(
    318                         (1..8).map(|ii| (x + ii, y + ii)),
    319                         &self.state.board,
    320                     ))
    321                     .chain(LineOfSight::new(
    322                         (1..8).map(|ii| (x - ii, y - ii)),
    323                         &self.state.board,
    324                     ))
    325                     .chain(LineOfSight::new(
    326                         (1..8).map(|ii| (x - ii, y + ii)),
    327                         &self.state.board,
    328                     ))
    329                     .chain(LineOfSight::new(
    330                         (1..8).map(|ii| (x + ii, y - ii)),
    331                         &self.state.board,
    332                     ))
    333                     .collect(),
    334                 // Queen moves in all eight directions.
    335                 Queen => vec![]
    336                     .into_iter()
    337                     .chain(LineOfSight::new(
    338                         (1..8).map(|ii| (x + ii, y)),
    339                         &self.state.board,
    340                     ))
    341                     .chain(LineOfSight::new(
    342                         (1..8).map(|ii| (x - ii, y)),
    343                         &self.state.board,
    344                     ))
    345                     .chain(LineOfSight::new(
    346                         (1..8).map(|ii| (x, y + ii)),
    347                         &self.state.board,
    348                     ))
    349                     .chain(LineOfSight::new(
    350                         (1..8).map(|ii| (x, y - ii)),
    351                         &self.state.board,
    352                     ))
    353                     .chain(LineOfSight::new(
    354                         (1..8).map(|ii| (x + ii, y + ii)),
    355                         &self.state.board,
    356                     ))
    357                     .chain(LineOfSight::new(
    358                         (1..8).map(|ii| (x - ii, y - ii)),
    359                         &self.state.board,
    360                     ))
    361                     .chain(LineOfSight::new(
    362                         (1..8).map(|ii| (x - ii, y + ii)),
    363                         &self.state.board,
    364                     ))
    365                     .chain(LineOfSight::new(
    366                         (1..8).map(|ii| (x + ii, y - ii)),
    367                         &self.state.board,
    368                     ))
    369                     .collect(),
    370                 // King can move to any adjacent cell that isn't occupied by
    371                 // a piece of the same player.
    372                 King => vec![
    373                     (x + 1, y + 1),
    374                     (x - 1, y - 1),
    375                     (x + 1, y - 1),
    376                     (x - 1, y + 1),
    377                     (x + 1, y),
    378                     (x - 1, y),
    379                     (x, y + 1),
    380                     (x, y - 1),
    381                 ],
    382             },
    383             None => vec![],
    384         }
    385         .into_iter()
    386         .filter(|(x, y)| !self.contains_ally((*x, *y)))
    387         .collect()
    388     }
    389     // Calculate line of sight for any piece at the given coordinate.
    390     pub fn line_of_sight(&self, pos: (i32, i32)) -> Vec<(i32, i32)> {
    391         let (x, y) = pos;
    392         self.moves(pos)
    393             .into_iter()
    394             .chain(
    395                 vec![
    396                     (x + 1, y + 1),
    397                     (x - 1, y - 1),
    398                     (x + 1, y - 1),
    399                     (x - 1, y + 1),
    400                     (x + 1, y),
    401                     (x - 1, y),
    402                     (x, y + 1),
    403                     (x, y - 1),
    404                 ]
    405                 .into_iter(),
    406             )
    407             .collect()
    408     }
    409     /// Move a piece and conclude the turn.
    410     pub fn move_turn(&mut self, from: (i32, i32), to: (i32, i32)) {
    411         if self.contains_ally(from) {
    412             self.state.board.move_piece((from.0, from.1), (to.0, to.1));
    413             if !self.state.single_player {
    414                 self.state.turn = match self.state.turn {
    415                     Player::Black => Player::White,
    416                     Player::White => Player::Black,
    417                 };
    418             }
    419             self.state.selected.clear();
    420         }
    421     }
    422     /// Attack move one piece onto another.
    423     pub fn attack_move(&mut self, from: (i32, i32), to: (i32, i32)) {
    424         if self.moves((from.0, from.1)).contains(&(to.0, to.1)) {
    425             self.move_turn((from.0, from.1), (to.0, to.1));
    426         }
    427     }
    428     /// Contains enemy if the specified position is occupied by a piece owned
    429     /// by the other player.
    430     pub fn contains_enemy(&self, pos: (i32, i32)) -> bool {
    431         let (x, y) = pos;
    432         if x > -1 && y > -1 && x - 1 < 7 && y - 1 < 7 {
    433             match &self.state.board.0[y as usize][x as usize] {
    434                 Some(Piece { player, .. }) => self.is_enemy(player),
    435                 _ => false,
    436             }
    437         } else {
    438             false
    439         }
    440     }
    441     /// Contains ally if the specified position is occupied by a piece owned by
    442     /// the currently player.
    443     pub fn contains_ally(&self, pos: (i32, i32)) -> bool {
    444         let (x, y) = pos;
    445         if x > -1 && y > -1 && x - 1 < 7 && y - 1 < 7 {
    446             match &self.state.board.0[y as usize][x as usize] {
    447                 Some(Piece { player, .. }) => *player == self.state.turn,
    448                 None => false,
    449             }
    450         } else {
    451             false
    452         }
    453     }
    454     /// Perform castle move if valid.
    455     /// Castle move where King and Rook crossover into the 2 spaces between them.
    456     /// Only valid if:
    457     /// - Pieces are the same player (duh).
    458     /// - Neither piece has been moved.
    459     /// - Nothing is in the two spaces between them.
    460     fn castle_move(&mut self) {
    461         let moves = self
    462             .state
    463             .selected
    464             .iter()
    465             .take(2)
    466             .filter_map(|pos| match self.state.board.get(*pos).cloned() {
    467                 Some(piece) => Some((pos, piece)),
    468                 None => None,
    469             })
    470             .filter_map(|(pos, piece)| {
    471                 // Direction is derived from standard chess layout,
    472                 // where Rook is 3 positions to the left of the King.
    473                 let projected_move = match piece {
    474                     Piece {
    475                         unit: Unit::Rook, ..
    476                     } => (pos.0 + 2, pos.1),
    477                     Piece {
    478                         unit: Unit::King, ..
    479                     } => (pos.0 - 2, pos.1),
    480                     _ => return None,
    481                 };
    482                 if piece.moved > 0 || self.state.board.get(projected_move).is_some() {
    483                     None
    484                 } else {
    485                     Some((*pos, projected_move))
    486                 }
    487             })
    488             .collect::<Vec<((i32, i32), (i32, i32))>>();
    489         if moves.len() == 2 {
    490             for (from, to) in moves {
    491                 self.state.board.move_piece(from, to);
    492             }
    493             self.state.selected.clear();
    494         }
    495     }
    496     /// Draw the board which the pieces are placed onto.
    497     fn draw_board(&self, ctx: &mut Context) -> GameResult<()> {
    498         let (w, h) = self.cell_size(ctx);
    499         let mut mb = MeshBuilder::new();
    500         for Position { x, y, .. } in self.state.board.iter() {
    501             let (x, y) = (x as i32, y as i32);
    502             // TODO: get color from color map.
    503             let color = if x % 2 == 0 && y % 2 == 0 {
    504                 SOARING_EAGLE
    505             } else if x % 2 != 0 && y % 2 != 0 {
    506                 SOARING_EAGLE
    507             } else {
    508                 WIZARD_GREY
    509             };
    510             let (x, y) = (x as f32, y as f32);
    511             mb.rectangle(
    512                 graphics::DrawMode::fill(),
    513                 graphics::Rect::new(x * w, y * h, w, h),
    514                 color,
    515             );
    516         }
    517         let mut mesh = mb.build(ctx)?;
    518         graphics::draw(ctx, &mut mesh, DrawParam::default())
    519     }
    520     // Draw the chess pieces onto the baord.
    521     fn draw_pieces(&self, ctx: &mut Context) -> GameResult<()> {
    522         let (w, h) = self.cell_size(ctx);
    523         let size = w.min(h);
    524         for Position { x, y, piece } in self.state.board.iter() {
    525             if let Some(Piece { player, unit, .. }) = piece {
    526                 // Chess pieces are part of unicode.
    527                 // All we need is a font that provides these.
    528                 let text = match unit {
    529                     Unit::Pawn => '\u{265F}',
    530                     Unit::King => '\u{265A}',
    531                     Unit::Queen => '\u{265B}',
    532                     Unit::Bishop => '\u{265D}',
    533                     Unit::Knight => '\u{265E}',
    534                     Unit::Rook => '\u{265C}',
    535                 };
    536                 let color = match player {
    537                     Player::White => graphics::Color::WHITE,
    538                     Player::Black => graphics::Color::BLACK,
    539                 };
    540                 // In order to center the pieces there are a few tricks to do.
    541                 // First, scale the text by the larger side to "fill out" the space.
    542                 // Then queue and draw the text immediately, centering the text horizontally.
    543                 // The fixed offset of -2.0 is required to counteract 1px borders (I think!).
    544                 // The text must be drawn individually so that we can scale each fragment individually.
    545                 let fragment: graphics::TextFragment = (text, self.state.font, size).into();
    546                 graphics::queue_text(ctx, &Text::new(fragment), [0.0, 0.0], Some(color));
    547                 let scale = if h > w {
    548                     [1.0, h / w]
    549                 } else if w > h {
    550                     [w / h, 1.0]
    551                 } else {
    552                     [1.0, 1.0]
    553                 };
    554                 graphics::draw_queued_text(
    555                     ctx,
    556                     DrawParam::default()
    557                         .dest([x as f32 * w + (w / 4.0 - 2.0), y as f32 * h])
    558                         .scale(scale),
    559                     None,
    560                     graphics::FilterMode::Linear,
    561                 )?;
    562             }
    563         }
    564         Ok(())
    565     }
    566     // Draw highlights for selected pieces.
    567     fn draw_highlights(&self, ctx: &mut Context) -> GameResult<()> {
    568         let mut mb = MeshBuilder::new();
    569         let (w, h) = self.cell_size(ctx);
    570         for (x, y) in self.state.selected.iter() {
    571             let (x, y) = (*x as f32, *y as f32);
    572             mb.rectangle(
    573                 DrawMode::stroke(2.0),
    574                 Rect::new(x * w, y * h, w, h),
    575                 PURE_APPLE,
    576             );
    577         }
    578         if let Ok(mut mesh) = mb.build(ctx) {
    579             graphics::draw(ctx, &mut mesh, DrawParam::default())?;
    580         }
    581         Ok(())
    582     }
    583     // Draw the fog over war over the enemy pieces.
    584     fn draw_fog(&self, ctx: &mut Context) -> GameResult<()> {
    585         #[derive(Copy, Clone)]
    586         enum Visibility {
    587             Fog,
    588             Clear,
    589         }
    590         let mut mask = [[Visibility::Fog; 8]; 8];
    591         let mut mb = MeshBuilder::new();
    592         let (w, h) = self.cell_size(ctx);
    593         for Position { x, y, piece } in self.state.board.iter() {
    594             if let Some(Piece { player, .. }) = piece {
    595                 if self.is_enemy(player) {
    596                     continue;
    597                 }
    598                 let (x, y) = (x as i32, y as i32);
    599                 for (x, y) in self.line_of_sight((x, y)).into_iter().chain(vec![(x, y)]) {
    600                     // TODO: Better way to handle these bounds checks?
    601                     // 1. Let trait define valid usize.
    602                     // 2. Let board size be dynamic.
    603                     if y >= 0 && x >= 0 && y < 8 && x < 8 {
    604                         mask[y as usize][x as usize] = Visibility::Clear;
    605                     }
    606                 }
    607             }
    608         }
    609         for (y, row) in mask.iter().enumerate() {
    610             for (x, visibility) in row.iter().enumerate() {
    611                 if let Visibility::Fog = visibility {
    612                     let (x, y) = (x as f32, y as f32);
    613                     mb.rectangle(
    614                         graphics::DrawMode::fill(),
    615                         graphics::Rect::new(x * w, y * h, w, h),
    616                         graphics::Color::BLACK,
    617                     );
    618                 }
    619             }
    620         }
    621         let mut mesh = mb.build(ctx)?;
    622         graphics::draw(ctx, &mut mesh, DrawParam::default())
    623     }
    624     // Draw meta information useful for debugging.
    625     fn draw_debug_stats(&self, ctx: &mut Context) -> GameResult<()> {
    626         let (text_size, padding) = (20.0, 5.0);
    627         let (width, height) = graphics::size(ctx);
    628         let (w, h) = self.cell_size(ctx);
    629         let stats = vec![
    630             format!("window: {} x {}", width, height),
    631             format!("  cell: {} x {}", w, h),
    632         ];
    633         for (ii, stat) in stats.iter().enumerate() {
    634             self.text(
    635                 ctx,
    636                 stat,
    637                 (10.0, ii as f32 * text_size + padding),
    638                 text_size,
    639                 None,
    640             );
    641         }
    642         Ok(())
    643     }
    644     fn is_enemy(&self, player: &Player) -> bool {
    645         self.state.turn != *player
    646     }
    647     // Calculate cell size based on window size (width, height).
    648     fn cell_size(&self, ctx: &mut Context) -> (f32, f32) {
    649         let (w, h) = graphics::drawable_size(ctx);
    650         ((w / 8.0), (h / 8.0))
    651     }
    652     fn text(
    653         &self,
    654         ctx: &mut Context,
    655         text: &str,
    656         coord: (f32, f32),
    657         scale: f32,
    658         color: Option<Color>,
    659     ) {
    660         let fragment: graphics::TextFragment = (text, self.state.font, scale).into();
    661         graphics::queue_text(
    662             ctx,
    663             &Text::new(fragment),
    664             [coord.0, coord.1],
    665             color.or(Some(graphics::Color::BLACK)),
    666         );
    667     }
    668     // Translate pixel coordinates to grid cells.
    669     fn pixels_to_grid(&self, ctx: &mut Context, coord: (f32, f32)) -> (i32, i32) {
    670         let (x, y) = coord;
    671         let (w, h) = self.cell_size(ctx);
    672         ((x / w).floor() as i32, (y / h).floor() as i32)
    673     }
    674 }
    675 
    676 impl Board {
    677     pub fn new() -> Self {
    678         use Player::*;
    679         use Unit::*;
    680         Board([
    681             [
    682                 Some(Piece {
    683                     unit: Rook,
    684                     player: White,
    685                     moved: 0,
    686                 }),
    687                 Some(Piece {
    688                     unit: Knight,
    689                     player: White,
    690                     moved: 0,
    691                 }),
    692                 Some(Piece {
    693                     unit: Bishop,
    694                     player: White,
    695                     moved: 0,
    696                 }),
    697                 Some(Piece {
    698                     unit: Queen,
    699                     player: White,
    700                     moved: 0,
    701                 }),
    702                 Some(Piece {
    703                     unit: King,
    704                     player: White,
    705                     moved: 0,
    706                 }),
    707                 Some(Piece {
    708                     unit: Bishop,
    709                     player: White,
    710                     moved: 0,
    711                 }),
    712                 Some(Piece {
    713                     unit: Knight,
    714                     player: White,
    715                     moved: 0,
    716                 }),
    717                 Some(Piece {
    718                     unit: Rook,
    719                     player: White,
    720                     moved: 0,
    721                 }),
    722             ],
    723             [
    724                 Some(Piece {
    725                     unit: Pawn,
    726                     player: White,
    727                     moved: 0,
    728                 }),
    729                 Some(Piece {
    730                     unit: Pawn,
    731                     player: White,
    732                     moved: 0,
    733                 }),
    734                 Some(Piece {
    735                     unit: Pawn,
    736                     player: White,
    737                     moved: 0,
    738                 }),
    739                 Some(Piece {
    740                     unit: Pawn,
    741                     player: White,
    742                     moved: 0,
    743                 }),
    744                 Some(Piece {
    745                     unit: Pawn,
    746                     player: White,
    747                     moved: 0,
    748                 }),
    749                 Some(Piece {
    750                     unit: Pawn,
    751                     player: White,
    752                     moved: 0,
    753                 }),
    754                 Some(Piece {
    755                     unit: Pawn,
    756                     player: White,
    757                     moved: 0,
    758                 }),
    759                 Some(Piece {
    760                     unit: Pawn,
    761                     player: White,
    762                     moved: 0,
    763                 }),
    764             ],
    765             [None, None, None, None, None, None, None, None],
    766             [None, None, None, None, None, None, None, None],
    767             [None, None, None, None, None, None, None, None],
    768             [None, None, None, None, None, None, None, None],
    769             [
    770                 Some(Piece {
    771                     unit: Pawn,
    772                     player: Black,
    773                     moved: 0,
    774                 }),
    775                 Some(Piece {
    776                     unit: Pawn,
    777                     player: Black,
    778                     moved: 0,
    779                 }),
    780                 Some(Piece {
    781                     unit: Pawn,
    782                     player: Black,
    783                     moved: 0,
    784                 }),
    785                 Some(Piece {
    786                     unit: Pawn,
    787                     player: Black,
    788                     moved: 0,
    789                 }),
    790                 Some(Piece {
    791                     unit: Pawn,
    792                     player: Black,
    793                     moved: 0,
    794                 }),
    795                 Some(Piece {
    796                     unit: Pawn,
    797                     player: Black,
    798                     moved: 0,
    799                 }),
    800                 Some(Piece {
    801                     unit: Pawn,
    802                     player: Black,
    803                     moved: 0,
    804                 }),
    805                 Some(Piece {
    806                     unit: Pawn,
    807                     player: Black,
    808                     moved: 0,
    809                 }),
    810             ],
    811             [
    812                 Some(Piece {
    813                     unit: Rook,
    814                     player: Black,
    815                     moved: 0,
    816                 }),
    817                 Some(Piece {
    818                     unit: Knight,
    819                     player: Black,
    820                     moved: 0,
    821                 }),
    822                 Some(Piece {
    823                     unit: Bishop,
    824                     player: Black,
    825                     moved: 0,
    826                 }),
    827                 Some(Piece {
    828                     unit: Queen,
    829                     player: Black,
    830                     moved: 0,
    831                 }),
    832                 Some(Piece {
    833                     unit: King,
    834                     player: Black,
    835                     moved: 0,
    836                 }),
    837                 Some(Piece {
    838                     unit: Bishop,
    839                     player: Black,
    840                     moved: 0,
    841                 }),
    842                 Some(Piece {
    843                     unit: Knight,
    844                     player: Black,
    845                     moved: 0,
    846                 }),
    847                 Some(Piece {
    848                     unit: Rook,
    849                     player: Black,
    850                     moved: 0,
    851                 }),
    852             ],
    853         ])
    854     }
    855     /// scenario sets up a board for the given scenario, identified by name.
    856     pub fn scenario(title: &str) -> Option<Self> {
    857         match title {
    858             "castle" => Some(Board::castle_test()),
    859             _ => None,
    860         }
    861     }
    862     /// castle_test creates a new board for testing castle moves.
    863     fn castle_test() -> Self {
    864         use Player::*;
    865         use Unit::*;
    866         Board([
    867             [
    868                 Some(Piece {
    869                     unit: Rook,
    870                     player: White,
    871                     moved: 0,
    872                 }),
    873                 None,
    874                 None,
    875                 Some(Piece {
    876                     unit: King,
    877                     player: White,
    878                     moved: 0,
    879                 }),
    880                 None,
    881                 None,
    882                 None,
    883                 None,
    884             ],
    885             [None, None, None, None, None, None, None, None],
    886             [None, None, None, None, None, None, None, None],
    887             [None, None, None, None, None, None, None, None],
    888             [None, None, None, None, None, None, None, None],
    889             [None, None, None, None, None, None, None, None],
    890             [None, None, None, None, None, None, None, None],
    891             [None, None, None, None, None, None, None, None],
    892         ])
    893     }
    894     /// Get the piece at the specified (x, y) coordinate.
    895     pub fn get(&self, pos: (i32, i32)) -> Option<&Piece> {
    896         let (x, y) = pos;
    897         if x < 0 || y < 0 || x > 7 || y > 7 {
    898             None
    899         } else {
    900             self.0[y as usize][x as usize].as_ref()
    901         }
    902     }
    903     /// Set the piece to the specified (x, y) coordinate.
    904     /// Overwrites anything already at the location.
    905     /// Noop if the coordinates are out of bounds.
    906     pub fn set(&mut self, pos: (i32, i32), p: Piece) {
    907         let (x, y) = pos;
    908         if !(x < 0 || y < 0 || x > 7 || y > 7) {
    909             self.0[y as usize][x as usize] = Some(p);
    910         }
    911     }
    912     /// Move any piece at `from` to `to`.
    913     /// Noop if there is no piece at `from`.
    914     pub fn move_piece(&mut self, from: (i32, i32), to: (i32, i32)) {
    915         if [from.0, from.1, to.0, to.1]
    916             .iter()
    917             .fold(false, |outofbounds, next| {
    918                 outofbounds || *next > 7 || *next < 0
    919             })
    920         {
    921             return;
    922         }
    923         if let Some(Piece {
    924             unit,
    925             player,
    926             moved,
    927         }) = self.0[from.1 as usize][from.0 as usize].take()
    928         {
    929             self.set(
    930                 (to.0, to.1),
    931                 Piece {
    932                     unit: unit,
    933                     player: player,
    934                     moved: moved + 1,
    935                 },
    936             );
    937         }
    938     }
    939     fn iter(&self) -> BoardIter {
    940         BoardIter {
    941             pos: None,
    942             board: self,
    943         }
    944     }
    945 }
    946 
    947 // LineOfSight yields coordinates from a move-set until a piece is found.
    948 // Truncate move-set for Queen/Rook/Bishop such that these pieces cannot
    949 // jump over another.
    950 struct LineOfSight<'a, Moves>
    951 where
    952     Moves: Iterator<Item = (i32, i32)>,
    953 {
    954     moves: Moves,
    955     board: &'a Board,
    956     stop: bool,
    957 }
    958 
    959 impl<'a, Moves> LineOfSight<'a, Moves>
    960 where
    961     Moves: Iterator<Item = (i32, i32)>,
    962 {
    963     fn new(moves: Moves, board: &'a Board) -> Self {
    964         LineOfSight {
    965             moves,
    966             board,
    967             stop: false,
    968         }
    969     }
    970 }
    971 
    972 impl<'a, Moves> Iterator for LineOfSight<'a, Moves>
    973 where
    974     Moves: Iterator<Item = (i32, i32)>,
    975 {
    976     type Item = (i32, i32);
    977     fn next(&mut self) -> Option<Self::Item> {
    978         if self.stop {
    979             return None;
    980         }
    981         match self.moves.next() {
    982             Some((x, y)) => match self.board.get((x, y)) {
    983                 Some(_) => {
    984                     self.stop = true;
    985                     Some((x, y))
    986                 }
    987                 None => Some((x, y)),
    988             },
    989             None => None,
    990         }
    991     }
    992 }
    993 
    994 /// Position is a coordinate on the board, potentially containing a piece.
    995 struct Position<'a> {
    996     piece: Option<&'a Piece>,
    997     x: usize,
    998     y: usize,
    999 }
   1000 
   1001 /// Iterate over a chess board, left to right.
   1002 struct BoardIter<'a> {
   1003     pos: Option<(usize, usize)>,
   1004     board: &'a Board,
   1005 }
   1006 
   1007 impl<'a> Iterator for BoardIter<'a> {
   1008     type Item = Position<'a>;
   1009     fn next(&mut self) -> Option<Self::Item> {
   1010         if let Some((x, y)) = self.pos.as_mut() {
   1011             *x += 1;
   1012             if *x > 7 {
   1013                 *x = 0;
   1014                 *y += 1;
   1015             }
   1016             if *y > 7 {
   1017                 return None;
   1018             }
   1019         } else {
   1020             self.pos = Some((0, 0));
   1021         }
   1022         if let Some((x, y)) = self.pos {
   1023             match self.board.0.get(y) {
   1024                 Some(cell) => match cell.get(x) {
   1025                     Some(piece) => Some(Position {
   1026                         piece: piece.as_ref(),
   1027                         x,
   1028                         y,
   1029                     }),
   1030                     None => None,
   1031                 },
   1032                 None => None,
   1033             }
   1034         } else {
   1035             None
   1036         }
   1037     }
   1038 }