keybinds
This commit is contained in:
parent
2f5809c5cd
commit
2f35ed64c5
|
@ -8,5 +8,8 @@ edition = "2021"
|
|||
[dependencies]
|
||||
pancurses = "0.17.0"
|
||||
rand = "0.8.5"
|
||||
serde = "1.0.136"
|
||||
serde_derive = "1.0.136"
|
||||
toml = "0.5.8"
|
||||
|
||||
[features]
|
||||
|
|
12
README.md
12
README.md
|
@ -1,2 +1,14 @@
|
|||
# tetris
|
||||
|
||||
Keybinds can be edited in `~/.config/tetris/binds.toml`.
|
||||
|
||||
Default bindings:
|
||||
|
||||
- Clockwise rotation: 'x' or up arrow
|
||||
- Counterclockwise rotation: 'z'
|
||||
- Hard drop: spacebar
|
||||
- Soft drop: down arrow
|
||||
- Hold: 'c'
|
||||
- Move left: left arrow
|
||||
- Move right: right arrow
|
||||
- Quit: 'q'
|
155
src/main.rs
155
src/main.rs
|
@ -1,7 +1,9 @@
|
|||
use std::{collections::VecDeque, time::{Instant, Duration}, thread};
|
||||
use std::{collections::VecDeque, time::{Instant, Duration}, thread, fs::{self, File, OpenOptions}, env, path::PathBuf, io::{Read, self, ErrorKind, Write}, process};
|
||||
use data::{TetriminoData, WallkickTable, WALLKICK_TABLE_1, WALLKICK_TABLE_2, Rotation};
|
||||
use pancurses::{initscr, noecho, Input, endwin, start_color, init_color, use_default_colors, init_pair, COLOR_RED, COLOR_CYAN, COLOR_YELLOW, COLOR_MAGENTA, COLOR_GREEN, COLOR_BLUE, Window, COLOR_WHITE, curs_set, cbreak};
|
||||
use rand::prelude::*;
|
||||
use serde::de::{Visitor, self};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
mod data;
|
||||
|
||||
|
@ -235,7 +237,142 @@ fn lock_tetrimino(tetrimino: Tetrimino, board: &mut [[Option::<Color>; 10]; 40],
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct Binds {
|
||||
cw_rot: Vec<Bind>,
|
||||
ccw_rot: Vec<Bind>,
|
||||
hard_drop: Vec<Bind>,
|
||||
soft_drop: Vec<Bind>,
|
||||
hold: Vec<Bind>,
|
||||
// pause: Vec<Bind>,
|
||||
left: Vec<Bind>,
|
||||
right: Vec<Bind>,
|
||||
quit: Vec<Bind>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Bind {
|
||||
Char(char),
|
||||
UpArrow,
|
||||
DownArrow,
|
||||
LeftArrow,
|
||||
RightArrow,
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for Bind {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de> {
|
||||
deserializer.deserialize_str(BindVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct BindVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for BindVisitor {
|
||||
type Value = Bind;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
// doesnt appear this is ever called, still implementing it
|
||||
write!(formatter, r#"expecting a char or one of "up", "down", "left", "right""#)
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
if v.chars().count() == 1 {
|
||||
Ok(Bind::Char(v.chars().next().unwrap()))
|
||||
} else {
|
||||
match v {
|
||||
"up" => Ok(Bind::UpArrow),
|
||||
"down" => Ok(Bind::DownArrow),
|
||||
"left" => Ok(Bind::LeftArrow),
|
||||
"right" => Ok(Bind::RightArrow),
|
||||
_ => Err(de::Error::unknown_variant(v, &["<char>", "up", "down", "left", "right"]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for Bind {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer {
|
||||
let s;
|
||||
serializer.serialize_str(match self {
|
||||
Bind::Char(c) => {
|
||||
s = c.to_string();
|
||||
&s
|
||||
},
|
||||
Bind::UpArrow => "up",
|
||||
Bind::DownArrow => "down",
|
||||
Bind::LeftArrow => "left",
|
||||
Bind::RightArrow => "right",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Binds {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
cw_rot: vec![Bind::Char('x'), Bind::UpArrow],
|
||||
ccw_rot: vec![Bind::Char('z')],
|
||||
hard_drop: vec![Bind::Char(' ')],
|
||||
soft_drop: vec![Bind::DownArrow],
|
||||
hold: vec![Bind::Char('c')],
|
||||
// pause: vec![Bind::Char('`')],
|
||||
left: vec![Bind::LeftArrow],
|
||||
right: vec![Bind::RightArrow],
|
||||
quit: vec![Bind::Char('q')]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Input> for Bind {
|
||||
fn eq(&self, other: &Input) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Char(l), Input::Character(f)) => l == f,
|
||||
(Self::UpArrow, Input::KeyUp) => true,
|
||||
(Self::DownArrow, Input::KeyDown) => true,
|
||||
(Self::LeftArrow, Input::KeyLeft) => true,
|
||||
(Self::RightArrow, Input::KeyRight) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_binds() -> io::Result<Binds> {
|
||||
let home = env::var("HOME");
|
||||
if let Ok(home) = home {
|
||||
let mut path = PathBuf::from(home);
|
||||
path.push(".config");
|
||||
path.push("tetris");
|
||||
fs::create_dir_all(&path)?;
|
||||
path.push("binds.toml");
|
||||
let mut file = File::open(&path).or_else(|e| {
|
||||
if e.kind() == ErrorKind::NotFound {
|
||||
let mut file = File::create(&path)?;
|
||||
file.write_all(toml::to_string_pretty(&Binds::default()).unwrap().as_bytes())?;
|
||||
File::open(&path)
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
})?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
Ok(toml::from_str(&contents).unwrap_or_else(|e| {
|
||||
eprintln!("Error parsing `{}`:\n{}", path.to_str().unwrap(), e);
|
||||
process::exit(1);
|
||||
}))
|
||||
} else {
|
||||
Ok(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let binds = get_binds().unwrap_or(Default::default());
|
||||
|
||||
let mut board = [[None; 10]; 40];
|
||||
|
||||
let window = initscr();
|
||||
|
@ -288,25 +425,25 @@ fn main() {
|
|||
let key = window.getch();
|
||||
|
||||
if let Some(key) = key {
|
||||
if key == Input::Character('q') {
|
||||
if binds.quit.iter().any(|v| *v == key) {
|
||||
break
|
||||
}
|
||||
|
||||
if key == Input::KeyLeft {
|
||||
if binds.left.iter().any(|v| *v == key) {
|
||||
if check_tetrimino_valid(tetrimino.into(), board, x - 1, y) {
|
||||
needs_refresh = true;
|
||||
x -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if key == Input::KeyRight {
|
||||
if binds.right.iter().any(|v| *v == key) {
|
||||
if check_tetrimino_valid(tetrimino.into(), board, x + 1, y) {
|
||||
needs_refresh = true;
|
||||
x += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if key == Input::KeyUp || key == Input::Character('x') {
|
||||
if binds.cw_rot.iter().any(|v| *v == key) {
|
||||
for kick in tetrimino.wallkick_table().for_rotation_cw(tetrimino.rotation) {
|
||||
tetrimino.rotate_cw();
|
||||
if check_tetrimino_valid(tetrimino.into(), board, x + kick.0, y - kick.1) {
|
||||
|
@ -320,7 +457,7 @@ fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
if key == Input::Character('z') {
|
||||
if binds.ccw_rot.iter().any(|v| *v == key) {
|
||||
for kick in tetrimino.wallkick_table().for_rotation_ccw(tetrimino.rotation) {
|
||||
tetrimino.rotate_ccw();
|
||||
if check_tetrimino_valid(tetrimino.into(), board, x + kick.0, y - kick.1) {
|
||||
|
@ -334,7 +471,7 @@ fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
if key == Input::KeyDown {
|
||||
if binds.soft_drop.iter().any(|v| *v == key) {
|
||||
if check_tetrimino_valid(tetrimino.into(), board, x, y + 1) {
|
||||
needs_refresh = true;
|
||||
y += 1;
|
||||
|
@ -346,7 +483,7 @@ fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
if key == Input::Character(' ') {
|
||||
if binds.hard_drop.iter().any(|v| *v == key) {
|
||||
// while !drop_tetrimino(tetrimino, &mut board, &mut x, &mut y) {}
|
||||
while check_tetrimino_valid(tetrimino.data(), board, x, y + 1) {
|
||||
y += 1;
|
||||
|
@ -357,7 +494,7 @@ fn main() {
|
|||
reset!(next_fall, tetrimino, y, x, hold_used, rando, board);
|
||||
}
|
||||
|
||||
if key == Input::Character('c') {
|
||||
if binds.hold.iter().any(|v| *v == key) {
|
||||
if !hold_used {
|
||||
tetrimino.rotation = Rotation::Spawn;
|
||||
let held = hold.replace(tetrimino);
|
||||
|
|
Loading…
Reference in a new issue