made wish.com paper.io lol

This commit is contained in:
FantasyPvP
2024-11-23 11:03:43 +00:00
parent 1588b4fbf3
commit 69591e6bb2
9 changed files with 497 additions and 6 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"rust-analyzer.cargo.extraArgs": ["-Z", "unstable-options"]
}
+1
View File
@@ -330,6 +330,7 @@ pub fn special_char(ch: char) -> Option<u8> {
'┌' => 218,
'┼' => 197,
'░' => 176,
'▒' => 177,
'▓' => 178,
'«' => 174,
_ => {
+13 -2
View File
@@ -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 » {}
+3
View File
@@ -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};
+4 -4
View File
@@ -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<String>) -> Result<(), Error> {
let d = Display::borrow();
async fn run(&mut self, _args: Vec<String>) -> 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,
+1
View File
@@ -3,3 +3,4 @@ pub mod gameoflife;
pub mod crystalrpg;
pub mod pong;
pub mod snake;
pub mod paper_rs;
+4
View File
@@ -0,0 +1,4 @@
/// this game is basically a ripoff of paper.io
pub mod paper;
pub use paper::GameBoard;
+464
View File
@@ -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<String>) -> 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<Frame, RenderError> {
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<u8>;
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;
}
}
}
}
}
}
+4
View File
@@ -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);