This commit is contained in:
missing 2022-03-30 14:21:22 -05:00
parent 2f5809c5cd
commit 2f35ed64c5
3 changed files with 161 additions and 9 deletions

View file

@ -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]

View file

@ -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'

View file

@ -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);