From 69591e6bb27d33dbc51e3033502aa484b71516b4 Mon Sep 17 00:00:00 2001 From: FantasyPvP <80643031+FantasyPvP@users.noreply.github.com> Date: Sat, 23 Nov 2024 11:03:43 +0000 Subject: [PATCH] made wish.com paper.io lol --- .vscode/settings.json | 3 + src/system/kernel/render.rs | 1 + src/user/bin/crystalfetch.rs | 15 +- src/user/bin/editor.rs | 3 + src/user/bin/games/asteroids.rs | 8 +- src/user/bin/games/mod.rs | 1 + src/user/bin/games/paper_rs/mod.rs | 4 + src/user/bin/games/paper_rs/paper.rs | 464 +++++++++++++++++++++++++++ src/user/bin/shell.rs | 4 + 9 files changed, 497 insertions(+), 6 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/user/bin/games/paper_rs/mod.rs create mode 100644 src/user/bin/games/paper_rs/paper.rs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..eb63768 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.cargo.extraArgs": ["-Z", "unstable-options"] +} diff --git a/src/system/kernel/render.rs b/src/system/kernel/render.rs index 09a63de..a4eb726 100644 --- a/src/system/kernel/render.rs +++ b/src/system/kernel/render.rs @@ -330,6 +330,7 @@ pub fn special_char(ch: char) -> Option { '┌' => 218, '┼' => 197, '░' => 176, + '▒' => 177, '▓' => 178, '«' => 174, _ => { diff --git a/src/user/bin/crystalfetch.rs b/src/user/bin/crystalfetch.rs index 72ab279..c363799 100644 --- a/src/user/bin/crystalfetch.rs +++ b/src/user/bin/crystalfetch.rs @@ -8,7 +8,7 @@ use crate::std::{ }; use crate::println; -const CRYSTAL_LOGO: &str = +const _CRYSTAL_LOGO: &str = "\n $$$$$$\\ $$\\ $$\\ $$$$$$\\ $$$$$$\\ $$ __$$\\ $$ | $$ $$ __$$\\$$ __$$\\ $$ / \\__|$$$$$$\\ $$\\ $$\\ $$$$$$$\\$$$$$$\\ $$$$$$\\ $$ $$ / $$ $$ / \\__| @@ -27,6 +27,17 @@ const CRYSTAL_LOGO: &str = const ZXQ5_LOGO: &str = " + /$$$$$$$$ /$$ /$$ /$$$$$$ /$$$$$$ /$$ + |_____ $$ | $$ / $$ /$$__ $$ /$$__ $$ /$$$$ + /$$/ | $$/ $$/| $$ \\ $$| $$ \\__/ /$$ /$$|_ $$ + /$$/ \\ $$$$/ | $$ | $$| $$$$$$ | $$ /$$/ | $$ + /$$/ >$$ $$ | $$ | $$|\\____ $$ \\ $$/$$/ | $$ + /$$/ /$$/\\ $$| $$/$$ $$ /$$ \\ $$ \\ $$$/ | $$ + /$$$$$$$$| $$ \\ $$| $$$$$$/| $$$$$$/ \\ $/ /$$$$$$ + |________/|__/ |__/ \\____ $$$ \\______/ \\_/ |______/ +"; +const _ZXQ5_LOGO_OLD: &str = " + /$$$$$$$$ /$$ /$$ /$$$$$$ /$$$$$$ /$$$$$$ /$$ |_____ $$ | $$ / $$ /$$__ $$ /$$__ $$ /$$__ $$ /$$$$ /$$/ | $$/ $$/| $$ \\ $$| $$ \\ $$| $$ \\__/ /$$ /$$|_ $$ @@ -51,7 +62,7 @@ impl Application for CrystalFetch { Screen::clear(); - let logo_string = CRYSTAL_LOGO; + let logo_string = ZXQ5_LOGO; let info_string = format!( " [ OS » {} [ BUILD » {} diff --git a/src/user/bin/editor.rs b/src/user/bin/editor.rs index a3bf9e9..c81997d 100644 --- a/src/user/bin/editor.rs +++ b/src/user/bin/editor.rs @@ -4,6 +4,9 @@ use crate::std::io::{Color, ColorCode, Display, KeyStroke, Screen}; use crate::std::render::{ColouredChar, Frame, Position, RenderError}; use crate::user::lib::libgui::cg_core::CgComponent; + + + use alloc::format; use alloc::{string::{String, ToString}, vec::Vec, boxed::Box}; diff --git a/src/user/bin/games/asteroids.rs b/src/user/bin/games/asteroids.rs index fd2be50..058455a 100644 --- a/src/user/bin/games/asteroids.rs +++ b/src/user/bin/games/asteroids.rs @@ -1,7 +1,7 @@ use crate::std::application::Error; use crate::std::application::Error::ApplicationError; use crate::std::render::{ColouredChar, Dimensions, Frame, Position, RenderError}; -use crate::std::io::{Color, ColorCode, Display, KeyStroke, Screen, Stdin}; +use crate::std::io::{Color, ColorCode, Display, KeyStroke, Stdin}; use crate::std::random::Random; use crate::system::std::application::Application; use crate::user::lib::libgui::cg_core::{CgComponent, Widget}; @@ -52,8 +52,8 @@ impl Application for Game { timer: GameTimer::new(), } } - async fn run(&mut self, args: Vec) -> Result<(), Error> { - let d = Display::borrow(); + async fn run(&mut self, _args: Vec) -> Result<(), Error> { + let _d = Display::borrow(); let mut container_data = CgContainer::new(Position::new(0, 0), Dimensions::new(80, 25), true); @@ -147,7 +147,7 @@ impl Game { self.timer.advance(); let game_update_delay = 5.0 / self.gamespeed; - let enemy_spawn_time = match self.difficulty_idx { + let _enemy_spawn_time = match self.difficulty_idx { 1 => 10, 2 => 10, 3 => 10, diff --git a/src/user/bin/games/mod.rs b/src/user/bin/games/mod.rs index 456c80c..784b6f9 100644 --- a/src/user/bin/games/mod.rs +++ b/src/user/bin/games/mod.rs @@ -3,3 +3,4 @@ pub mod gameoflife; pub mod crystalrpg; pub mod pong; pub mod snake; +pub mod paper_rs; \ No newline at end of file diff --git a/src/user/bin/games/paper_rs/mod.rs b/src/user/bin/games/paper_rs/mod.rs new file mode 100644 index 0000000..dc79c47 --- /dev/null +++ b/src/user/bin/games/paper_rs/mod.rs @@ -0,0 +1,4 @@ +/// this game is basically a ripoff of paper.io +pub mod paper; + +pub use paper::GameBoard; \ No newline at end of file diff --git a/src/user/bin/games/paper_rs/paper.rs b/src/user/bin/games/paper_rs/paper.rs new file mode 100644 index 0000000..2ee35e0 --- /dev/null +++ b/src/user/bin/games/paper_rs/paper.rs @@ -0,0 +1,464 @@ +use core::any::Any; + +use alloc::{boxed::Box, format, string::String, vec::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::{bin::games::asteroids::Game, lib::libgui::cg_core::CgComponent}}; + + +#[derive(Copy, Clone)] +enum Cell { + Empty, + Solid(u8, bool), + Tail(u8, bool), + Head(u8, bool), +} + +#[derive(Copy, Clone)] +struct Player { + id: u8, + alive: bool, + position: (i32, i32), + ai_ticks: u32, // Ticks until next direction change + ai_direction: (i32, i32), // Current direction for AI +} + +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 { id: 0, alive: true, position: (10, 10), ai_ticks: 0, ai_direction: (1, 0) }, + Player { id: 1, alive: true, position: (70, 10), ai_ticks: 5, ai_direction: (-1, 0) }, + Player { id: 2, alive: true, position: (10, 15), ai_ticks: 10, ai_direction: (1, 0) }, + Player { id: 3, alive: true, position: (70, 15), ai_ticks: 15, ai_direction: (-1, 0) }, + Player { id: 4, alive: true, position: (35, 5), ai_ticks: 20, ai_direction: (0, 1) }, + Player { id: 5, alive: true, position: (35, 20), ai_ticks: 25, ai_direction: (0, -1) }, + ], + max_territory: 0, + current_territory: 0, + } + } + + async fn run(&mut self, args: Vec) -> Result<(), Error> { + let _display = Display::borrow(); + + 'outer: loop { + + self.players.clone().into_iter().for_each(|p| { + self.move_player(p.id, 0, 0); + }); + + let mut player_direction: (i32, i32) = (0, 0); + + // player controls player 1. + loop { + 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(); + + self.render().unwrap().write_to_screen().unwrap(); + + 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, // only cardinal directions + (1, 1), (1, -1), (-1, 1), (-1, -1), + ]; + + + fn reset(&mut self) { + self.board = [[Cell::Empty; 80]; 25]; + self.players = [ + Player { id: 0, alive: true, position: (10, 10), ai_ticks: 0, ai_direction: (1, 0) }, + Player { id: 1, alive: true, position: (70, 10), ai_ticks: 5, ai_direction: (-1, 0) }, + Player { id: 2, alive: true, position: (10, 15), ai_ticks: 10, ai_direction: (1, 0) }, + Player { id: 3, alive: true, position: (70, 15), ai_ticks: 15, ai_direction: (-1, 0) }, + Player { id: 4, alive: true, position: (35, 5), ai_ticks: 20, ai_direction: (0, 1) }, + Player { id: 5, alive: true, position: (35, 20), ai_ticks: 25, ai_direction: (0, -1) }, + ]; + 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 { + id: player_id, + alive: true, + position: (x, y), + ai_ticks: std::random::Random::int(5, 10) as u32, + ai_direction: Self::DIRS[dir_idx], + }; + // Place the head at spawn position + self.board[y as usize][x as usize] = Cell::Head(player_id, false); + 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) { + let (ox, oy) = self.players[playerid as usize].position; + let nx = ox + dx; + let ny = oy + dy; + + // Bounds checking + if nx < 0 || nx >= 80 || ny < 0 || ny >= 25 { + if playerid == 0 { + self.players[playerid as usize].alive = false; + } + return; + } + + // Check for collisions with tails + let mut player_to_eliminate = None; + + // check if player hit an enemy's tail + match self.board[ny as usize][nx as usize] { + Cell::Tail(id, _) => { + if id != playerid { + // Hit other player's tail + player_to_eliminate = Some(id); + } + } + _ => {} + } + + // Convert old head to tail + if let Cell::Head(id, owned) = self.board[oy as usize][ox as usize] { + if owned { + self.board[oy as usize][ox as usize] = Cell::Solid(id, true); + } else { + self.board[oy as usize][ox as usize] = Cell::Tail(id, false); + } + } + + // Place new head + if let Cell::Solid(p, _) = self.board[ny as usize][nx as usize] { + self.board[ny as usize][nx as usize] = Cell::Head(playerid, p == playerid); + } else { + self.board[ny as usize][nx as usize] = Cell::Head(playerid, false); + } + + // Update the player's position + self.players[playerid as usize].position = (nx, ny); + + // Handle elimination if needed + if let Some(pid) = player_to_eliminate { + self.players[pid as usize].alive = false; + // Clear territory and respawn if AI + if pid != 0 { + self.clear_player_territory(pid); + self.respawn_ai_player(pid); + } 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 { + if !self.players[i].alive { + continue; + } + + // Decrease tick counter + if self.players[i].ai_ticks > 0 { + self.players[i].ai_ticks -= 1; + } else { + // Time to change direction + let random_dir = std::random::Random::int(0, 3); + self.players[i].ai_direction = Self::DIRS[random_dir as usize]; // + self.players[i].ai_ticks = 5 + (random_dir % 5) as u32; // Random ticks between 5-10 + } + + // Move in current direction + let dir = self.players[i].ai_direction; + 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; + + // 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 four 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; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/user/bin/shell.rs b/src/user/bin/shell.rs index 4d07eec..5a216ce 100644 --- a/src/user/bin/shell.rs +++ b/src/user/bin/shell.rs @@ -138,6 +138,10 @@ async fn exec() -> Result<(), Error> { "games/pong" => { pong::Game::new().run(args).await?; } + "games/paper.rs" => { + let mut game = paper_rs::GameBoard::new(); + game.run(args).await?; + } "serial" => { let c = Serial::reply_char('e'); println!("{}", c);