diff --git a/README.md b/README.md index f1197ac..552ccea 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## Snake in rust using [ncurses](https://docs.rs/ncurses) +## Snake in rust using [ncurses](https://docs.rs/ncurses) crate The game is playable @@ -18,7 +18,19 @@ esc or p to pause Click the image for asciinema -[](https://asciinema.org/a/PtMG7dghPAEZ7tNgx70sKplKq?autoplay=1) +[](https://asciinema.org/a/FVB1DZmA7lVK4BILFkzkreMQ9?autoplay=1) + +#### Need unicode support in terminal to print the snake + +The unicode characters used are: + +- 0x0298 ʘ +- 0x2550 ═ +- 0x2551 ║ +- 0x2554 ╔ +- 0x2557 ╗ +- 0x255a ╚ +- 0x255d ╝ #### Todo :construction: @@ -28,13 +40,16 @@ Click the image for asciinema - [ ] Implement Highscore System - [ ] Make the ui - [ ] Internal Implementation +- [x] Use unicode charachters to draw the snake #### Bugs :bug: - ~~Snake going through the walls~~ - ~~Food spawning in the walls~~ +- ~~Boxdraw characters not rendering properly~~ - Remove all the logging in the ui - Pausing delayed if esc is pressed but not if p is pressed. +- The game flickers a lot after sometime (probably due to the constant redrawing of the whole board every tick) #### Maybe in the future @@ -42,19 +57,32 @@ Click the image for asciinema #### Notes -A few notes about the complexity of the game and how I should improve the game +A few notes about the game and how I should improve the game
-Read More +A few notes about time complexity and redrawing with ncurses -The complexity of the program is O(n) every tick (time which changes relative to the speed) +> The complexity of the program is O(n) every tick (time which changes relative to the speed) -However the place, I can improve is the redrawing of the game +> However the place, I can improve is the redrawing of the game -As of commit -f9be68e -the game redraws the total board and the total snake every tick. +> At the time of writing the in commit +> f9be68e +> the game still redraws the total board and the total snake every tick. -I think this can be improved by only drawing the parts of the snake and the board when needed +> I think this can be improved by only drawing the parts of the snake and the board when needed + +
+
+ +
+ +Rust specific stuff I learned + +> [char](https://doc.rust-lang.org/stable/std/primitive.char.html) and [std::char](https://doc.rust-lang.org/stable/std/char/index.html) are different + +> [char](https://doc.rust-lang.org/stable/std/primitive.char.html) is the primitive type [std::char](https://doc.rust-lang.org/stable/std/char/index.html) is the char module. All the functions are not completely same for both (as of rust v1.49.0) + +> [ncursesw](httsp://docs.rs/ncursesw) is not needed to print unicode characters. I have no clue where I got that idea from.
diff --git a/images/screenshot.png b/images/screenshot.png index 2515d7d..80bcdac 100644 Binary files a/images/screenshot.png and b/images/screenshot.png differ diff --git a/src/game/backend.rs b/src/game/backend.rs index 83d2ef7..bc5b578 100644 --- a/src/game/backend.rs +++ b/src/game/backend.rs @@ -5,6 +5,7 @@ use std::collections::LinkedList; use std::ops::Sub; use std::thread::sleep; use std::time::Duration; +#[derive(Debug)] pub enum Direction { Up, Down, @@ -39,6 +40,20 @@ impl Direction { } } } +#[derive(Debug)] +// impl std::fmt::Debug for Direction { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// // write!(f,) +// write!( +// f, +// "{}", +// match *self { +// Self::Up => (), +// _ => (), +// } +// ); +// } +// } pub enum CellType { Food, @@ -89,10 +104,10 @@ impl Cell { } pub fn is_adjacent(&self, other: &Cell) -> Option { match *self - *other { - (0, 1) => Some(Direction::Left), + (0, 1) => Some(Direction::Right), (1, 0) => Some(Direction::Down), - (-1, 0) => Some(Direction::Right), - (0, -1) => Some(Direction::Up), + (-1, 0) => Some(Direction::Up), + (0, -1) => Some(Direction::Left), _ => None, } } diff --git a/src/game/frontend.rs b/src/game/frontend.rs index 5cccbf7..df6475b 100644 --- a/src/game/frontend.rs +++ b/src/game/frontend.rs @@ -10,8 +10,6 @@ pub fn game_window(mlines: i32, mcols: i32, vmargin: i32, hmargin: i32) -> WINDO let (starty, startx): (i32, i32); lines = mlines - vmargin * 2; cols = mcols - hmargin * 2; - // starty = (mlines - lines) / 2; - // startx = (mcols - cols) / 2; starty = vmargin; startx = hmargin; game_win = newwin(lines, cols, starty, startx); @@ -41,54 +39,67 @@ pub fn draw_snake(snake: &Snake, game_win: WINDOW) { // So I'll need to know the last and next cell of the snake to draw the current snake_cell // For some reason the snake goes invisible after the first food prev = snake_iter.next().unwrap(); // currently this should be head. On initial run this should be the only snake_cell - mvwaddstr(game_win, prev.posyx().0, prev.posyx().1, &format!("H")); + mvwaddstr( + game_win, + prev.posyx().0, + prev.posyx().1, + &format!("{}", std::char::from_u32(0x0298).unwrap_or('O')), + ); let _current = snake_iter.next(); - // current = match _current { - // Some(cell) => cell, - // None => return, - // }; - if _current.is_some() { - current = _current.unwrap(); - } else { - return; - } // the match is equivalent to this block of code - // this should be none in the initial run - // as of now this will panic as soon as the game starts - // I need to match this with Some(Cell) and None and if - // none then just straight exit from the function - - // The head is never in current so the head is not being printed as of now + current = match _current { + Some(cell) => cell, + None => return, + }; for next in snake_iter { // O(n) the whole snake is redrawn every single tick let (snake_l, snake_c): (i32, i32) = current.posyx(); // mvwaddstr(game_win, snake_l, snake_c, "o"); - let snake_char = match ( + let snake_char: u32 = match ( prev.is_adjacent(current).unwrap(), next.is_adjacent(current).unwrap(), ) { - (Direction::Up, Direction::Down) | (Direction::Down, Direction::Up) => "║", //186, //boxdraw double vertical line ║ code 186 - (Direction::Up, Direction::Left) | (Direction::Left, Direction::Left) => "╝", //188, // ╝ - (Direction::Up, Direction::Right) | (Direction::Right, Direction::Up) => "╚", //200, // ╚ - (Direction::Down, Direction::Left) | (Direction::Left, Direction::Down) => "╗", //187, // ╗ - (Direction::Down, Direction::Right) | (Direction::Right, Direction::Down) => "╔", //,201, // ╔ - (Direction::Left, Direction::Right) | (Direction::Right, Direction::Left) => "═", //205 ═ - _ => " ", + (Direction::Up, Direction::Down) | (Direction::Down, Direction::Up) => 0x2551, //"║" + (Direction::Up, Direction::Left) | (Direction::Left, Direction::Up) => 0x255d, //"╝" + (Direction::Up, Direction::Right) | (Direction::Right, Direction::Up) => 0x255a, //"╚" + (Direction::Down, Direction::Left) | (Direction::Left, Direction::Down) => 0x2557, // "╗" + (Direction::Down, Direction::Right) | (Direction::Right, Direction::Down) => 0x2554, //"╔" + (Direction::Left, Direction::Right) | (Direction::Right, Direction::Left) => 0x2550, //"═" + _ => 0x20, }; mvwaddstr( - game_win, snake_l, snake_c, // &format!("{}", snake_char as char), - // &format!("{}", snake_char), - "o", + game_win, + snake_l, + snake_c, + &format!("{}", std::char::from_u32(snake_char).unwrap_or('o')), ); prev = current; current = next; } - mvwaddstr(game_win, current.posyx().0, current.posyx().1, "t"); + + mvwaddstr( + game_win, + current.posyx().0, + current.posyx().1, + &format!( + "{}", + std::char::from_u32(match current.is_adjacent(prev).unwrap() { + Direction::Up | Direction::Down => 0x2551, + Direction::Left | Direction::Right => 0x2550, + }) + .unwrap_or('o') + ), + ); wrefresh(game_win); } pub fn draw_board(board: &Board, game_win: WINDOW) { let (food_l, food_c): (i32, i32) = board.food_posyx(); - mvwaddstr(game_win, food_l, food_c, "F"); + mvwaddstr( + game_win, + food_l, + food_c, + &format!("{}", std::char::from_u32(0x0298).unwrap_or('F')), + ); } pub fn _log(snake: &Snake, board: &Board) { @@ -96,26 +107,16 @@ pub fn _log(snake: &Snake, board: &Board) { let (bfl, bfc): (i32, i32) = board.food_posyx(); mvwaddstr(stdscr(), 0, 0, &format!("snake:head: {} {} ", shl, shc)); mvwaddstr(stdscr(), 1, 0, &format!("board:food: {} {} ", bfl, bfc)); - // mvwaddstr( - // stdscr(), - // 2, - // 0, - // &format!( - // "board:maxlines {} maxcols {}", - // board.maxlines, board.maxcols - // ), - // ); - wmove(stdscr(), 2, 0); for snake_cell in snake.iter() { - let (scl, scc): (i32, i32) = snake_cell.posyx(); - waddstr(stdscr(), &format!("cell: {} {} ", scl, scc)); + waddstr( + stdscr(), + &format!( + "", + snake_cell.posyx().0, + snake_cell.posyx().1 + ), + ); } - mvwaddstr( - stdscr(), - 3, - 0, - &format!("snake_size {}", snake.iter().size_hint().0), - ); wrefresh(stdscr()); } diff --git a/src/game/mod.rs b/src/game/mod.rs index f2c30b6..f3e6a20 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -24,7 +24,7 @@ pub fn start() { loop { frontend::draw_snake(&snake, game_win); // always draw snake before board because the snake will clear the game win frontend::draw_board(&board, game_win); - frontend::_log(&snake, &board); + // frontend::_log(&snake, &board); if board.check_collision(&snake) { // Add stuff here to show the score and // how You lose screen diff --git a/src/main.rs b/src/main.rs index 51ba3f0..55434c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,11 +5,13 @@ mod menu; // use game::{Cell, Snake}; // use ncurses::*; use ncurses::{ - curs_set, endwin, getmaxyx, initscr, keypad, noecho, raw, refresh, stdscr, CURSOR_VISIBILITY, + curs_set, endwin, getmaxyx, initscr, keypad, noecho, raw, refresh, setlocale, stdscr, + LcCategory, CURSOR_VISIBILITY, }; fn main() { // let (lines, cols): (i32, i32) = (0, 0); + setlocale(LcCategory::all, ""); initscr(); raw(); keypad(stdscr(), true); @@ -17,10 +19,6 @@ fn main() { noecho(); let (mut mlines, mut mcols): (i32, i32) = (0, 0); getmaxyx(stdscr(), &mut mlines, &mut mcols); - // let (mlines, mcols) = match getmaxyx(stdscr()) { - // Ok(size) => (size.lines, size.columns), - // Err(e) => panic!(e), - // }; if (mlines < 20) || (mcols < 35) { refresh(); endwin();