From b28dd478e31901d42bbdfbf6364e0496d13936c5 Mon Sep 17 00:00:00 2001
From: "Eric S. Raymond" <esr@thyrsus.com>
Date: Wed, 13 May 2026 11:29:17 -0400
Subject: [PATCH] Change the back end to use crossterm and build with cargo.

---
 Cargo.lock  | 331 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 Cargo.toml  |  12 ++
 greed.rs    | 256 ++++++++++++++++++++++++----------------
 7 files changed, 514 insertions(+), 112 deletions(-)
 create mode 100644 Cargo.lock
 create mode 100644 Cargo.toml

diff --git Cargo.lock Cargo.lock
new file mode 100644
index 0000000..98e4d01
--- /dev/null
+++ Cargo.lock
@@ -0,0 +1,331 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "bitflags"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "convert_case"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "crossterm"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
+dependencies = [
+ "bitflags",
+ "crossterm_winapi",
+ "derive_more",
+ "document-features",
+ "mio",
+ "parking_lot",
+ "rustix",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "derive_more"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn",
+]
+
+[[package]]
+name = "document-features"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
+dependencies = [
+ "litrs",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "greed"
+version = "5.0.0"
+dependencies = [
+ "crossterm",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.186"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
+name = "litrs"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+
+[[package]]
+name = "mio"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "semver"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
+
+[[package]]
+name = "signal-hook"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-mio"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
+dependencies = [
+ "libc",
+ "mio",
+ "signal-hook",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
+dependencies = [
+ "errno",
+ "libc",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
diff --git Cargo.toml Cargo.toml
new file mode 100644
index 0000000..e8e73a7
--- /dev/null
+++ Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "greed"
+version = "5.0.0"
+edition = "2021"
+license = "BSD-2-Clause"
+
+[[bin]]
+name = "greed"
+path = "greed.rs"
+
+[dependencies]
+crossterm = "0.29"
diff --git greed.rs greed.rs
index ad87406..ab4a822 100644
--- greed.rs
+++ greed.rs
@@ -11,10 +11,18 @@ use std::io::{self, Read, Seek, SeekFrom, Write};
 use std::mem::{align_of, size_of};
 use std::os::unix::fs::OpenOptionsExt;
 use std::path::{Path, PathBuf};
-use std::process::{self, Command, Stdio};
+use std::process::{self, Command};
 use std::thread;
 use std::time::{Duration, SystemTime, UNIX_EPOCH};
 
+use crossterm::cursor::{Hide, MoveTo, Show};
+use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
+use crossterm::style::{
+    Attribute, Color as TermColor, Print, ResetColor, SetAttribute, SetForegroundColor,
+};
+use crossterm::terminal::{self, Clear, ClearType};
+use crossterm::{execute, queue};
+
 const ME: char = '@';
 const MAXSCORES: usize = 10;
 const LOCKPATH: &str = "/tmp/Greed.lock";
@@ -124,11 +132,7 @@ impl Lrand48 {
     }
 
     fn next(&mut self) -> i32 {
-        self.state = self
-            .state
-            .wrapping_mul(0x5deece66d)
-            .wrapping_add(0xb)
-            & Self::MASK;
+        self.state = self.state.wrapping_mul(0x5deece66d).wrapping_add(0xb) & Self::MASK;
         (self.state >> 17) as i32
     }
 
@@ -138,31 +142,32 @@ impl Lrand48 {
 }
 
 struct Terminal {
-    saved_stty: Option<String>,
+    raw_mode: bool,
     stdout: io::Stdout,
 }
 
 impl Terminal {
     fn enter() -> io::Result<Self> {
-        let saved_stty = stty_state();
-        if saved_stty.is_some() {
-            let _ = Command::new("stty")
-                .args(["raw", "-echo"])
-                .stdin(Stdio::inherit())
-                .status();
-        }
+        terminal::enable_raw_mode()?;
 
         let mut terminal = Self {
-            saved_stty,
+            raw_mode: true,
             stdout: io::stdout(),
         };
-        terminal.write("\x1b[?25l")?;
-        terminal.flush()?;
+        if let Err(err) = terminal.hide_cursor() {
+            let _ = terminal::disable_raw_mode();
+            return Err(err);
+        }
         Ok(terminal)
     }
 
+    fn hide_cursor(&mut self) -> io::Result<()> {
+        queue!(self.stdout, Hide)?;
+        self.flush()
+    }
+
     fn write(&mut self, text: &str) -> io::Result<()> {
-        self.stdout.write_all(text.as_bytes())
+        queue!(self.stdout, Print(text))
     }
 
     fn flush(&mut self) -> io::Result<()> {
@@ -170,33 +175,33 @@ impl Terminal {
     }
 
     fn clear_screen(&mut self) -> io::Result<()> {
-        self.write("\x1b[2J")
+        queue!(self.stdout, Clear(ClearType::All))
     }
 
     fn move_to(&mut self, row: i32, col: i32) -> io::Result<()> {
-        write!(self.stdout, "\x1b[{};{}H", row + 1, col + 1)
+        queue!(self.stdout, MoveTo(coord(col), coord(row)))
     }
 
     fn clear_to_eol(&mut self) -> io::Result<()> {
-        self.write("\x1b[K")
+        queue!(self.stdout, Clear(ClearType::UntilNewLine))
     }
 
     fn set_attr(&mut self, attr: CellAttr, reverse: bool) -> io::Result<()> {
-        self.write("\x1b[0")?;
+        queue!(self.stdout, SetAttribute(Attribute::Reset), ResetColor)?;
         if attr.bold {
-            self.write(";1")?;
+            queue!(self.stdout, SetAttribute(Attribute::Bold))?;
         }
         if reverse {
-            self.write(";7")?;
+            queue!(self.stdout, SetAttribute(Attribute::Reverse))?;
         }
         if let Some(color) = attr.color {
-            write!(self.stdout, ";{}", color.sgr_code())?;
+            queue!(self.stdout, SetForegroundColor(color.term_color()))?;
         }
-        self.write("m")
+        Ok(())
     }
 
     fn reset_attr(&mut self) -> io::Result<()> {
-        self.write("\x1b[0m")
+        queue!(self.stdout, SetAttribute(Attribute::Reset), ResetColor)
     }
 
     fn addstr(&mut self, row: i32, col: i32, text: &str) -> io::Result<()> {
@@ -206,24 +211,25 @@ impl Terminal {
 
     fn addch(&mut self, row: i32, col: i32, ch: char) -> io::Result<()> {
         self.move_to(row, col)?;
-        write!(self.stdout, "{}", ch)
+        queue!(self.stdout, Print(ch))
     }
 }
 
 impl Drop for Terminal {
     fn drop(&mut self) {
         let _ = self.reset_attr();
-        let _ = self.write("\x1b[?25h");
+        let _ = queue!(self.stdout, Show);
         let _ = self.flush();
-        if let Some(saved) = &self.saved_stty {
-            let _ = Command::new("stty")
-                .arg(saved)
-                .stdin(Stdio::inherit())
-                .status();
+        if self.raw_mode {
+            let _ = terminal::disable_raw_mode();
         }
     }
 }
 
+fn coord(value: i32) -> u16 {
+    value.clamp(0, i32::from(u16::MAX)) as u16
+}
+
 enum Key {
     Char(char),
     Ctrl(char),
@@ -241,15 +247,15 @@ enum MoveResult {
 }
 
 impl Color {
-    fn sgr_code(self) -> i32 {
+    fn term_color(self) -> TermColor {
         match self {
-            Color::Blue => 34,
-            Color::Green => 32,
-            Color::Cyan => 36,
-            Color::Red => 31,
-            Color::Magenta => 35,
-            Color::Yellow => 33,
-            Color::White => 37,
+            Color::Blue => TermColor::Blue,
+            Color::Green => TermColor::Green,
+            Color::Cyan => TermColor::Cyan,
+            Color::Red => TermColor::Red,
+            Color::Magenta => TermColor::Magenta,
+            Color::Yellow => TermColor::Yellow,
+            Color::White => TermColor::White,
         }
     }
 }
@@ -470,7 +476,11 @@ impl Game {
         term.addstr(
             self.config.status_row,
             7,
-            &format!("{}  {:.2}%", self.score, score_perc(self.score, self.config.height, self.config.width)),
+            &format!(
+                "{}  {:.2}%",
+                self.score,
+                score_perc(self.score, self.config.height, self.config.width)
+            ),
         )?;
         term.clear_to_eol()?;
         if self.havebotmsg {
@@ -530,23 +540,74 @@ impl Game {
                 version()
             ),
         )?;
-        term.addstr(4, 9, " The object of Greed is to erase as much of the screen as")?;
-        term.addstr(5, 9, " possible by moving around in a grid of numbers.  To move,")?;
-        term.addstr(6, 9, " use the arrow keys, your number pad, or one of the letters")?;
+        term.addstr(
+            4,
+            9,
+            " The object of Greed is to erase as much of the screen as",
+        )?;
+        term.addstr(
+            5,
+            9,
+            " possible by moving around in a grid of numbers.  To move,",
+        )?;
+        term.addstr(
+            6,
+            9,
+            " use the arrow keys, your number pad, or one of the letters",
+        )?;
         term.addstr(
             7,
             9,
-            &format!(" 'hjklyubn'. Your location is signified by the '{}' symbol.", ME),
+            &format!(
+                " 'hjklyubn'. Your location is signified by the '{}' symbol.",
+                ME
+            ),
+        )?;
+        term.addstr(
+            8,
+            9,
+            " When you move in a direction, you erase N number of grid",
+        )?;
+        term.addstr(
+            9,
+            9,
+            " squares in that direction, N being the first number in that",
+        )?;
+        term.addstr(
+            10,
+            9,
+            " direction.  Your score reflects the total number of squares",
+        )?;
+        term.addstr(
+            11,
+            9,
+            " eaten.  Greed will not let you make a move that would have",
+        )?;
+        term.addstr(
+            12,
+            9,
+            " placed you off the grid or over a previously eaten square",
+        )?;
+        term.addstr(
+            13,
+            9,
+            " unless no valid moves exist, in which case your game ends.",
+        )?;
+        term.addstr(
+            14,
+            9,
+            " Other Greed commands are 'Ctrl-L' to redraw the screen,",
+        )?;
+        term.addstr(
+            15,
+            9,
+            " 'p' to toggle the highlighting of the possible moves, and",
+        )?;
+        term.addstr(
+            16,
+            9,
+            " 'q' to quit.  Command line options include '-s' (scores)",
         )?;
-        term.addstr(8, 9, " When you move in a direction, you erase N number of grid")?;
-        term.addstr(9, 9, " squares in that direction, N being the first number in that")?;
-        term.addstr(10, 9, " direction.  Your score reflects the total number of squares")?;
-        term.addstr(11, 9, " eaten.  Greed will not let you make a move that would have")?;
-        term.addstr(12, 9, " placed you off the grid or over a previously eaten square")?;
-        term.addstr(13, 9, " unless no valid moves exist, in which case your game ends.")?;
-        term.addstr(14, 9, " Other Greed commands are 'Ctrl-L' to redraw the screen,")?;
-        term.addstr(15, 9, " 'p' to toggle the highlighting of the possible moves, and")?;
-        term.addstr(16, 9, " 'q' to quit.  Command line options include '-s' (scores)")?;
         term.addstr(17, 9, " and '-r' to set the RNG seed.")?;
         term.move_to(18, 71)?;
         term.flush()?;
@@ -704,56 +765,40 @@ fn fit_to_tty(config: &mut Config) {
 }
 
 fn terminal_size() -> Option<(i32, i32)> {
-    let output = Command::new("stty")
-        .arg("size")
-        .stdin(Stdio::inherit())
-        .output()
-        .ok()?;
-    let text = String::from_utf8(output.stdout).ok()?;
-    let mut fields = text.split_whitespace();
-    let rows = fields.next()?.parse::<i32>().ok()?;
-    let cols = fields.next()?.parse::<i32>().ok()?;
-    Some((rows, cols))
-}
-
-fn stty_state() -> Option<String> {
-    let output = Command::new("stty")
-        .arg("-g")
-        .stdin(Stdio::inherit())
-        .output()
-        .ok()?;
-    if output.status.success() {
-        Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
-    } else {
-        None
-    }
+    let (cols, rows) = terminal::size().ok()?;
+    Some((i32::from(rows), i32::from(cols)))
 }
 
 fn read_key() -> io::Result<Key> {
-    let mut byte = [0];
-    io::stdin().read_exact(&mut byte)?;
-    match byte[0] {
-        b'\x1b' => read_escape_key(),
-        0x03 => Ok(Key::Ctrl('c')),
-        0x0c => Ok(Key::Ctrl('l')),
-        0x12 => Ok(Key::Ctrl('r')),
-        0x1c => Ok(Key::Ctrl('\\')),
-        byte if byte.is_ascii() => Ok(Key::Char(byte as char)),
-        _ => Ok(Key::Unknown),
+    loop {
+        let Event::Key(event) = event::read()? else {
+            continue;
+        };
+        if !matches!(event.kind, KeyEventKind::Press | KeyEventKind::Repeat) {
+            continue;
+        }
+        return Ok(key_from_event(event));
     }
 }
 
-fn read_escape_key() -> io::Result<Key> {
-    let mut seq = [0; 2];
-    if io::stdin().read_exact(&mut seq).is_err() {
-        return Ok(Key::Unknown);
+fn key_from_event(event: KeyEvent) -> Key {
+    if event.modifiers.contains(KeyModifiers::CONTROL) {
+        return match event.code {
+            KeyCode::Char('c') | KeyCode::Char('C') => Key::Ctrl('c'),
+            KeyCode::Char('l') | KeyCode::Char('L') => Key::Ctrl('l'),
+            KeyCode::Char('r') | KeyCode::Char('R') => Key::Ctrl('r'),
+            KeyCode::Char('\\') | KeyCode::Char('4') => Key::Ctrl('\\'),
+            _ => Key::Unknown,
+        };
     }
-    match seq {
-        [b'[', b'A'] | [b'O', b'A'] => Ok(Key::Up),
-        [b'[', b'B'] | [b'O', b'B'] => Ok(Key::Down),
-        [b'[', b'C'] | [b'O', b'C'] => Ok(Key::Right),
-        [b'[', b'D'] | [b'O', b'D'] => Ok(Key::Left),
-        _ => Ok(Key::Unknown),
+
+    match event.code {
+        KeyCode::Char(ch) if ch.is_ascii() => Key::Char(ch),
+        KeyCode::Up => Key::Up,
+        KeyCode::Down => Key::Down,
+        KeyCode::Left => Key::Left,
+        KeyCode::Right => Key::Right,
+        _ => Key::Unknown,
     }
 }
 
@@ -1073,7 +1118,14 @@ fn topscores(newscore: i32, config: &Config) {
     if newscore > 0 {
         let newscore_p = score_adj(newscore, config.height, config.width, config.maxstep);
         for (idx, existing) in toplist.iter().enumerate() {
-            if newscore_p > score_adj(existing.score, existing.height, existing.width, existing.maxstep) {
+            if newscore_p
+                > score_adj(
+                    existing.score,
+                    existing.height,
+                    existing.width,
+                    existing.maxstep,
+                )
+            {
                 new_index = Some(idx);
                 break;
             }
@@ -1110,7 +1162,7 @@ fn topscores(newscore: i32, config: &Config) {
             break;
         }
         if Some(idx) == new_index {
-            print!("\x1b[7m");
+            let _ = execute!(io::stdout(), SetAttribute(Attribute::Reverse));
         }
 
         let sizestr = if entry.maxstep < 9 {
@@ -1130,7 +1182,7 @@ fn topscores(newscore: i32, config: &Config) {
         );
 
         if Some(idx) == new_index {
-            print!("\x1b[0m");
+            let _ = execute!(io::stdout(), SetAttribute(Attribute::Reset));
         }
     }
 }
-- 
GitLab

