ui rework, added scoreboard + timer
This commit is contained in:
parent
97839e250c
commit
7594ac19d6
@ -11,3 +11,9 @@ categories = ["games", "mathematics", "science", "algorithms"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
crossterm = "0.27.0"
|
crossterm = "0.27.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
codegen-units = 1
|
||||||
|
lto = true
|
||||||
|
panic = "abort"
|
||||||
|
strip = true
|
||||||
|
|||||||
79
README.md
79
README.md
@ -5,6 +5,25 @@ A basic tui sudoku game for your shell.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Preview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌────────┬────────┬────────┐ ┌───────┐
|
||||||
|
│ │ │ 8 9 │ │ Hard │ <- Difficulty
|
||||||
|
│ 4 9 7 │ │ 6 │ │ 36/81 │ <- Completion in number of cells
|
||||||
|
│ 2 │ 3 1 │ 7 │ ├───────┤
|
||||||
|
├────────┼────────┼────────┤ │ 01:22 │ <- Elapsed Time
|
||||||
|
│ 6 │ 9 7 │ 3 │ ├───────┤
|
||||||
|
│ 3 │ 5 2 │ │ │> Edit │ <- Active Mode
|
||||||
|
│ 7 2 │ 1 3 │ 5 4 │ │ Mark │
|
||||||
|
├────────┼────────┼────────┤ │ Go │
|
||||||
|
│ 2 1 │ 3 7 │ 9 5 │ ├───────┤
|
||||||
|
│ 5 │ 9 │ 3 4 │ │ [9] │ <- Preselected Number
|
||||||
|
│ │ 4 │ 6 1 │ │ 4 / 9 │ <- Completion of preselected Number
|
||||||
|
└────────┴────────┴────────┘ └───────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Controls
|
### Controls
|
||||||
|
|
||||||
The current control scheme adheres to vim-like keybindings and is modal.
|
The current control scheme adheres to vim-like keybindings and is modal.
|
||||||
@ -30,35 +49,12 @@ The current control scheme adheres to vim-like keybindings and is modal.
|
|||||||
- `q` to quit
|
- `q` to quit
|
||||||
|
|
||||||
|
|
||||||
### Preview
|
|
||||||
|
|
||||||
This is what the sudoku will be displayed like.
|
|
||||||
|
|
||||||
```
|
|
||||||
┌────────┬────────┬────────┐
|
|
||||||
│ 5 3 │ 7 │ │
|
|
||||||
│ 6 │ 1 9 5 │ │
|
|
||||||
│ 9 8 │ │ 6 │
|
|
||||||
├────────┼────────┼────────┤
|
|
||||||
│ 8 │ 6 │ 3 │
|
|
||||||
│ 4 │ 8 3 │ 1 │
|
|
||||||
│ 7 │ 2 │ 6 │
|
|
||||||
├────────┼────────┼────────┤
|
|
||||||
│ 6 │ │ 2 8 │
|
|
||||||
│ │ 4 1 9 │ 5 │
|
|
||||||
│ │ 8 │ 7 9 │
|
|
||||||
└────────┴────────┴────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Todo
|
### Todo
|
||||||
|
|
||||||
- [ ] Game logic
|
- [ ] Game logic
|
||||||
- [x] Validate Sudokus
|
- [x] Validate Sudokus
|
||||||
- [x] Generate Sudokus
|
- [x] Generate Sudokus
|
||||||
- [x] Difficulties to choose
|
- [x] Difficulties to choose
|
||||||
- [ ] Timer
|
|
||||||
- [ ] Scoreboard per difficulty
|
|
||||||
- [ ] Undo functionality
|
- [ ] Undo functionality
|
||||||
|
|
||||||
|
|
||||||
@ -76,31 +72,16 @@ This is what the sudoku will be displayed like.
|
|||||||
- [x] Markup Mode to mark where numbers could go
|
- [x] Markup Mode to mark where numbers could go
|
||||||
- [x] Go Mode to move to blocks 1-9
|
- [x] Go Mode to move to blocks 1-9
|
||||||
- [x] Toggle Number/Mark with Space
|
- [x] Toggle Number/Mark with Space
|
||||||
|
- [ ] Undo/Redo stack
|
||||||
- [ ] Colored UI
|
- [ ] Colored UI
|
||||||
- [ ] Hightlight selected numbers
|
- [ ] Hightlight preselected numbers
|
||||||
- [ ] Hightlight selected markups
|
- [ ] Hightlight preselected markups
|
||||||
|
- [x] Scoreboard
|
||||||
|
- [x] Live timer
|
||||||
|
- [x] Mode indicator
|
||||||
|
- [x] Difficulty indicator
|
||||||
|
- [x] Completion information
|
||||||
|
- [ ] Menu
|
||||||
|
- [ ] Difficulty selection
|
||||||
|
- [ ] Highscore section
|
||||||
- [ ] Color chooser
|
- [ ] Color chooser
|
||||||
- [ ] Live timer
|
|
||||||
- [ ] Scoreboard access
|
|
||||||
- [ ] Difficulty selection
|
|
||||||
|
|
||||||
|
|
||||||
The Final UI design should include a sidebar,
|
|
||||||
that will look something like the following:
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
┌────────┬────────┬────────┐ ┌───────┐
|
|
||||||
│ │ │ 8 9 │ │ Hard │ <- Difficulty
|
|
||||||
│ 4 9 7 │ │ 6 │ │ 36/81 │ <- Completion in number of cells
|
|
||||||
│ 2 │ 3 1 │ 7 │ ├───────┤
|
|
||||||
├────────┼────────┼────────┤ │ 01:22 │ <- Elapsed Time
|
|
||||||
│ 6 │ 9 7 │ 3 │ ├───────┤
|
|
||||||
│ 3 │ 5 2 │ │ │> Edit │ <- Active Mode
|
|
||||||
│ 7 2 │ 1 3 │ 5 4 │ │ Mark │
|
|
||||||
├────────┼────────┼────────┤ │ Go │
|
|
||||||
│ 2 1 │ 3 7 │ 9 5 │ ├───────┤
|
|
||||||
│ 5 │ 9 │ 3 4 │ │ [9] │ <- Preselected Number
|
|
||||||
│ │ 4 │ 6 1 │ │ 4 / 9 │ <- Completion of preselected Number
|
|
||||||
└────────┴────────┴────────┘ └───────┘
|
|
||||||
```
|
|
||||||
|
|||||||
26
src/main.rs
26
src/main.rs
@ -14,12 +14,12 @@ use ui::*;
|
|||||||
|
|
||||||
use std::{io, time::Duration};
|
use std::{io, time::Duration};
|
||||||
|
|
||||||
fn main() -> io::Result<()> {
|
fn main() {
|
||||||
let mut screen = Screen::init(io::stdout());
|
let mut screen = Screen::init(io::stdout());
|
||||||
let mut state = State::init(Difficulty::Mid);
|
let mut state = State::init(Difficulty::Mid);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if poll(Duration::from_millis(250))? {
|
if poll(Duration::from_millis(250)).unwrap_or(false) {
|
||||||
if let Ok(Key(k)) = read() {
|
if let Ok(Key(k)) = read() {
|
||||||
match k.code {
|
match k.code {
|
||||||
Char('h') => state.move_cursor(Dir::Left),
|
Char('h') => state.move_cursor(Dir::Left),
|
||||||
@ -49,8 +49,12 @@ fn main() -> io::Result<()> {
|
|||||||
state.toggle_current_cell();
|
state.toggle_current_cell();
|
||||||
state.enter_next_mode();
|
state.enter_next_mode();
|
||||||
if is_solution(&state.board) {
|
if is_solution(&state.board) {
|
||||||
screen.deinit()?;
|
screen.deinit();
|
||||||
println!("you win");
|
println!("+------------+");
|
||||||
|
println!("| You Win :) |");
|
||||||
|
println!("+------------+");
|
||||||
|
println!("Difficulty: {}", state.difficulty);
|
||||||
|
println!("Final Time: {}", state.get_timer_string());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,7 +81,7 @@ fn main() -> io::Result<()> {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Char('q') | Char('Q') => {
|
Char('q') | Char('Q') => {
|
||||||
screen.deinit()?;
|
screen.deinit();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,12 +91,14 @@ fn main() -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
screen.update_dimensions()?;
|
screen.update_dimensions()
|
||||||
screen.render(&state);
|
.unwrap_or_else(|_| std::process::exit(1));
|
||||||
screen.draw(state.cur_row, state.cur_col)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
screen.render(&state);
|
||||||
|
|
||||||
|
screen.draw(state.cur_row, state.cur_col)
|
||||||
|
.unwrap_or_else(|_| std::process::exit(1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|||||||
81
src/state.rs
81
src/state.rs
@ -1,6 +1,8 @@
|
|||||||
use crate::sudoku::*;
|
use crate::sudoku::*;
|
||||||
use crate::Dir::*;
|
use crate::Dir::*;
|
||||||
|
|
||||||
|
use std::time;
|
||||||
|
|
||||||
pub enum Dir {
|
pub enum Dir {
|
||||||
Up,
|
Up,
|
||||||
Down,
|
Down,
|
||||||
@ -22,11 +24,13 @@ pub enum Mode {
|
|||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub board: Board,
|
pub board: Board,
|
||||||
|
pub difficulty: Difficulty,
|
||||||
pub modifiable: [[bool; 9]; 9],
|
pub modifiable: [[bool; 9]; 9],
|
||||||
pub markups: [[[bool; 9]; 9]; 9],
|
pub markups: [[[bool; 9]; 9]; 9],
|
||||||
|
pub start_time: time::Instant,
|
||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
pub next_mode: Mode,
|
pub next_mode: Mode,
|
||||||
pub cur_num: u8,
|
pub preselection: u8,
|
||||||
pub cur_row: usize,
|
pub cur_row: usize,
|
||||||
pub cur_col: usize,
|
pub cur_col: usize,
|
||||||
}
|
}
|
||||||
@ -38,16 +42,22 @@ impl State {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
board,
|
board,
|
||||||
|
difficulty,
|
||||||
modifiable,
|
modifiable,
|
||||||
markups: [[[false; 9]; 9]; 9],
|
markups: [[[false; 9]; 9]; 9],
|
||||||
|
start_time: time::Instant::now(),
|
||||||
mode: Mode::default(),
|
mode: Mode::default(),
|
||||||
next_mode: Mode::default(),
|
next_mode: Mode::default(),
|
||||||
cur_num: 1,
|
preselection: 1,
|
||||||
cur_row: 4,
|
cur_row: 4,
|
||||||
cur_col: 4,
|
cur_col: 4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_time(&self) -> time::Duration {
|
||||||
|
time::Instant::now() - self.start_time
|
||||||
|
}
|
||||||
|
|
||||||
fn get_modifiables(board: Board) -> [[bool; 9]; 9] {
|
fn get_modifiables(board: Board) -> [[bool; 9]; 9] {
|
||||||
let mut modifiable = [[false; 9]; 9];
|
let mut modifiable = [[false; 9]; 9];
|
||||||
for (board_row, modifiable_row) in board.iter().zip(modifiable.iter_mut()) {
|
for (board_row, modifiable_row) in board.iter().zip(modifiable.iter_mut()) {
|
||||||
@ -87,15 +97,15 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn preselect_num(&mut self, num: u8) {
|
pub fn preselect_num(&mut self, num: u8) {
|
||||||
self.cur_num = num;
|
self.preselection = num;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_current_cell(&mut self) {
|
pub fn toggle_current_cell(&mut self) {
|
||||||
if self.current_cell_is_modifiable() {
|
if self.current_cell_is_modifiable() {
|
||||||
*self.current_cell() = if *self.current_cell() == self.cur_num {
|
*self.current_cell() = if *self.current_cell() == self.preselection {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
self.cur_num
|
self.preselection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,10 +149,67 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_current_mark(&mut self) {
|
pub fn toggle_current_mark(&mut self) {
|
||||||
self.markups[self.cur_row][self.cur_col][self.cur_num as usize] ^= true;
|
self.markups[self.cur_row][self.cur_col][self.preselection as usize] ^= true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_current_mark(&mut self) {
|
pub fn delete_current_mark(&mut self) {
|
||||||
self.markups[self.cur_row][self.cur_col][self.cur_num as usize] = false;
|
self.markups[self.cur_row][self.cur_col][self.preselection as usize] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_completion_chars(&self) -> [char; 2] {
|
||||||
|
let mut count = 0;
|
||||||
|
for row in self.board {
|
||||||
|
for cell in row {
|
||||||
|
if cell != 0 {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let to_char = |x| (x + b'0') as char;
|
||||||
|
[to_char(count / 10), to_char(count % 10)]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_preselection_completion_char(&self) -> char {
|
||||||
|
let count = self
|
||||||
|
.board
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.filter(|&cell| cell == self.preselection)
|
||||||
|
.count();
|
||||||
|
(count.min(9) as u8 + b'0') as char
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_difficulty_chars(&self) -> [char; 5] {
|
||||||
|
match self.difficulty {
|
||||||
|
Difficulty::Easy => [' ', 'E', 'a', 's', 'y'],
|
||||||
|
Difficulty::Mid => [' ', 'M', 'i', 'd', ' '],
|
||||||
|
Difficulty::Hard => [' ', 'H', 'a', 'r', 'd'],
|
||||||
|
Difficulty::Expert => ['E', 'x', 'p', 'r', 't'],
|
||||||
|
Difficulty::Custom(x) => [
|
||||||
|
'C',
|
||||||
|
'(',
|
||||||
|
(x as u8 / 10 + b'0') as char,
|
||||||
|
(x as u8 % 10 + b'0') as char,
|
||||||
|
')',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_timer_chars(&self) -> [char; 5] {
|
||||||
|
let mut secs = self.get_time().as_secs();
|
||||||
|
let mut chars = [':'; 5];
|
||||||
|
let to_char = |x: u64| (x as u8 + b'0') as char;
|
||||||
|
chars[0] = to_char(secs / 600);
|
||||||
|
secs %= 600;
|
||||||
|
chars[1] = to_char(secs / 60);
|
||||||
|
secs %= 60;
|
||||||
|
chars[3] = to_char(secs / 10);
|
||||||
|
secs %= 10;
|
||||||
|
chars[4] = to_char(secs);
|
||||||
|
chars
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_timer_string(&self) -> String {
|
||||||
|
self.get_timer_chars().iter().collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,9 @@ use std::fmt;
|
|||||||
/// categories of difficulty, indicating how many
|
/// categories of difficulty, indicating how many
|
||||||
/// empty spaces will be on a sudoku board.
|
/// empty spaces will be on a sudoku board.
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
#[derive(Default, Copy, Clone, PartialEq)]
|
||||||
pub enum Difficulty {
|
pub enum Difficulty {
|
||||||
|
#[default]
|
||||||
Easy,
|
Easy,
|
||||||
Mid,
|
Mid,
|
||||||
Hard,
|
Hard,
|
||||||
@ -36,10 +38,10 @@ impl fmt::Display for Difficulty {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Easy => write!(f, "Easy"),
|
Easy => write!(f, "Easy"),
|
||||||
Mid => write!(f, " Mid"),
|
Mid => write!(f, "Mid"),
|
||||||
Hard => write!(f, "Hard"),
|
Hard => write!(f, "Hard"),
|
||||||
Expert => write!(f, "Exprt"),
|
Expert => write!(f, "Expert"),
|
||||||
Custom(x) => write!(f, "C({})", x),
|
Custom(x) => write!(f, "Custom ({:02})", x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
229
src/ui.rs
229
src/ui.rs
@ -2,7 +2,7 @@ use crate::state::*;
|
|||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
cursor::{MoveTo, SetCursorStyle},
|
cursor::{MoveTo, MoveToColumn, SetCursorStyle},
|
||||||
execute, queue,
|
execute, queue,
|
||||||
terminal::{
|
terminal::{
|
||||||
disable_raw_mode, enable_raw_mode, size, Clear, ClearType::All, EnterAlternateScreen,
|
disable_raw_mode, enable_raw_mode, size, Clear, ClearType::All, EnterAlternateScreen,
|
||||||
@ -14,7 +14,7 @@ pub struct Screen<T>
|
|||||||
where
|
where
|
||||||
T: io::Write,
|
T: io::Write,
|
||||||
{
|
{
|
||||||
pub data: Vec<String>,
|
pub data: Vec<Vec<char>>,
|
||||||
pub ostream: T,
|
pub ostream: T,
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
@ -26,12 +26,15 @@ where
|
|||||||
{
|
{
|
||||||
pub fn init(ostream: T) -> Self {
|
pub fn init(ostream: T) -> Self {
|
||||||
let (width, height) = size().unwrap();
|
let (width, height) = size().unwrap();
|
||||||
|
let (width, height) = (width as usize, height as usize);
|
||||||
|
|
||||||
|
let data = RENDER_TEMPLATE.iter().map(|s| s.to_vec()).collect();
|
||||||
|
|
||||||
let mut screen = Screen {
|
let mut screen = Screen {
|
||||||
data: vec![String::with_capacity(width as usize); height as usize],
|
data,
|
||||||
ostream,
|
ostream,
|
||||||
width: width as usize,
|
width,
|
||||||
height: height as usize,
|
height,
|
||||||
};
|
};
|
||||||
|
|
||||||
enable_raw_mode().expect("[-]: Error: ui::init: Failed to enable raw mode.");
|
enable_raw_mode().expect("[-]: Error: ui::init: Failed to enable raw mode.");
|
||||||
@ -40,86 +43,126 @@ where
|
|||||||
screen
|
screen
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(&mut self) -> io::Result<()> {
|
pub fn deinit(&mut self) {
|
||||||
disable_raw_mode()?;
|
disable_raw_mode().unwrap_or(());
|
||||||
execute!(self.ostream, LeaveAlternateScreen)?;
|
execute!(self.ostream, LeaveAlternateScreen).unwrap_or(());
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_dimensions(&mut self) -> io::Result<()> {
|
pub fn update_dimensions(&mut self) -> io::Result<()> {
|
||||||
let (width, height) = size()?;
|
let (width, height) = size()?;
|
||||||
self.width = width as usize;
|
self.width = width as usize;
|
||||||
self.height = height as usize;
|
self.height = height as usize;
|
||||||
|
|
||||||
|
if height < 14 || width < 54 {
|
||||||
|
self.clear()?;
|
||||||
|
self.deinit();
|
||||||
|
eprintln!("[-]: Error: ui::update_dimensions: Terminal size too small to display UI.");
|
||||||
|
return Err(io::Error::from(io::ErrorKind::Other));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self) -> io::Result<()> {
|
pub fn clear(&mut self) -> io::Result<()> {
|
||||||
queue!(self.ostream, Clear(All))
|
queue!(self.ostream, Clear(All))?;
|
||||||
|
queue!(self.ostream, MoveToColumn(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self, row: usize, col: usize) -> io::Result<()> {
|
pub fn draw(&mut self, row: usize, col: usize) -> io::Result<()> {
|
||||||
self.draw_screen()?;
|
self.draw_screen()?;
|
||||||
self.place_cursor(row, col)?;
|
self.draw_cursor(row, col)?;
|
||||||
self.ostream.flush()?;
|
self.ostream.flush()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_screen(&mut self) -> io::Result<()> {
|
pub fn draw_screen(&mut self) -> io::Result<()> {
|
||||||
self.clear()?;
|
self.clear()?;
|
||||||
let s: String = self.data.join("\r\n");
|
let lft_pad = self.width / 2 - 14;
|
||||||
write!(self.ostream, "{}", s)?;
|
let bot_pad = self.height / 2 - 7;
|
||||||
|
|
||||||
|
let mut display = String::with_capacity((43 + lft_pad) * 13 + bot_pad * 2);
|
||||||
|
|
||||||
|
display.extend(self.data.iter().flat_map(|line| {
|
||||||
|
std::iter::repeat(' ')
|
||||||
|
.take(lft_pad)
|
||||||
|
.chain(line.to_owned())
|
||||||
|
.chain(['\r', '\n'])
|
||||||
|
}));
|
||||||
|
|
||||||
|
display.push_str(&"\r\n".repeat(bot_pad));
|
||||||
|
|
||||||
|
write!(self.ostream, "{}", display)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&mut self, state: &State) {
|
pub fn render(&mut self, state: &State) {
|
||||||
let mut lines = Vec::new();
|
self.render_board();
|
||||||
lines.push("┌────────┬────────┬────────┐".to_string());
|
self.render_board_cells(state);
|
||||||
|
self.render_scoreboard_elements(state);
|
||||||
for (row, row_slice) in state.board.iter().enumerate() {
|
|
||||||
match row {
|
|
||||||
3 | 6 => lines.push("├────────┼────────┼────────┤".to_string()),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
let mut line = String::new();
|
|
||||||
for (col, cur_cell) in row_slice.iter().enumerate() {
|
|
||||||
match col {
|
|
||||||
0 => line.push_str("│ "),
|
|
||||||
3 | 6 => line.push_str(" │ "),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
line.push(match cur_cell {
|
|
||||||
0 => ' ',
|
|
||||||
n => (b'0' + n) as char,
|
|
||||||
});
|
|
||||||
line.push(' ');
|
|
||||||
}
|
|
||||||
line.push_str(" │");
|
|
||||||
lines.push(line);
|
|
||||||
}
|
|
||||||
lines.push("└────────┴────────┴────────┘".to_string());
|
|
||||||
|
|
||||||
let pad_hori = self.width / 2 + lines[0].chars().count() / 2;
|
|
||||||
let pad_vert = self.height - lines.len();
|
|
||||||
let pad_top = pad_vert / 2;
|
|
||||||
let pad_bot = pad_vert - pad_top;
|
|
||||||
|
|
||||||
let mut new_data = Vec::new();
|
|
||||||
|
|
||||||
for _ in 0..pad_top {
|
|
||||||
new_data.push(String::new());
|
|
||||||
}
|
|
||||||
for line in lines {
|
|
||||||
let padded = format!("{: >width$}", line, width = pad_hori);
|
|
||||||
new_data.push(padded);
|
|
||||||
}
|
|
||||||
for _ in 0..pad_bot {
|
|
||||||
new_data.push(String::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.data = new_data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn place_cursor(&mut self, row: usize, col: usize) -> io::Result<()> {
|
fn render_board(&mut self) {
|
||||||
|
self.data = RENDER_TEMPLATE.iter().map(|s| s.to_vec()).collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_board_cells(&mut self, state: &State) {
|
||||||
|
for row in 0..9 {
|
||||||
|
for col in 0..9 {
|
||||||
|
let i = row + 1 + row / 3;
|
||||||
|
let j = col + 2 + col + col / 3 * 3;
|
||||||
|
|
||||||
|
self.data[i][j] = match state.board[row][col] {
|
||||||
|
0 => ' ',
|
||||||
|
x => (x + b'0') as char,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_scoreboard_elements(&mut self, state: &State) {
|
||||||
|
// char indeces of scoreboard elements
|
||||||
|
// -----
|
||||||
|
//
|
||||||
|
// difficulty [1][34] - [1][38]
|
||||||
|
// completion [2][34] - [1][35]
|
||||||
|
//
|
||||||
|
// timer [4][34] - [3][38]
|
||||||
|
//
|
||||||
|
// > edit: [6][33]
|
||||||
|
// > markup: [7][33]
|
||||||
|
// > go: [8][33]
|
||||||
|
//
|
||||||
|
// preselection: [10][36]
|
||||||
|
// preselection completion: [11][34]
|
||||||
|
|
||||||
|
for (i, c) in (34..39).zip(state.get_difficulty_chars()) {
|
||||||
|
self.data[1][i] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, c) in (34..36).zip(state.get_completion_chars()) {
|
||||||
|
self.data[2][i] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, c) in (34..39).zip(state.get_timer_chars()) {
|
||||||
|
self.data[4][i] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 6..9 {
|
||||||
|
self.data[i][33] = ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
self.data[match state.mode {
|
||||||
|
Mode::Edit => 6,
|
||||||
|
Mode::Markup => 7,
|
||||||
|
Mode::Go => 8,
|
||||||
|
}][33] = '>';
|
||||||
|
|
||||||
|
self.data[10][36] = (state.preselection + b'0') as char;
|
||||||
|
|
||||||
|
self.data[11][34] = state.get_preselection_completion_char();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_cursor(&mut self, row: usize, col: usize) -> io::Result<()> {
|
||||||
let (x, y) = (
|
let (x, y) = (
|
||||||
(self.width / 2 - 14) as u16,
|
(self.width / 2 - 14) as u16,
|
||||||
(self.height / 2 - 7 + (self.height & 1)) as u16,
|
(self.height / 2 - 7 + (self.height & 1)) as u16,
|
||||||
@ -139,3 +182,71 @@ where
|
|||||||
queue!(self.ostream, MoveTo(x, y), SetCursorStyle::SteadyBlock)
|
queue!(self.ostream, MoveTo(x, y), SetCursorStyle::SteadyBlock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RENDER_TEMPLATE: [[char; 41]; 13] = [
|
||||||
|
[
|
||||||
|
'┌', '─', '─', '─', '─', '─', '─', '─', '─', '┬', '─', '─', '─', '─', '─', '─', '─', '─',
|
||||||
|
'┬', '─', '─', '─', '─', '─', '─', '─', '─', '┐', ' ', ' ', ' ', ' ', '┌', '─', '─', '─',
|
||||||
|
'─', '─', '─', '─', '┐',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
|
||||||
|
'│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ',
|
||||||
|
' ', ' ', ' ', ' ', '│',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
|
||||||
|
'│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ',
|
||||||
|
'/', '8', '1', ' ', '│',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
|
||||||
|
'│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ', ' ', '├', '─', '─', '─',
|
||||||
|
'─', '─', '─', '─', '┤',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'├', '─', '─', '─', '─', '─', '─', '─', '─', '┼', '─', '─', '─', '─', '─', '─', '─', '─',
|
||||||
|
'┼', '─', '─', '─', '─', '─', '─', '─', '─', '┤', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ',
|
||||||
|
' ', ' ', ' ', ' ', '│',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
|
||||||
|
'│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ', ' ', '├', '─', '─', '─',
|
||||||
|
'─', '─', '─', '─', '┤',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
|
||||||
|
'│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ', ' ', '│', ' ', ' ', 'E',
|
||||||
|
'd', 'i', 't', ' ', '│',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
|
||||||
|
'│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ', ' ', '│', ' ', ' ', 'M',
|
||||||
|
'a', 'r', 'k', ' ', '│',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'├', '─', '─', '─', '─', '─', '─', '─', '─', '┼', '─', '─', '─', '─', '─', '─', '─', '─',
|
||||||
|
'┼', '─', '─', '─', '─', '─', '─', '─', '─', '┤', ' ', ' ', ' ', ' ', '│', ' ', ' ', 'G',
|
||||||
|
'o', ' ', ' ', ' ', '│',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
|
||||||
|
'│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ', ' ', '├', '─', '─', '─',
|
||||||
|
'─', '─', '─', '─', '┤',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
|
||||||
|
'│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ', ' ', '│', ' ', ' ', '[',
|
||||||
|
' ', ']', ' ', ' ', '│',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
|
||||||
|
'│', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ', ' ', '│', ' ', ' ', ' ',
|
||||||
|
'/', ' ', '9', ' ', '│',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'└', '─', '─', '─', '─', '─', '─', '─', '─', '┴', '─', '─', '─', '─', '─', '─', '─', '─',
|
||||||
|
'┴', '─', '─', '─', '─', '─', '─', '─', '─', '┘', ' ', ' ', ' ', ' ', '└', '─', '─', '─',
|
||||||
|
'─', '─', '─', '─', '┘',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user