major control scheme rework
This commit is contained in:
parent
23e83cc9a5
commit
97839e250c
52
README.md
52
README.md
@ -7,15 +7,27 @@ A basic tui sudoku game for your shell.
|
||||
|
||||
### Controls
|
||||
|
||||
The current control scheme adheres to vim-like keybindings:
|
||||
The current control scheme adheres to vim-like keybindings and is modal.
|
||||
|
||||
- `h, j, k, l` to move `left, down, up, right`
|
||||
- `H, J, K, L` to move 3 spaces at once
|
||||
- `x` to delete a number
|
||||
- `1-9` to place a number
|
||||
- `q` to quit
|
||||
|
||||
This shall be reworked. 😼
|
||||
- `1-9` to preselect a number
|
||||
|
||||
- Modes:
|
||||
- `a` to enter Markup mode
|
||||
- `i` to enter Edit mode
|
||||
- `g` to enter Go mode
|
||||
- `1-9` to move to block
|
||||
- you then return to the previous mode
|
||||
- `A` and `I` to enter Edit/Markup mode "once"
|
||||
- do a single edit/mark
|
||||
- you then return to the previous mode
|
||||
- `<esc>` to return to Edit mode
|
||||
|
||||
- `<space>` to place/unplace preselected number/mark
|
||||
- `x` to delete a number/mark
|
||||
- `q` to quit
|
||||
|
||||
|
||||
### Preview
|
||||
@ -58,9 +70,12 @@ This is what the sudoku will be displayed like.
|
||||
|
||||
|
||||
- [ ] Final UI
|
||||
- [ ] Final controls
|
||||
- [ ] Preselect numbers
|
||||
- [ ] Cell markups (perhaps with unicode block thingies?)
|
||||
- [x] Final controls
|
||||
- [x] Preselect numbers
|
||||
- [x] Edit Mode to (re)place numbers
|
||||
- [x] Markup Mode to mark where numbers could go
|
||||
- [x] Go Mode to move to blocks 1-9
|
||||
- [x] Toggle Number/Mark with Space
|
||||
- [ ] Colored UI
|
||||
- [ ] Hightlight selected numbers
|
||||
- [ ] Hightlight selected markups
|
||||
@ -68,3 +83,24 @@ This is what the sudoku will be displayed like.
|
||||
- [ ] 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
|
||||
└────────┴────────┴────────┘ └───────┘
|
||||
```
|
||||
|
||||
61
src/main.rs
61
src/main.rs
@ -16,7 +16,7 @@ use std::{io, time::Duration};
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let mut screen = Screen::init(io::stdout());
|
||||
let mut state = State::init(Difficulty::Medium);
|
||||
let mut state = State::init(Difficulty::Mid);
|
||||
|
||||
loop {
|
||||
if poll(Duration::from_millis(250))? {
|
||||
@ -26,36 +26,69 @@ fn main() -> io::Result<()> {
|
||||
Char('j') => state.move_cursor(Dir::Down),
|
||||
Char('k') => state.move_cursor(Dir::Up),
|
||||
Char('l') => state.move_cursor(Dir::Right),
|
||||
|
||||
Char('H') => state.move_cursor(Dir::FarLeft),
|
||||
Char('J') => state.move_cursor(Dir::FarDown),
|
||||
Char('K') => state.move_cursor(Dir::FarUp),
|
||||
Char('L') => state.move_cursor(Dir::FarRight),
|
||||
|
||||
Char('i') => state.enter_mode(Mode::Edit),
|
||||
Char('I') => state.enter_mode_once(Mode::Edit),
|
||||
|
||||
Char('a') => state.enter_mode(Mode::Markup),
|
||||
Char('A') => state.enter_mode_once(Mode::Markup),
|
||||
|
||||
Char('g') | Char('G') => state.enter_mode_once(Mode::Go),
|
||||
|
||||
Char(' ') => match state.mode {
|
||||
Mode::Markup => {
|
||||
state.toggle_current_mark();
|
||||
state.enter_next_mode();
|
||||
}
|
||||
Mode::Edit => {
|
||||
state.toggle_current_cell();
|
||||
state.enter_next_mode();
|
||||
if is_solution(&state.board) {
|
||||
screen.deinit()?;
|
||||
println!("you win");
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
|
||||
Char('x') => {
|
||||
if state.current_cell_modifiable() {
|
||||
*state.current_cell() = 0;
|
||||
if state.current_cell_is_modifiable() {
|
||||
match state.mode {
|
||||
Mode::Go => {}
|
||||
Mode::Edit => state.delete_current_cell(),
|
||||
Mode::Markup => state.delete_current_mark(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Char(num) if ('1'..='9').contains(&num) => {
|
||||
if state.current_cell_modifiable() {
|
||||
*state.current_cell() = num as u8 - b'0';
|
||||
|
||||
Char(num) if ('1'..='9').contains(&num) => match state.mode {
|
||||
Mode::Go => {
|
||||
let idx = (num as u8 - b'1') as usize;
|
||||
state.move_cursor_to(1 + idx / 3 * 3, 1 + idx % 3 * 3);
|
||||
state.enter_next_mode();
|
||||
}
|
||||
if is_solution(&state.board) {
|
||||
screen.deinit()?;
|
||||
println!("you win");
|
||||
break;
|
||||
}
|
||||
}
|
||||
Char('q') => {
|
||||
_ => state.preselect_num(num as u8 - b'0'),
|
||||
},
|
||||
|
||||
Char('q') | Char('Q') => {
|
||||
screen.deinit()?;
|
||||
break;
|
||||
}
|
||||
|
||||
Esc => state.enter_mode(Mode::Edit),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
screen.update_dimensions()?;
|
||||
screen.render(state.board);
|
||||
screen.render(&state);
|
||||
screen.draw(state.cur_row, state.cur_col)?;
|
||||
}
|
||||
|
||||
|
||||
78
src/state.rs
78
src/state.rs
@ -12,9 +12,21 @@ pub enum Dir {
|
||||
FarRight,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone, Copy)]
|
||||
pub enum Mode {
|
||||
#[default]
|
||||
Edit,
|
||||
Markup,
|
||||
Go,
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
pub board: Board,
|
||||
pub modifiable: [[bool; 9]; 9],
|
||||
pub markups: [[[bool; 9]; 9]; 9],
|
||||
pub mode: Mode,
|
||||
pub next_mode: Mode,
|
||||
pub cur_num: u8,
|
||||
pub cur_row: usize,
|
||||
pub cur_col: usize,
|
||||
}
|
||||
@ -27,6 +39,10 @@ impl State {
|
||||
Self {
|
||||
board,
|
||||
modifiable,
|
||||
markups: [[[false; 9]; 9]; 9],
|
||||
mode: Mode::default(),
|
||||
next_mode: Mode::default(),
|
||||
cur_num: 1,
|
||||
cur_row: 4,
|
||||
cur_col: 4,
|
||||
}
|
||||
@ -44,7 +60,7 @@ impl State {
|
||||
modifiable
|
||||
}
|
||||
|
||||
pub fn current_cell_modifiable(&self) -> bool {
|
||||
pub fn current_cell_is_modifiable(&self) -> bool {
|
||||
self.modifiable[self.cur_row][self.cur_col]
|
||||
}
|
||||
|
||||
@ -69,4 +85,64 @@ impl State {
|
||||
_ => self.cur_row,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn preselect_num(&mut self, num: u8) {
|
||||
self.cur_num = num;
|
||||
}
|
||||
|
||||
pub fn toggle_current_cell(&mut self) {
|
||||
if self.current_cell_is_modifiable() {
|
||||
*self.current_cell() = if *self.current_cell() == self.cur_num {
|
||||
0
|
||||
} else {
|
||||
self.cur_num
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_current_cell(&mut self) {
|
||||
if self.current_cell_is_modifiable() {
|
||||
*self.current_cell() = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enter_mode(&mut self, mode: Mode) {
|
||||
match mode {
|
||||
Mode::Go => {
|
||||
self.enter_mode_once(mode);
|
||||
return;
|
||||
}
|
||||
Mode::Edit => self.mode = Mode::Edit,
|
||||
Mode::Markup => self.mode = Mode::Markup,
|
||||
}
|
||||
self.next_mode = self.mode;
|
||||
}
|
||||
|
||||
pub fn enter_mode_once(&mut self, mode: Mode) {
|
||||
if self.mode != mode {
|
||||
self.next_mode = self.mode;
|
||||
self.mode = mode;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enter_next_mode(&mut self) {
|
||||
self.mode = self.next_mode;
|
||||
}
|
||||
|
||||
pub fn move_cursor_to(&mut self, row: usize, col: usize) {
|
||||
assert!(
|
||||
row < 9 && col < 9,
|
||||
"[-] Error: State::go_to: Can't move to row/column out of bounds."
|
||||
);
|
||||
self.cur_row = row;
|
||||
self.cur_col = col;
|
||||
}
|
||||
|
||||
pub fn toggle_current_mark(&mut self) {
|
||||
self.markups[self.cur_row][self.cur_col][self.cur_num as usize] ^= true;
|
||||
}
|
||||
|
||||
pub fn delete_current_mark(&mut self) {
|
||||
self.markups[self.cur_row][self.cur_col][self.cur_num as usize] = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,14 +5,16 @@ use crate::generator::Difficulty::*;
|
||||
use crate::rand::{seq::SliceRandom, thread_rng};
|
||||
use crate::sudoku::{Board, New};
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// categories of difficulty, indicating how many
|
||||
/// empty spaces will be on a sudoku board.
|
||||
#[allow(unused)]
|
||||
pub enum Difficulty {
|
||||
Easy,
|
||||
Medium,
|
||||
Mid,
|
||||
Hard,
|
||||
Extreme,
|
||||
Expert,
|
||||
Custom(usize),
|
||||
}
|
||||
|
||||
@ -22,14 +24,26 @@ impl Difficulty {
|
||||
pub fn removal_count(&self) -> usize {
|
||||
match self {
|
||||
Easy => 35,
|
||||
Medium => 45,
|
||||
Mid => 45,
|
||||
Hard => 52,
|
||||
Extreme => 62,
|
||||
Expert => 62,
|
||||
Custom(x) => *x,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Difficulty {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Easy => write!(f, "Easy"),
|
||||
Mid => write!(f, " Mid"),
|
||||
Hard => write!(f, "Hard"),
|
||||
Expert => write!(f, "Exprt"),
|
||||
Custom(x) => write!(f, "C({})", x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// generate a random, unsolved sudoku board with a given `Difficulty`.
|
||||
pub fn generate_sudoku(difficulty: Difficulty) -> Board {
|
||||
let mut board = Board::new();
|
||||
|
||||
13
src/ui.rs
13
src/ui.rs
@ -1,3 +1,4 @@
|
||||
use crate::state::*;
|
||||
use std::io;
|
||||
|
||||
use crossterm::{
|
||||
@ -70,15 +71,13 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn render(&mut self, board: [[u8; 9]; 9]) {
|
||||
pub fn render(&mut self, state: &State) {
|
||||
let mut lines = Vec::new();
|
||||
lines.push("┌────────┬────────┬────────┐".to_string().chars().collect());
|
||||
lines.push("┌────────┬────────┬────────┐".to_string());
|
||||
|
||||
for (row, row_slice) in board.iter().enumerate() {
|
||||
for (row, row_slice) in state.board.iter().enumerate() {
|
||||
match row {
|
||||
3 | 6 => {
|
||||
lines.push("├────────┼────────┼────────┤".to_string().chars().collect());
|
||||
}
|
||||
3 | 6 => lines.push("├────────┼────────┼────────┤".to_string()),
|
||||
_ => {}
|
||||
}
|
||||
let mut line = String::new();
|
||||
@ -97,7 +96,7 @@ where
|
||||
line.push_str(" │");
|
||||
lines.push(line);
|
||||
}
|
||||
lines.push("└────────┴────────┴────────┘".to_string().chars().collect());
|
||||
lines.push("└────────┴────────┴────────┘".to_string());
|
||||
|
||||
let pad_hori = self.width / 2 + lines[0].chars().count() / 2;
|
||||
let pad_vert = self.height - lines.len();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user