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 }