Add unicode snake printing

Removed a lot of unused and commented code
This commit is contained in:
Uttarayan Mondal
2021-01-24 03:12:13 +05:30
parent c2eff08c9f
commit eeaa8a2675
6 changed files with 110 additions and 68 deletions
+38 -10
View File
@@ -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 The game is playable
@@ -18,7 +18,19 @@ esc or p to pause
Click the image for asciinema Click the image for asciinema
[<img src="images/screenshot.png" width="650" />](https://asciinema.org/a/PtMG7dghPAEZ7tNgx70sKplKq?autoplay=1) [<img src="images/screenshot.png" width="650" />](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: #### Todo :construction:
@@ -28,13 +40,16 @@ Click the image for asciinema
- [ ] Implement Highscore System - [ ] Implement Highscore System
- [ ] Make the ui - [ ] Make the ui
- [ ] Internal Implementation - [ ] Internal Implementation
- [x] Use unicode charachters to draw the snake
#### Bugs :bug: #### Bugs :bug:
- ~~Snake going through the walls~~ - ~~Snake going through the walls~~
- ~~Food spawning in the walls~~ - ~~Food spawning in the walls~~
- ~~Boxdraw characters not rendering properly~~
- Remove all the logging in the ui - Remove all the logging in the ui
- Pausing delayed if esc is pressed but not if p is pressed. - 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 #### Maybe in the future
@@ -42,19 +57,32 @@ Click the image for asciinema
#### Notes #### 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
<details> <details>
<summary>Read More</summary> <summary>A few notes about time complexity and redrawing with ncurses</summary>
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 > At the time of writing the in commit
<a href="https://github.com/uttarayan21/snake/commit/de66f7d249a56f883dd632598a4178b1bd1320ba">f9be68e</a> > <a href="https://github.com/uttarayan21/snake/commit/de66f7d249a56f883dd632598a4178b1bd1320ba">f9be68e</a>
the game redraws the total board and the total snake every tick. > 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
</details>
<br>
<details>
<summary>Rust specific stuff I learned</summary>
> [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.
</details> </details>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 57 KiB

+18 -3
View File
@@ -5,6 +5,7 @@ use std::collections::LinkedList;
use std::ops::Sub; use std::ops::Sub;
use std::thread::sleep; use std::thread::sleep;
use std::time::Duration; use std::time::Duration;
#[derive(Debug)]
pub enum Direction { pub enum Direction {
Up, Up,
Down, 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 { pub enum CellType {
Food, Food,
@@ -89,10 +104,10 @@ impl Cell {
} }
pub fn is_adjacent(&self, other: &Cell) -> Option<Direction> { pub fn is_adjacent(&self, other: &Cell) -> Option<Direction> {
match *self - *other { match *self - *other {
(0, 1) => Some(Direction::Left), (0, 1) => Some(Direction::Right),
(1, 0) => Some(Direction::Down), (1, 0) => Some(Direction::Down),
(-1, 0) => Some(Direction::Right), (-1, 0) => Some(Direction::Up),
(0, -1) => Some(Direction::Up), (0, -1) => Some(Direction::Left),
_ => None, _ => None,
} }
} }
+50 -49
View File
@@ -10,8 +10,6 @@ pub fn game_window(mlines: i32, mcols: i32, vmargin: i32, hmargin: i32) -> WINDO
let (starty, startx): (i32, i32); let (starty, startx): (i32, i32);
lines = mlines - vmargin * 2; lines = mlines - vmargin * 2;
cols = mcols - hmargin * 2; cols = mcols - hmargin * 2;
// starty = (mlines - lines) / 2;
// startx = (mcols - cols) / 2;
starty = vmargin; starty = vmargin;
startx = hmargin; startx = hmargin;
game_win = newwin(lines, cols, starty, startx); 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 // 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 // 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 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(); let _current = snake_iter.next();
// current = match _current { current = match _current {
// Some(cell) => cell, Some(cell) => cell,
// None => return, 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
for next in snake_iter { for next in snake_iter {
// O(n) the whole snake is redrawn every single tick // O(n) the whole snake is redrawn every single tick
let (snake_l, snake_c): (i32, i32) = current.posyx(); let (snake_l, snake_c): (i32, i32) = current.posyx();
// mvwaddstr(game_win, snake_l, snake_c, "o"); // mvwaddstr(game_win, snake_l, snake_c, "o");
let snake_char = match ( let snake_char: u32 = match (
prev.is_adjacent(current).unwrap(), prev.is_adjacent(current).unwrap(),
next.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::Down) | (Direction::Down, Direction::Up) => 0x2551, //"║"
(Direction::Up, Direction::Left) | (Direction::Left, Direction::Left) => "", //188, // ╝ (Direction::Up, Direction::Left) | (Direction::Left, Direction::Up) => 0x255d, //"╝"
(Direction::Up, Direction::Right) | (Direction::Right, Direction::Up) => "", //200, // ╚ (Direction::Up, Direction::Right) | (Direction::Right, Direction::Up) => 0x255a, //"╚"
(Direction::Down, Direction::Left) | (Direction::Left, Direction::Down) => "", //187, // ╗ (Direction::Down, Direction::Left) | (Direction::Left, Direction::Down) => 0x2557, // "╗"
(Direction::Down, Direction::Right) | (Direction::Right, Direction::Down) => "", //,201, // ╔ (Direction::Down, Direction::Right) | (Direction::Right, Direction::Down) => 0x2554, //"╔"
(Direction::Left, Direction::Right) | (Direction::Right, Direction::Left) => "", //205 ═ (Direction::Left, Direction::Right) | (Direction::Right, Direction::Left) => 0x2550, //"═"
_ => " ", _ => 0x20,
}; };
mvwaddstr( mvwaddstr(
game_win, snake_l, snake_c, // &format!("{}", snake_char as char), game_win,
// &format!("{}", snake_char), snake_l,
"o", snake_c,
&format!("{}", std::char::from_u32(snake_char).unwrap_or('o')),
); );
prev = current; prev = current;
current = next; 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); wrefresh(game_win);
} }
pub fn draw_board(board: &Board, game_win: WINDOW) { pub fn draw_board(board: &Board, game_win: WINDOW) {
let (food_l, food_c): (i32, i32) = board.food_posyx(); 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) { 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(); let (bfl, bfc): (i32, i32) = board.food_posyx();
mvwaddstr(stdscr(), 0, 0, &format!("snake:head: {} {} ", shl, shc)); mvwaddstr(stdscr(), 0, 0, &format!("snake:head: {} {} ", shl, shc));
mvwaddstr(stdscr(), 1, 0, &format!("board:food: {} {} ", bfl, bfc)); 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); wmove(stdscr(), 2, 0);
for snake_cell in snake.iter() { for snake_cell in snake.iter() {
let (scl, scc): (i32, i32) = snake_cell.posyx(); waddstr(
waddstr(stdscr(), &format!("cell: {} {} ", scl, scc)); stdscr(),
&format!(
"<cell y:{} x:{}>",
snake_cell.posyx().0,
snake_cell.posyx().1
),
);
} }
mvwaddstr(
stdscr(),
3,
0,
&format!("snake_size {}", snake.iter().size_hint().0),
);
wrefresh(stdscr()); wrefresh(stdscr());
} }
+1 -1
View File
@@ -24,7 +24,7 @@ pub fn start() {
loop { loop {
frontend::draw_snake(&snake, game_win); // always draw snake before board because the snake will clear the game win 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::draw_board(&board, game_win);
frontend::_log(&snake, &board); // frontend::_log(&snake, &board);
if board.check_collision(&snake) { if board.check_collision(&snake) {
// Add stuff here to show the score and // Add stuff here to show the score and
// how You lose screen // how You lose screen
+3 -5
View File
@@ -5,11 +5,13 @@ mod menu;
// use game::{Cell, Snake}; // use game::{Cell, Snake};
// use ncurses::*; // use ncurses::*;
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() { fn main() {
// let (lines, cols): (i32, i32) = (0, 0); // let (lines, cols): (i32, i32) = (0, 0);
setlocale(LcCategory::all, "");
initscr(); initscr();
raw(); raw();
keypad(stdscr(), true); keypad(stdscr(), true);
@@ -17,10 +19,6 @@ fn main() {
noecho(); noecho();
let (mut mlines, mut mcols): (i32, i32) = (0, 0); let (mut mlines, mut mcols): (i32, i32) = (0, 0);
getmaxyx(stdscr(), &mut mlines, &mut mcols); 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) { if (mlines < 20) || (mcols < 35) {
refresh(); refresh();
endwin(); endwin();