use core::any::Any; use alloc::{boxed::Box, format, string::String, vec::Vec}; use async_trait::async_trait; use crate::{ std::{ self, application::{Application, Error}, io::{Color, ColorCode, Display, KeyStroke, Stdin}, render::{ColouredChar, Dimensions, Frame, Position, RenderError}, time }, user::lib::libgui::cg_core::CgComponent }; use super::player::Player; #[derive(Copy, Clone)] pub enum Cell { Empty, Solid(u8, bool), Tail(u8, bool), Head(u8, bool), } pub struct GameBoard { board: [[Cell; 80]; 25], players: [Player; 6], max_territory: u32, // Track maximum territory captured current_territory: u32, // Track current territory } #[async_trait] impl Application for GameBoard { fn new() -> GameBoard { GameBoard { board: [[Cell::Empty; 80]; 25], players: [ Player::new(0, (10, 10), false), Player::new(1, (70, 10), true), Player::new(2, (10, 15), true), Player::new(3, (70, 15), true), Player::new(4, (35, 5), true), Player::new(5, (35, 20), true), ], max_territory: 0, current_territory: 0, } } async fn run(&mut self, _args: Vec) -> Result<(), Error> { let _display = Display::borrow(); 'outer: loop { // Set initial positions as solid territory for player in &self.players { let (x, y) = player.position; self.board[y as usize][x as usize] = Cell::Head(player.id, true); } let mut player_direction: (i32, i32) = (0, 0); // player controls player 1. loop { self.render().unwrap().write_to_screen().unwrap(); time::wait(0.1); // first get player input if let Some(keystroke) = Stdin::try_keystroke() { match keystroke { KeyStroke::Up => player_direction = (0, -1), KeyStroke::Down => player_direction = (0, 1), KeyStroke::Left => player_direction = (-1, 0), KeyStroke::Right => player_direction = (1, 0), KeyStroke::Char('w') | KeyStroke::Char('W') => player_direction = (0, -1), KeyStroke::Char('s') | KeyStroke::Char('S') => player_direction = (0, 1), KeyStroke::Char('a') | KeyStroke::Char('A') => player_direction = (-1, 0), KeyStroke::Char('d') | KeyStroke::Char('D') => player_direction = (1, 0), KeyStroke::Char('`') => break 'outer, _ => {}, } } // perform movement commands self.move_player(0, player_direction.0, player_direction.1); self.update_ai_players(); self.run_fill_algorithm(); if !self.players[0].alive { self.render_end_screen().await.unwrap(); break; } } // Wait for any key to exit match Stdin::keystroke().await { KeyStroke::Char('`') => break 'outer, _ => self.reset(), } } Ok(()) } } impl CgComponent for GameBoard { fn render(&self) -> Result { let mut frame = Frame::new(Position::new(0, 0), Dimensions::new(80, 25))?; for i in 0..25 { for j in 0..80 { let character: char; let pid: Option; match self.board[i][j] { Cell::Empty => { character = ' '; pid = None; } Cell::Solid(p, _) => { character = '█'; pid = Some(p); } Cell::Tail(p, _) => { character = '▒'; pid = Some(p); } Cell::Head(p, _) => { character = '▓'; pid = Some(p); } } let colour = if let Some(p) = pid { match p { 0 => ColorCode::new(Color::Green, Color::Black), 1 => ColorCode::new(Color::Blue, Color::Black), 2 => ColorCode::new(Color::Red, Color::Black), 3 => ColorCode::new(Color::Yellow, Color::Black), 4 => ColorCode::new(Color::Magenta, Color::Black), _ => ColorCode::new(Color::Cyan, Color::Black), } } else { ColorCode::new(Color::White, Color::Black) }; frame[i][j] = ColouredChar { character, colour, }; } } Ok(frame) } fn as_any(&self) -> &dyn core::any::Any { self as &dyn Any } } impl GameBoard { const UP: (i32, i32) = (0, -1); const DOWN: (i32, i32) = (0, 1); const LEFT: (i32, i32) = (-1, 0); const RIGHT: (i32, i32) = (1, 0); const DIRS: [(i32, i32); 8] = [ Self::UP, Self::DOWN, Self::LEFT, Self::RIGHT, (1, 1), (1, -1), (-1, 1), (-1, -1), ]; fn reset(&mut self) { self.board = [[Cell::Empty; 80]; 25]; self.players = [ Player::new(0, (10, 10), false), Player::new(1, (70, 10), true), Player::new(2, (10, 15), true), Player::new(3, (70, 15), true), Player::new(4, (35, 5), true), Player::new(5, (35, 20), true), ]; // Set initial positions as solid territory for player in &self.players { let (x, y) = player.position; self.board[y as usize][x as usize] = Cell::Head(player.id, true); } self.max_territory = 0; self.current_territory = 0; } fn respawn_ai_player(&mut self, player_id: u8) { // Try up to 10 times to find a valid spawn position for _ in 0..10 { let x = std::random::Random::int(5, 75) as i32; let y = std::random::Random::int(5, 20) as i32; // Check if position is empty or not in someone's territory if let Cell::Empty = self.board[y as usize][x as usize] { let dir_idx = std::random::Random::int(0, 3) as usize; self.players[player_id as usize] = Player::new(player_id, (x, y), true); // Place the head at spawn position self.board[y as usize][x as usize] = Cell::Head(player_id, true); return; } } // If we couldn't find a spot after 10 tries, just keep the player dead self.players[player_id as usize].alive = false; } fn count_territory(&self) -> u32 { let mut count = 0; for row in &self.board { for cell in row { if let Cell::Solid(0, true) = cell { count += 1; } } } count } async fn render_end_screen(&mut self) -> Result<(), RenderError> { let mut frame = self.render()?; // Game Over message let game_over_text = "Game Over!"; let center_y = 12; let center_x = 40 - (game_over_text.len() / 2) as u16; // Draw the game over text for (i, c) in game_over_text.chars().enumerate() { let pos = center_x + i as u16; frame[center_y][pos as usize] = ColouredChar { character: c, colour: ColorCode::new(Color::White, Color::Black), }; } // Show player's score let score_text = format!("Score: {}", self.current_territory); let score_x = 40 - (score_text.len() / 2) as u16; for (i, c) in score_text.chars().enumerate() { let pos = score_x + i as u16; frame[center_y + 1][pos as usize] = ColouredChar { character: c, colour: ColorCode::new(Color::White, Color::Black), }; } // Show restart instruction let restart_text = "Press any key to restart"; let restart_x = 40 - (restart_text.len() / 2) as u16; for (i, c) in restart_text.chars().enumerate() { let pos = restart_x + i as u16; frame[center_y + 2][pos as usize] = ColouredChar { character: c, colour: ColorCode::new(Color::White, Color::Black), }; } frame.write_to_screen()?; Ok(()) } fn move_player(&mut self, playerid: u8, dx: i32, dy: i32) { if let Some(eliminated_id) = self.players[playerid as usize].move_player(dx, dy, &mut self.board) { self.players[eliminated_id as usize].alive = false; // Clear territory and respawn if AI if eliminated_id != 0 { self.clear_player_territory(eliminated_id); self.respawn_ai_player(eliminated_id); } else { // Update max territory before game over self.current_territory = self.count_territory(); self.max_territory = self.max_territory.max(self.current_territory); } } } fn update_ai_players(&mut self) { // Skip player 0 (human player) for i in 1..6 { let dir = self.players[i].update_ai(&self.board); if dir != (0, 0) { self.move_player(i as u8, dir.0, dir.1); } } } fn run_fill_algorithm(&mut self) { // Pre-allocate a single reusable grid let mut fill_grid = [[false; 80]; 25]; let mut queue = Vec::with_capacity(80 * 25); // Process for each alive player for player in self.players.iter().filter(|p| p.alive) { let pid = player.id; // Check if player's head is touching their territory let (px, py) = player.position; let mut head_touching_territory = false; // Check all adjacent cells to head for (dy, dx) in Self::DIRS.iter().take(4) { // Only check cardinal directions let ny = py + dy; let nx = px + dx; if ny >= 0 && ny < 25 && nx >= 0 && nx < 80 { if let Cell::Solid(p, _) = self.board[ny as usize][nx as usize] { if p == pid { head_touching_territory = true; break; } } } } // Skip if head is not touching territory if !head_touching_territory { continue; } // Reset grid for row in fill_grid.iter_mut() { row.fill(false); } // Mark all player lines as walls but don't fill them yet for y in 0..25 { for x in 0..80 { if let Cell::Head(p, _) | Cell::Tail(p, _) | Cell::Solid(p, _) = self.board[y][x] { if p == pid { fill_grid[y][x] = true; } } } } // Clear queue for reuse queue.clear(); // Add edge cells to queue for x in 0..80 { if !fill_grid[0][x] { queue.push((0, x)); fill_grid[0][x] = true; } if !fill_grid[24][x] { queue.push((24, x)); fill_grid[24][x] = true; } } for y in 1..24 { if !fill_grid[y][0] { queue.push((y, 0)); fill_grid[y][0] = true; } if !fill_grid[y][79] { queue.push((y, 79)); fill_grid[y][79] = true; } } // Mark cells that are reachable from outside while let Some((y, x)) = queue.pop() { // Mark current cell as visited in the fill grid fill_grid[y][x] = true; // Check all 8 directions for (dy, dx) in Self::DIRS { let ny = (y as i32 + dy) as usize; let nx = (x as i32 + dx) as usize; if ny < 25 && nx < 80 && !fill_grid[ny][nx] { fill_grid[ny][nx] = true; queue.push((ny, nx)); } } } // Check if we found any cells to fill let mut found_territory = false; 'outer: for y in 0..25 { for x in 0..80 { if !fill_grid[y][x] && matches!(self.board[y][x], Cell::Empty) { found_territory = true; break 'outer; } } } // Only convert tails if we actually filled some territory if found_territory { // First convert all tails to solid for this player for y in 0..25 { for x in 0..80 { if let Cell::Tail(p, _) = self.board[y][x] { if p == pid { self.board[y][x] = Cell::Solid(pid, true); } } if let Cell::Head(p, _) = self.board[y][x] { if p == pid { self.board[y][x] = Cell::Head(pid, true); } } } } } // Convert all unreached cells to territory for y in 0..25 { for x in 0..80 { if !fill_grid[y][x] { self.board[y][x] = Cell::Solid(pid, true); } } } } } fn clear_player_territory(&mut self, player_id: u8) { for y in 0..25 { for x in 0..80 { if let Cell::Head(pid, _) | Cell::Tail(pid, _) | Cell::Solid(pid, _) = self.board[y][x] { if pid == player_id { self.board[y][x] = Cell::Empty; } } } } } }