diff options
| author | salmonik <salmonik@tuta.io> | 2026-10-29 22:16:15 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-10-29 22:16:15 +0100 |
| commit | bc0de9955c375cf27d52c9934403964205afc182 (patch) | |
| tree | 42799fb426fb0ec164f44c92a63525003c582f35 /src | |
| parent | initial connection timeout set to 10s (diff) | |
| download | zagan-bet-bc0de9955c375cf27d52c9934403964205afc182.tar.gz zagan-bet-bc0de9955c375cf27d52c9934403964205afc182.zip | |
formatting
Diffstat (limited to '')
| -rw-r--r-- | src/bin/client/repl.rs | 27 | ||||
| -rw-r--r-- | src/bin/client/tls.rs | 17 | ||||
| -rw-r--r-- | src/bin/server/args.rs | 5 | ||||
| -rw-r--r-- | src/bin/server/main.rs | 6 | ||||
| -rw-r--r-- | src/bin/server/tls.rs | 5 | ||||
| -rw-r--r-- | src/client/core.rs | 43 | ||||
| -rw-r--r-- | src/client/database.rs | 7 | ||||
| -rw-r--r-- | src/client/games/blackjack.rs | 11 | ||||
| -rw-r--r-- | src/client/games/manager.rs | 8 | ||||
| -rw-r--r-- | src/client/games/response.rs | 4 | ||||
| -rw-r--r-- | src/client/games/roulette.rs | 12 | ||||
| -rw-r--r-- | src/client/games/slots.rs | 6 | ||||
| -rw-r--r-- | src/client/ui.rs | 13 | ||||
| -rw-r--r-- | src/crash.rs | 4 | ||||
| -rw-r--r-- | src/roulette.rs | 10 | ||||
| -rw-r--r-- | src/server/core.rs | 45 | ||||
| -rw-r--r-- | src/server/games/blackjack.rs | 4 | ||||
| -rw-r--r-- | src/server/games/roulette.rs | 10 | ||||
| -rw-r--r-- | src/server/games/slots.rs | 4 | ||||
| -rw-r--r-- | src/server/setup.rs | 20 | ||||
| -rw-r--r-- | src/slots.rs | 10 | ||||
| -rw-r--r-- | src/tofu.rs | 5 |
22 files changed, 88 insertions, 188 deletions
diff --git a/src/bin/client/repl.rs b/src/bin/client/repl.rs index 7fa462a..9af4ea9 100644 --- a/src/bin/client/repl.rs +++ b/src/bin/client/repl.rs | |||
| @@ -1,14 +1,14 @@ | |||
| 1 | use rusqlite::Connection; | 1 | use rusqlite::Connection; |
| 2 | use std::io::{stdout, Write}; | 2 | use std::io::{Write, stdout}; |
| 3 | use std::sync::{Arc, Mutex}; | 3 | use std::sync::{Arc, Mutex}; |
| 4 | use std::time::Instant; | 4 | use std::time::Instant; |
| 5 | 5 | ||
| 6 | use crossterm::{ | 6 | use crossterm::{ |
| 7 | cursor::{MoveToColumn, MoveUp, Show}, | 7 | cursor::{MoveToColumn, MoveUp, Show}, |
| 8 | event::{read, Event, KeyCode, KeyEventKind, KeyModifiers}, | 8 | event::{Event, KeyCode, KeyEventKind, KeyModifiers, read}, |
| 9 | execute, | 9 | execute, |
| 10 | style::Stylize, | 10 | style::Stylize, |
| 11 | terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType}, | 11 | terminal::{Clear, ClearType, disable_raw_mode, enable_raw_mode}, |
| 12 | }; | 12 | }; |
| 13 | 13 | ||
| 14 | use sha2::{Digest, Sha256}; | 14 | use sha2::{Digest, Sha256}; |
| @@ -20,7 +20,7 @@ use zagan_bet::client::ui::{ | |||
| 20 | visual_line_count, | 20 | visual_line_count, |
| 21 | }; | 21 | }; |
| 22 | use zagan_bet::protocol::io_failure_message; | 22 | use zagan_bet::protocol::io_failure_message; |
| 23 | use zagan_bet::roulette::{color as roulette_color, WHEEL}; | 23 | use zagan_bet::roulette::{WHEEL, color as roulette_color}; |
| 24 | 24 | ||
| 25 | use crate::connection::TlsStream; | 25 | use crate::connection::TlsStream; |
| 26 | 26 | ||
| @@ -71,8 +71,7 @@ impl ClientState { | |||
| 71 | self.core.set_connected(true); | 71 | self.core.set_connected(true); |
| 72 | self.core.set_logged_in(authed_user); | 72 | self.core.set_logged_in(authed_user); |
| 73 | let fingerprint = database::get_fingerprint(&self.conn, addr); | 73 | let fingerprint = database::get_fingerprint(&self.conn, addr); |
| 74 | self.core | 74 | self.core.set_host_info(Some(addr.to_string()), fingerprint); |
| 75 | .set_host_info(Some(addr.to_string()), fingerprint); | ||
| 76 | database::save_host(&self.conn, addr); | 75 | database::save_host(&self.conn, addr); |
| 77 | } | 76 | } |
| 78 | Err(e) => { | 77 | Err(e) => { |
| @@ -178,12 +177,9 @@ impl ClientState { | |||
| 178 | Effect::Print(lines) => { | 177 | Effect::Print(lines) => { |
| 179 | self.commit_prompt_line(); | 178 | self.commit_prompt_line(); |
| 180 | for line in lines { | 179 | for line in lines { |
| 181 | let rendered = if line.starts_with("ERROR") | 180 | let rendered = if line.starts_with("ERROR") || line.contains("failed") { |
| 182 | || line.contains("failed") | ||
| 183 | { | ||
| 184 | line.as_str().red().to_string() | 181 | line.as_str().red().to_string() |
| 185 | } else if line.starts_with("Logged in") | 182 | } else if line.starts_with("Logged in") || line.starts_with("Successfully") |
| 186 | || line.starts_with("Successfully") | ||
| 187 | { | 183 | { |
| 188 | line.as_str().green().to_string() | 184 | line.as_str().green().to_string() |
| 189 | } else if line.starts_with("Available commands:") | 185 | } else if line.starts_with("Available commands:") |
| @@ -249,10 +245,7 @@ impl ClientState { | |||
| 249 | Effect::ForgetHost(target) => { | 245 | Effect::ForgetHost(target) => { |
| 250 | self.commit_prompt_line(); | 246 | self.commit_prompt_line(); |
| 251 | match database::forget_host(&self.conn, &target) { | 247 | match database::forget_host(&self.conn, &target) { |
| 252 | Ok(_) => println!( | 248 | Ok(_) => println!("Cleared trust and stored credentials for {}.", target), |
| 253 | "Cleared trust and stored credentials for {}.", | ||
| 254 | target | ||
| 255 | ), | ||
| 256 | Err(error) => println!("Failed to forget host: {}", error), | 249 | Err(error) => println!("Failed to forget host: {}", error), |
| 257 | } | 250 | } |
| 258 | } | 251 | } |
| @@ -297,9 +290,7 @@ impl ClientState { | |||
| 297 | let roulette = lines | 290 | let roulette = lines |
| 298 | .first() | 291 | .first() |
| 299 | .is_some_and(|first| first.contains("ROULETTE")); | 292 | .is_some_and(|first| first.contains("ROULETTE")); |
| 300 | let crash = lines | 293 | let crash = lines.first().is_some_and(|first| first.contains("CRASH")); |
| 301 | .first() | ||
| 302 | .is_some_and(|first| first.contains("CRASH")); | ||
| 303 | let rendered: Vec<String> = lines | 294 | let rendered: Vec<String> = lines |
| 304 | .iter() | 295 | .iter() |
| 305 | .map(|line| { | 296 | .map(|line| { |
diff --git a/src/bin/client/tls.rs b/src/bin/client/tls.rs index b46f52b..e14ed98 100644 --- a/src/bin/client/tls.rs +++ b/src/bin/client/tls.rs | |||
| @@ -17,10 +17,10 @@ use std::net::TcpStream; | |||
| 17 | use std::sync::Arc; | 17 | use std::sync::Arc; |
| 18 | 18 | ||
| 19 | use parking_lot::Mutex; | 19 | use parking_lot::Mutex; |
| 20 | use rustls::client::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; | ||
| 21 | use rustls::DigitallySignedStruct; | 20 | use rustls::DigitallySignedStruct; |
| 21 | use rustls::client::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; | ||
| 22 | use zagan_bet::protocol::LineBuffer; | 22 | use zagan_bet::protocol::LineBuffer; |
| 23 | use zagan_bet::tofu::{decide, spki_fingerprint, TrustDecision}; | 23 | use zagan_bet::tofu::{TrustDecision, decide, spki_fingerprint}; |
| 24 | 24 | ||
| 25 | pub struct TlsConnector { | 25 | pub struct TlsConnector { |
| 26 | config: Arc<rustls::ClientConfig>, | 26 | config: Arc<rustls::ClientConfig>, |
| @@ -53,7 +53,10 @@ impl TlsConnector { | |||
| 53 | }) | 53 | }) |
| 54 | } | 54 | } |
| 55 | 55 | ||
| 56 | pub fn connect(&self, stream: TcpStream) -> Result<TlsStream, Box<dyn std::error::Error + Send + Sync>> { | 56 | pub fn connect( |
| 57 | &self, | ||
| 58 | stream: TcpStream, | ||
| 59 | ) -> Result<TlsStream, Box<dyn std::error::Error + Send + Sync>> { | ||
| 57 | let mut conn = | 60 | let mut conn = |
| 58 | rustls::ClientConnection::new(Arc::clone(&self.config), self.server_name.clone())?; | 61 | rustls::ClientConnection::new(Arc::clone(&self.config), self.server_name.clone())?; |
| 59 | let mut sock = stream; | 62 | let mut sock = stream; |
| @@ -200,10 +203,7 @@ impl TlsStream { | |||
| 200 | } | 203 | } |
| 201 | 204 | ||
| 202 | /// Apply a read timeout to the underlying TCP socket. | 205 | /// Apply a read timeout to the underlying TCP socket. |
| 203 | pub fn set_read_timeout( | 206 | pub fn set_read_timeout(&self, duration: Option<std::time::Duration>) -> std::io::Result<()> { |
| 204 | &self, | ||
| 205 | duration: Option<std::time::Duration>, | ||
| 206 | ) -> std::io::Result<()> { | ||
| 207 | let mut s = self.session.lock(); | 207 | let mut s = self.session.lock(); |
| 208 | s.read_timeout = duration; | 208 | s.read_timeout = duration; |
| 209 | s.sock.set_read_timeout(duration) | 209 | s.sock.set_read_timeout(duration) |
| @@ -235,8 +235,7 @@ impl TlsStream { | |||
| 235 | Err(error) | 235 | Err(error) |
| 236 | if matches!( | 236 | if matches!( |
| 237 | error.kind(), | 237 | error.kind(), |
| 238 | std::io::ErrorKind::WouldBlock | 238 | std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut |
| 239 | | std::io::ErrorKind::TimedOut | ||
| 240 | ) => | 239 | ) => |
| 241 | { | 240 | { |
| 242 | return Ok(()); | 241 | return Ok(()); |
diff --git a/src/bin/server/args.rs b/src/bin/server/args.rs index 1babd1d..1db2a28 100644 --- a/src/bin/server/args.rs +++ b/src/bin/server/args.rs | |||
| @@ -31,10 +31,7 @@ pub fn parse_args() -> Args { | |||
| 31 | if i + 1 < args.len() { | 31 | if i + 1 < args.len() { |
| 32 | ip = args[i + 1].clone(); | 32 | ip = args[i + 1].clone(); |
| 33 | if ip != "127.0.0.1" && ip != "0.0.0.0" { | 33 | if ip != "127.0.0.1" && ip != "0.0.0.0" { |
| 34 | eprintln!( | 34 | eprintln!("Invalid IP address: {}. Must be 127.0.0.1 or 0.0.0.0", ip); |
| 35 | "Invalid IP address: {}. Must be 127.0.0.1 or 0.0.0.0", | ||
| 36 | ip | ||
| 37 | ); | ||
| 38 | std::process::exit(1); | 35 | std::process::exit(1); |
| 39 | } | 36 | } |
| 40 | i += 2; | 37 | i += 2; |
diff --git a/src/bin/server/main.rs b/src/bin/server/main.rs index 85e3134..f42574d 100644 --- a/src/bin/server/main.rs +++ b/src/bin/server/main.rs | |||
| @@ -40,9 +40,9 @@ fn main() { | |||
| 40 | let db_path = std::env::var("SERVER_DB").unwrap_or_else(|_| "server.db".to_string()); | 40 | let db_path = std::env::var("SERVER_DB").unwrap_or_else(|_| "server.db".to_string()); |
| 41 | match zagan_bet::server::core::recover_orphaned_reservations(&db_path) { | 41 | match zagan_bet::server::core::recover_orphaned_reservations(&db_path) { |
| 42 | Ok(0) => {} | 42 | Ok(0) => {} |
| 43 | Ok(count) => println!( | 43 | Ok(count) => { |
| 44 | "Settled {count} orphaned reservation(s) as abandoned after a restart" | 44 | println!("Settled {count} orphaned reservation(s) as abandoned after a restart") |
| 45 | ), | 45 | } |
| 46 | Err(error) => { | 46 | Err(error) => { |
| 47 | eprintln!("Failed to settle orphaned reservations in {db_path}: {error}"); | 47 | eprintln!("Failed to settle orphaned reservations in {db_path}: {error}"); |
| 48 | std::process::exit(1); | 48 | std::process::exit(1); |
diff --git a/src/bin/server/tls.rs b/src/bin/server/tls.rs index 5d84589..420b5b4 100644 --- a/src/bin/server/tls.rs +++ b/src/bin/server/tls.rs | |||
| @@ -80,10 +80,7 @@ impl TlsStream { | |||
| 80 | 80 | ||
| 81 | /// Read timeout for the socket. The connection loop uses a short one so it | 81 | /// Read timeout for the socket. The connection loop uses a short one so it |
| 82 | /// can advance crash rounds while no request is in flight. | 82 | /// can advance crash rounds while no request is in flight. |
| 83 | pub fn set_read_timeout( | 83 | pub fn set_read_timeout(&self, duration: Option<std::time::Duration>) -> std::io::Result<()> { |
| 84 | &self, | ||
| 85 | duration: Option<std::time::Duration>, | ||
| 86 | ) -> std::io::Result<()> { | ||
| 87 | self.stream.get_ref().set_read_timeout(duration) | 84 | self.stream.get_ref().set_read_timeout(duration) |
| 88 | } | 85 | } |
| 89 | } | 86 | } |
diff --git a/src/client/core.rs b/src/client/core.rs index 5f8c513..06d9c42 100644 --- a/src/client/core.rs +++ b/src/client/core.rs | |||
| @@ -3,8 +3,8 @@ use crate::client::games::response::{ | |||
| 3 | parse_blackjack_response, parse_crash_response, parse_roulette_response, parse_spin_response, | 3 | parse_blackjack_response, parse_crash_response, parse_roulette_response, parse_spin_response, |
| 4 | }; | 4 | }; |
| 5 | use crate::client::ui::{ | 5 | use crate::client::ui::{ |
| 6 | command_names, completion_lines, display_width, help_lines, input_cursor_column, | 6 | command_names, completion_lines, display_width, help_lines, input_cursor_column, parse_bet_arg, |
| 7 | parse_bet_arg, record_history, | 7 | record_history, |
| 8 | }; | 8 | }; |
| 9 | use crate::crash::{multiplier_at, CrashState}; | 9 | use crate::crash::{multiplier_at, CrashState}; |
| 10 | use crate::roulette::{Bets, NumberBet}; | 10 | use crate::roulette::{Bets, NumberBet}; |
| @@ -225,10 +225,7 @@ impl ClientCore { | |||
| 225 | "Game exited. Active hand forfeited ({} PLN lost).", | 225 | "Game exited. Active hand forfeited ({} PLN lost).", |
| 226 | state.bet | 226 | state.bet |
| 227 | ); | 227 | ); |
| 228 | vec![ | 228 | vec![Effect::ClearRegion, Effect::Print(vec![message])] |
| 229 | Effect::ClearRegion, | ||
| 230 | Effect::Print(vec![message]), | ||
| 231 | ] | ||
| 232 | } | 229 | } |
| 233 | Err(message) => { | 230 | Err(message) => { |
| 234 | self.game.set_error(message); | 231 | self.game.set_error(message); |
| @@ -258,10 +255,7 @@ impl ClientCore { | |||
| 258 | Ok(state) => { | 255 | Ok(state) => { |
| 259 | self.crash_started = None; | 256 | self.crash_started = None; |
| 260 | self.game.end_game(); | 257 | self.game.end_game(); |
| 261 | let message = format!( | 258 | let message = format!("Game exited. Round forfeited ({} PLN lost).", state.bet); |
| 262 | "Game exited. Round forfeited ({} PLN lost).", | ||
| 263 | state.bet | ||
| 264 | ); | ||
| 265 | vec![Effect::ClearRegion, Effect::Print(vec![message])] | 259 | vec![Effect::ClearRegion, Effect::Print(vec![message])] |
| 266 | } | 260 | } |
| 267 | Err(_) if self.game.crash_status() != "running" => { | 261 | Err(_) if self.game.crash_status() != "running" => { |
| @@ -286,9 +280,7 @@ impl ClientCore { | |||
| 286 | self.logged_in = None; | 280 | self.logged_in = None; |
| 287 | vec![ | 281 | vec![ |
| 288 | Effect::Disconnect, | 282 | Effect::Disconnect, |
| 289 | Effect::Print(vec![ | 283 | Effect::Print(vec!["Connection lost".to_string()]), |
| 290 | "Connection lost".to_string(), | ||
| 291 | ]), | ||
| 292 | ] | 284 | ] |
| 293 | } | 285 | } |
| 294 | } | 286 | } |
| @@ -531,27 +523,20 @@ impl ClientCore { | |||
| 531 | } | 523 | } |
| 532 | } | 524 | } |
| 533 | Key::Char(' ') => self.start_blackjack(), | 525 | Key::Char(' ') => self.start_blackjack(), |
| 534 | Key::Char('h') | 526 | Key::Char('h') if hand.as_ref().is_some_and(|hand| hand.phase == "player") => { |
| 535 | if hand.as_ref().is_some_and(|hand| hand.phase == "player") => | ||
| 536 | { | ||
| 537 | self.blackjack_action("hit") | 527 | self.blackjack_action("hit") |
| 538 | } | 528 | } |
| 539 | Key::Char('s') | 529 | Key::Char('s') if hand.as_ref().is_some_and(|hand| hand.phase == "player") => { |
| 540 | if hand.as_ref().is_some_and(|hand| hand.phase == "player") => | ||
| 541 | { | ||
| 542 | self.blackjack_action("stand") | 530 | self.blackjack_action("stand") |
| 543 | } | 531 | } |
| 544 | Key::Char('d') | 532 | Key::Char('d') |
| 545 | if hand.as_ref().is_some_and(|hand| { | 533 | if hand.as_ref().is_some_and(|hand| { |
| 546 | hand.phase == "player" | 534 | hand.phase == "player" && hand.actions.iter().any(|action| action == "double") |
| 547 | && hand.actions.iter().any(|action| action == "double") | ||
| 548 | }) => | 535 | }) => |
| 549 | { | 536 | { |
| 550 | self.blackjack_action("double") | 537 | self.blackjack_action("double") |
| 551 | } | 538 | } |
| 552 | Key::Char('+') | Key::Char('=') | 539 | Key::Char('+') | Key::Char('=') if matches!(status.as_str(), "ready" | "done") => { |
| 553 | if matches!(status.as_str(), "ready" | "done") => | ||
| 554 | { | ||
| 555 | let _ = self.game.adjust_bet(10); | 540 | let _ = self.game.adjust_bet(10); |
| 556 | self.board_effects() | 541 | self.board_effects() |
| 557 | } | 542 | } |
| @@ -602,9 +587,7 @@ impl ClientCore { | |||
| 602 | if hand.phase != "player" { | 587 | if hand.phase != "player" { |
| 603 | return Vec::new(); | 588 | return Vec::new(); |
| 604 | } | 589 | } |
| 605 | if action == "double" | 590 | if action == "double" && !hand.actions.iter().any(|available| available == "double") { |
| 606 | && !hand.actions.iter().any(|available| available == "double") | ||
| 607 | { | ||
| 608 | return Vec::new(); | 591 | return Vec::new(); |
| 609 | } | 592 | } |
| 610 | self.pending = Some(Pending::BlackjackAction); | 593 | self.pending = Some(Pending::BlackjackAction); |
| @@ -914,9 +897,9 @@ impl ClientCore { | |||
| 914 | Some(fingerprint) => { | 897 | Some(fingerprint) => { |
| 915 | effects.push(Effect::Print(vec![format!("Fingerprint: {fingerprint}")])) | 898 | effects.push(Effect::Print(vec![format!("Fingerprint: {fingerprint}")])) |
| 916 | } | 899 | } |
| 917 | None => effects.push(Effect::Print(vec![ | 900 | None => effects.push(Effect::Print( |
| 918 | "Fingerprint: (not trusted)".to_string(), | 901 | vec!["Fingerprint: (not trusted)".to_string()], |
| 919 | ])), | 902 | )), |
| 920 | } | 903 | } |
| 921 | 904 | ||
| 922 | self.pending = Some(Pending::Info); | 905 | self.pending = Some(Pending::Info); |
diff --git a/src/client/database.rs b/src/client/database.rs index e1fa42d..e1cedaf 100644 --- a/src/client/database.rs +++ b/src/client/database.rs | |||
| @@ -110,9 +110,8 @@ pub fn get_all_hosts(conn: &Connection) -> Vec<(String, Option<String>)> { | |||
| 110 | let mut stmt = conn | 110 | let mut stmt = conn |
| 111 | .prepare("SELECT host, fingerprint FROM hosts ORDER BY last_used DESC") | 111 | .prepare("SELECT host, fingerprint FROM hosts ORDER BY last_used DESC") |
| 112 | .unwrap(); | 112 | .unwrap(); |
| 113 | let rows = stmt.query_map([], |row| { | 113 | let rows = stmt |
| 114 | Ok((row.get(0)?, row.get(1)?)) | 114 | .query_map([], |row| Ok((row.get(0)?, row.get(1)?))) |
| 115 | }) | 115 | .unwrap(); |
| 116 | .unwrap(); | ||
| 117 | rows.filter_map(|r| r.ok()).collect() | 116 | rows.filter_map(|r| r.ok()).collect() |
| 118 | } | 117 | } |
diff --git a/src/client/games/blackjack.rs b/src/client/games/blackjack.rs index bad1781..d5f45b3 100644 --- a/src/client/games/blackjack.rs +++ b/src/client/games/blackjack.rs | |||
| @@ -56,15 +56,14 @@ impl Game for BlackjackGame { | |||
| 56 | .get("status") | 56 | .get("status") |
| 57 | .map(String::as_str) | 57 | .map(String::as_str) |
| 58 | .unwrap_or("ready"); | 58 | .unwrap_or("ready"); |
| 59 | let balance = state | 59 | let balance = state.data.get("balance").map(String::as_str).unwrap_or("?"); |
| 60 | .data | ||
| 61 | .get("balance") | ||
| 62 | .map(String::as_str) | ||
| 63 | .unwrap_or("?"); | ||
| 64 | let hand = Self::state(state); | 60 | let hand = Self::state(state); |
| 65 | 61 | ||
| 66 | if let Some(error) = state.data.get("error") { | 62 | if let Some(error) = state.data.get("error") { |
| 67 | let bet = hand.as_ref().map(|hand| hand.bet).unwrap_or(state.bet_amount); | 63 | let bet = hand |
| 64 | .as_ref() | ||
| 65 | .map(|hand| hand.bet) | ||
| 66 | .unwrap_or(state.bet_amount); | ||
| 68 | return format!( | 67 | return format!( |
| 69 | "=== BLACKJACK ===\r\nBalance: {balance} PLN Bet: {bet} PLN\r\n\r\n\r\nERROR: {error}\r\n[Q] Quit" | 68 | "=== BLACKJACK ===\r\nBalance: {balance} PLN Bet: {bet} PLN\r\n\r\n\r\nERROR: {error}\r\n[Q] Quit" |
| 70 | ); | 69 | ); |
diff --git a/src/client/games/manager.rs b/src/client/games/manager.rs index 4d9e6a8..b20ff44 100644 --- a/src/client/games/manager.rs +++ b/src/client/games/manager.rs | |||
| @@ -138,9 +138,7 @@ impl GameManager { | |||
| 138 | session.bet_amount = result.bet_amount; | 138 | session.bet_amount = result.bet_amount; |
| 139 | session.data.remove("error"); | 139 | session.data.remove("error"); |
| 140 | session.data.insert("reels".to_string(), result.reels); | 140 | session.data.insert("reels".to_string(), result.reels); |
| 141 | session | 141 | session.data.insert("win_type".to_string(), result.win_type); |
| 142 | .data | ||
| 143 | .insert("win_type".to_string(), result.win_type); | ||
| 144 | session.data.insert( | 142 | session.data.insert( |
| 145 | "win_lines".to_string(), | 143 | "win_lines".to_string(), |
| 146 | result | 144 | result |
| @@ -339,7 +337,9 @@ impl GameManager { | |||
| 339 | for key in ["crash_point", "payout"] { | 337 | for key in ["crash_point", "payout"] { |
| 340 | session.data.remove(key); | 338 | session.data.remove(key); |
| 341 | } | 339 | } |
| 342 | session.data.insert("status".to_string(), state.phase.clone()); | 340 | session |
| 341 | .data | ||
| 342 | .insert("status".to_string(), state.phase.clone()); | ||
| 343 | session | 343 | session |
| 344 | .data | 344 | .data |
| 345 | .insert("session_id".to_string(), state.session_id); | 345 | .insert("session_id".to_string(), state.session_id); |
diff --git a/src/client/games/response.rs b/src/client/games/response.rs index 71e1fed..88df459 100644 --- a/src/client/games/response.rs +++ b/src/client/games/response.rs | |||
| @@ -128,8 +128,8 @@ pub fn parse_roulette_response(response: &str) -> Result<RouletteResult, String> | |||
| 128 | return Err(format!("Invalid roulette color: {color}")); | 128 | return Err(format!("Invalid roulette color: {color}")); |
| 129 | } | 129 | } |
| 130 | let bets = parts[7].to_string(); | 130 | let bets = parts[7].to_string(); |
| 131 | let _: crate::roulette::Bets = serde_json::from_str(&bets) | 131 | let _: crate::roulette::Bets = |
| 132 | .map_err(|error| format!("Invalid roulette bets: {error}"))?; | 132 | serde_json::from_str(&bets).map_err(|error| format!("Invalid roulette bets: {error}"))?; |
| 133 | 133 | ||
| 134 | Ok(RouletteResult { | 134 | Ok(RouletteResult { |
| 135 | session_id: parts[1].to_string(), | 135 | session_id: parts[1].to_string(), |
diff --git a/src/client/games/roulette.rs b/src/client/games/roulette.rs index 3379317..e80eca8 100644 --- a/src/client/games/roulette.rs +++ b/src/client/games/roulette.rs | |||
| @@ -53,11 +53,7 @@ impl RouletteGame { | |||
| 53 | 53 | ||
| 54 | match state.data.get("status").map(String::as_str) { | 54 | match state.data.get("status").map(String::as_str) { |
| 55 | Some("result") => { | 55 | Some("result") => { |
| 56 | let number = state | 56 | let number = state.data.get("number").map(String::as_str).unwrap_or("?"); |
| 57 | .data | ||
| 58 | .get("number") | ||
| 59 | .map(String::as_str) | ||
| 60 | .unwrap_or("?"); | ||
| 61 | let color = state | 57 | let color = state |
| 62 | .data | 58 | .data |
| 63 | .get("color") | 59 | .get("color") |
| @@ -102,11 +98,7 @@ impl Game for RouletteGame { | |||
| 102 | fn render(&self, state: &GameState) -> String { | 98 | fn render(&self, state: &GameState) -> String { |
| 103 | let bets = Self::bets(state); | 99 | let bets = Self::bets(state); |
| 104 | let chip = state.data.get("chip").map(String::as_str).unwrap_or("10"); | 100 | let chip = state.data.get("chip").map(String::as_str).unwrap_or("10"); |
| 105 | let balance = state | 101 | let balance = state.data.get("balance").map(String::as_str).unwrap_or("?"); |
| 106 | .data | ||
| 107 | .get("balance") | ||
| 108 | .map(String::as_str) | ||
| 109 | .unwrap_or("?"); | ||
| 110 | let input = state | 102 | let input = state |
| 111 | .data | 103 | .data |
| 112 | .get("number_input") | 104 | .get("number_input") |
diff --git a/src/client/games/slots.rs b/src/client/games/slots.rs index d34bf49..2ec354e 100644 --- a/src/client/games/slots.rs +++ b/src/client/games/slots.rs | |||
| @@ -109,11 +109,7 @@ impl Game for SlotsGame { | |||
| 109 | 109 | ||
| 110 | fn render(&self, state: &GameState) -> String { | 110 | fn render(&self, state: &GameState) -> String { |
| 111 | let reels = state.data.get("reels").cloned().unwrap_or_default(); | 111 | let reels = state.data.get("reels").cloned().unwrap_or_default(); |
| 112 | let balance = state | 112 | let balance = state.data.get("balance").map(String::as_str).unwrap_or("?"); |
| 113 | .data | ||
| 114 | .get("balance") | ||
| 115 | .map(String::as_str) | ||
| 116 | .unwrap_or("?"); | ||
| 117 | let winning = Self::highlighted_cells(state); | 113 | let winning = Self::highlighted_cells(state); |
| 118 | let grid = self.render_grid(&reels, &winning); | 114 | let grid = self.render_grid(&reels, &winning); |
| 119 | let paytable = paytable_lines(); | 115 | let paytable = paytable_lines(); |
diff --git a/src/client/ui.rs b/src/client/ui.rs index 0fe78ae..4ab0759 100644 --- a/src/client/ui.rs +++ b/src/client/ui.rs | |||
| @@ -94,10 +94,7 @@ pub fn command_names() -> Vec<&'static str> { | |||
| 94 | pub fn help_lines() -> Vec<String> { | 94 | pub fn help_lines() -> Vec<String> { |
| 95 | let mut lines = vec!["Available commands:".to_string()]; | 95 | let mut lines = vec!["Available commands:".to_string()]; |
| 96 | for command in COMMANDS { | 96 | for command in COMMANDS { |
| 97 | lines.push(format!( | 97 | lines.push(format!(" {:<28} - {}", command.usage, command.description)); |
| 98 | " {:<28} - {}", | ||
| 99 | command.usage, command.description | ||
| 100 | )); | ||
| 101 | } | 98 | } |
| 102 | lines | 99 | lines |
| 103 | } | 100 | } |
| @@ -291,16 +288,12 @@ pub fn host_lines(hosts: &[(String, Option<String>)]) -> Vec<String> { | |||
| 291 | /// on the current line. `render()` has no trailing newline, so a 7-line board | 288 | /// on the current line. `render()` has no trailing newline, so a 7-line board |
| 292 | /// needs `MoveUp(6)`. | 289 | /// needs `MoveUp(6)`. |
| 293 | pub fn lines_to_move_up(previous_lines: usize) -> u16 { | 290 | pub fn lines_to_move_up(previous_lines: usize) -> u16 { |
| 294 | previous_lines | 291 | previous_lines.saturating_sub(1).min(u16::MAX as usize) as u16 |
| 295 | .saturating_sub(1) | ||
| 296 | .min(u16::MAX as usize) as u16 | ||
| 297 | } | 292 | } |
| 298 | 293 | ||
| 299 | /// Column (0-based) to move to after the given prompt and input text. | 294 | /// Column (0-based) to move to after the given prompt and input text. |
| 300 | pub fn input_cursor_column(prompt_len: usize, input_len: usize) -> u16 { | 295 | pub fn input_cursor_column(prompt_len: usize, input_len: usize) -> u16 { |
| 301 | prompt_len | 296 | prompt_len.saturating_add(input_len).min(u16::MAX as usize) as u16 |
| 302 | .saturating_add(input_len) | ||
| 303 | .min(u16::MAX as usize) as u16 | ||
| 304 | } | 297 | } |
| 305 | 298 | ||
| 306 | /// Number of terminal rows a block of logical lines occupies after wrapping. | 299 | /// Number of terminal rows a block of logical lines occupies after wrapping. |
diff --git a/src/crash.rs b/src/crash.rs index 55aab19..2936b99 100644 --- a/src/crash.rs +++ b/src/crash.rs | |||
| @@ -24,9 +24,7 @@ pub fn multiplier_at_secs(seconds: f64) -> f64 { | |||
| 24 | if !seconds.is_finite() || seconds <= 0.0 { | 24 | if !seconds.is_finite() || seconds <= 0.0 { |
| 25 | return 1.0; | 25 | return 1.0; |
| 26 | } | 26 | } |
| 27 | (GROWTH_RATE * seconds) | 27 | (GROWTH_RATE * seconds).exp().clamp(1.0, MAX_MULTIPLIER) |
| 28 | .exp() | ||
| 29 | .clamp(1.0, MAX_MULTIPLIER) | ||
| 30 | } | 28 | } |
| 31 | 29 | ||
| 32 | /// How long a round runs before reaching `multiplier`. | 30 | /// How long a round runs before reaching `multiplier`. |
diff --git a/src/roulette.rs b/src/roulette.rs index b59f92e..426ecc3 100644 --- a/src/roulette.rs +++ b/src/roulette.rs | |||
| @@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize}; | |||
| 4 | 4 | ||
| 5 | /// European wheel clockwise from zero. | 5 | /// European wheel clockwise from zero. |
| 6 | pub const WHEEL: [u8; 37] = [ | 6 | pub const WHEEL: [u8; 37] = [ |
| 7 | 0, 32, 15, 19, 4, 21, 2, 25, 17, 34, 6, 27, 13, 36, 11, 30, 8, 23, 10, 5, | 7 | 0, 32, 15, 19, 4, 21, 2, 25, 17, 34, 6, 27, 13, 36, 11, 30, 8, 23, 10, 5, 24, 16, 33, 1, 20, |
| 8 | 24, 16, 33, 1, 20, 14, 31, 9, 22, 18, 29, 7, 28, 12, 35, 3, 26, | 8 | 14, 31, 9, 22, 18, 29, 7, 28, 12, 35, 3, 26, |
| 9 | ]; | 9 | ]; |
| 10 | 10 | ||
| 11 | pub const RED_NUMBERS: [u8; 18] = [ | 11 | pub const RED_NUMBERS: [u8; 18] = [ |
| @@ -92,8 +92,7 @@ impl Bets { | |||
| 92 | if is_red(number) { | 92 | if is_red(number) { |
| 93 | winnings = winnings.saturating_add(self.red.saturating_mul(EVEN_MONEY_RETURN)); | 93 | winnings = winnings.saturating_add(self.red.saturating_mul(EVEN_MONEY_RETURN)); |
| 94 | } else { | 94 | } else { |
| 95 | winnings = | 95 | winnings = winnings.saturating_add(self.black.saturating_mul(EVEN_MONEY_RETURN)); |
| 96 | winnings.saturating_add(self.black.saturating_mul(EVEN_MONEY_RETURN)); | ||
| 97 | } | 96 | } |
| 98 | if is_even(number) { | 97 | if is_even(number) { |
| 99 | winnings = winnings.saturating_add(self.even.saturating_mul(EVEN_MONEY_RETURN)); | 98 | winnings = winnings.saturating_add(self.even.saturating_mul(EVEN_MONEY_RETURN)); |
| @@ -104,8 +103,7 @@ impl Bets { | |||
| 104 | 103 | ||
| 105 | for bet in &self.numbers { | 104 | for bet in &self.numbers { |
| 106 | if bet.number == number { | 105 | if bet.number == number { |
| 107 | winnings = winnings | 106 | winnings = winnings.saturating_add(bet.amount.saturating_mul(STRAIGHT_RETURN)); |
| 108 | .saturating_add(bet.amount.saturating_mul(STRAIGHT_RETURN)); | ||
| 109 | } | 107 | } |
| 110 | } | 108 | } |
| 111 | 109 | ||
diff --git a/src/server/core.rs b/src/server/core.rs index e0f0ff2..4b68ccb 100644 --- a/src/server/core.rs +++ b/src/server/core.rs | |||
| @@ -49,11 +49,7 @@ impl ServerCore { | |||
| 49 | Self::with_registry(conn, GameRegistry::with_defaults(), DEFAULT_COST) | 49 | Self::with_registry(conn, GameRegistry::with_defaults(), DEFAULT_COST) |
| 50 | } | 50 | } |
| 51 | 51 | ||
| 52 | pub fn with_registry( | 52 | pub fn with_registry(conn: Connection, registry: GameRegistry, bcrypt_cost: u32) -> Self { |
| 53 | conn: Connection, | ||
| 54 | registry: GameRegistry, | ||
| 55 | bcrypt_cost: u32, | ||
| 56 | ) -> Self { | ||
| 57 | Self { | 53 | Self { |
| 58 | conn, | 54 | conn, |
| 59 | sessions: SessionManager::new(), | 55 | sessions: SessionManager::new(), |
| @@ -115,8 +111,7 @@ impl ServerCore { | |||
| 115 | } | 111 | } |
| 116 | "GAME_INFO" => { | 112 | "GAME_INFO" => { |
| 117 | if parts.len() != 2 { | 113 | if parts.len() != 2 { |
| 118 | "ERROR Invalid GAME_INFO format. Usage: GAME_INFO <game_type>" | 114 | "ERROR Invalid GAME_INFO format. Usage: GAME_INFO <game_type>".to_string() |
| 119 | .to_string() | ||
| 120 | } else { | 115 | } else { |
| 121 | match self.registry.get_game(parts[1]) { | 116 | match self.registry.get_game(parts[1]) { |
| 122 | Some(game) => format!( | 117 | Some(game) => format!( |
| @@ -143,16 +138,14 @@ impl ServerCore { | |||
| 143 | } | 138 | } |
| 144 | "CRASH_CASHOUT" => { | 139 | "CRASH_CASHOUT" => { |
| 145 | if parts.len() != 2 { | 140 | if parts.len() != 2 { |
| 146 | "ERROR Invalid CRASH_CASHOUT format. Usage: CRASH_CASHOUT <session>" | 141 | "ERROR Invalid CRASH_CASHOUT format. Usage: CRASH_CASHOUT <session>".to_string() |
| 147 | .to_string() | ||
| 148 | } else { | 142 | } else { |
| 149 | self.handle_crash_end(state, parts[1], false) | 143 | self.handle_crash_end(state, parts[1], false) |
| 150 | } | 144 | } |
| 151 | } | 145 | } |
| 152 | "CRASH_FORFEIT" => { | 146 | "CRASH_FORFEIT" => { |
| 153 | if parts.len() != 2 { | 147 | if parts.len() != 2 { |
| 154 | "ERROR Invalid CRASH_FORFEIT format. Usage: CRASH_FORFEIT <session>" | 148 | "ERROR Invalid CRASH_FORFEIT format. Usage: CRASH_FORFEIT <session>".to_string() |
| 155 | .to_string() | ||
| 156 | } else { | 149 | } else { |
| 157 | self.handle_crash_end(state, parts[1], true) | 150 | self.handle_crash_end(state, parts[1], true) |
| 158 | } | 151 | } |
| @@ -351,12 +344,8 @@ impl ServerCore { | |||
| 351 | if hand.player.len() != 2 { | 344 | if hand.player.len() != 2 { |
| 352 | return "ERROR Double is only allowed on the first two cards".to_string(); | 345 | return "ERROR Double is only allowed on the first two cards".to_string(); |
| 353 | } | 346 | } |
| 354 | if database::extend_reservation( | 347 | if database::extend_reservation(&mut self.conn, session_id, hand.base_bet as i64) |
| 355 | &mut self.conn, | 348 | .is_err() |
| 356 | session_id, | ||
| 357 | hand.base_bet as i64, | ||
| 358 | ) | ||
| 359 | .is_err() | ||
| 360 | { | 349 | { |
| 361 | return "ERROR Insufficient balance".to_string(); | 350 | return "ERROR Insufficient balance".to_string(); |
| 362 | } | 351 | } |
| @@ -515,7 +504,12 @@ impl ServerCore { | |||
| 515 | 504 | ||
| 516 | /// Cash out or forfeit. The multiplier comes from the server clock at the | 505 | /// Cash out or forfeit. The multiplier comes from the server clock at the |
| 517 | /// moment the request is handled; the client never supplies one. | 506 | /// moment the request is handled; the client never supplies one. |
| 518 | fn handle_crash_end(&mut self, state: &ConnectionState, session_id: &str, forfeit: bool) -> String { | 507 | fn handle_crash_end( |
| 508 | &mut self, | ||
| 509 | state: &ConnectionState, | ||
| 510 | session_id: &str, | ||
| 511 | forfeit: bool, | ||
| 512 | ) -> String { | ||
| 519 | let Some(user) = state.user.as_deref() else { | 513 | let Some(user) = state.user.as_deref() else { |
| 520 | return "ERROR Not logged in".to_string(); | 514 | return "ERROR Not logged in".to_string(); |
| 521 | }; | 515 | }; |
| @@ -698,11 +692,7 @@ impl ServerCore { | |||
| 698 | .get("number") | 692 | .get("number") |
| 699 | .cloned() | 693 | .cloned() |
| 700 | .unwrap_or_default(); | 694 | .unwrap_or_default(); |
| 701 | let color = result | 695 | let color = result.result_data.get("color").cloned().unwrap_or_default(); |
| 702 | .result_data | ||
| 703 | .get("color") | ||
| 704 | .cloned() | ||
| 705 | .unwrap_or_default(); | ||
| 706 | let bets = result | 696 | let bets = result |
| 707 | .result_data | 697 | .result_data |
| 708 | .get("bets") | 698 | .get("bets") |
| @@ -712,11 +702,7 @@ impl ServerCore { | |||
| 712 | "ROULETTE_RESULT {session_id} {winnings} {bet_amount} {new_balance} {number} {color} {bets}" | 702 | "ROULETTE_RESULT {session_id} {winnings} {bet_amount} {new_balance} {number} {color} {bets}" |
| 713 | ) | 703 | ) |
| 714 | } else { | 704 | } else { |
| 715 | let reels = result | 705 | let reels = result.result_data.get("reels").cloned().unwrap_or_default(); |
| 716 | .result_data | ||
| 717 | .get("reels") | ||
| 718 | .cloned() | ||
| 719 | .unwrap_or_default(); | ||
| 720 | let win_type = result | 706 | let win_type = result |
| 721 | .result_data | 707 | .result_data |
| 722 | .get("win_type") | 708 | .get("win_type") |
| @@ -753,8 +739,7 @@ impl ServerCore { | |||
| 753 | if let Ok(mut hand) = BlackjackHand::from_json(raw) { | 739 | if let Ok(mut hand) = BlackjackHand::from_json(raw) { |
| 754 | if !hand.is_done() { | 740 | if !hand.is_done() { |
| 755 | hand.forfeit(); | 741 | hand.forfeit(); |
| 756 | let balance = | 742 | let balance = database::get_balance(&self.conn, user).unwrap_or(0); |
| 757 | database::get_balance(&self.conn, user).unwrap_or(0); | ||
| 758 | let final_state = hand.state(&session.session_id, balance); | 743 | let final_state = hand.state(&session.session_id, balance); |
| 759 | let details = serde_json::to_string(&final_state) | 744 | let details = serde_json::to_string(&final_state) |
| 760 | .unwrap_or_else(|_| "{}".to_string()); | 745 | .unwrap_or_else(|_| "{}".to_string()); |
diff --git a/src/server/games/blackjack.rs b/src/server/games/blackjack.rs index df5a6ac..cea817a 100644 --- a/src/server/games/blackjack.rs +++ b/src/server/games/blackjack.rs | |||
| @@ -1,7 +1,5 @@ | |||
| 1 | use super::traits::{Game, GameError, GamePhase, GameResult, GameState}; | 1 | use super::traits::{Game, GameError, GamePhase, GameResult, GameState}; |
| 2 | use crate::blackjack::{ | 2 | use crate::blackjack::{card_value, fresh_shoe, hand_total, is_blackjack, BlackjackState}; |
| 3 | card_value, fresh_shoe, hand_total, is_blackjack, BlackjackState, | ||
| 4 | }; | ||
| 5 | use rand::seq::SliceRandom; | 3 | use rand::seq::SliceRandom; |
| 6 | use rand::thread_rng; | 4 | use rand::thread_rng; |
| 7 | use serde::{Deserialize, Serialize}; | 5 | use serde::{Deserialize, Serialize}; |
diff --git a/src/server/games/roulette.rs b/src/server/games/roulette.rs index 7beb8c9..0a58323 100644 --- a/src/server/games/roulette.rs +++ b/src/server/games/roulette.rs | |||
| @@ -65,17 +65,15 @@ impl Game for RouletteGame { | |||
| 65 | 65 | ||
| 66 | match action { | 66 | match action { |
| 67 | "spin" => { | 67 | "spin" => { |
| 68 | if state.phase != GamePhase::WaitingToStart | 68 | if state.phase != GamePhase::WaitingToStart && state.phase != GamePhase::Playing { |
| 69 | && state.phase != GamePhase::Playing | ||
| 70 | { | ||
| 71 | return Err(GameError::InvalidAction( | 69 | return Err(GameError::InvalidAction( |
| 72 | "Cannot spin in current phase".to_string(), | 70 | "Cannot spin in current phase".to_string(), |
| 73 | )); | 71 | )); |
| 74 | } | 72 | } |
| 75 | 73 | ||
| 76 | let payload = params | 74 | let payload = params.first().ok_or(GameError::InvalidAction( |
| 77 | .first() | 75 | "Missing roulette bets".to_string(), |
| 78 | .ok_or(GameError::InvalidAction("Missing roulette bets".to_string()))?; | 76 | ))?; |
| 79 | let bets: Bets = serde_json::from_str(payload) | 77 | let bets: Bets = serde_json::from_str(payload) |
| 80 | .map_err(|error| GameError::InvalidAction(format!("Invalid bets: {error}")))?; | 78 | .map_err(|error| GameError::InvalidAction(format!("Invalid bets: {error}")))?; |
| 81 | 79 | ||
diff --git a/src/server/games/slots.rs b/src/server/games/slots.rs index 298cd4c..caced24 100644 --- a/src/server/games/slots.rs +++ b/src/server/games/slots.rs | |||
| @@ -175,9 +175,7 @@ impl Game for SlotsGame { | |||
| 175 | 175 | ||
| 176 | match action { | 176 | match action { |
| 177 | "spin" => { | 177 | "spin" => { |
| 178 | if state.phase != GamePhase::WaitingToStart | 178 | if state.phase != GamePhase::WaitingToStart && state.phase != GamePhase::Playing { |
| 179 | && state.phase != GamePhase::Playing | ||
| 180 | { | ||
| 181 | return Err(GameError::InvalidAction( | 179 | return Err(GameError::InvalidAction( |
| 182 | "Cannot spin in current phase".to_string(), | 180 | "Cannot spin in current phase".to_string(), |
| 183 | )); | 181 | )); |
diff --git a/src/server/setup.rs b/src/server/setup.rs index 96982c5..023e6ed 100644 --- a/src/server/setup.rs +++ b/src/server/setup.rs | |||
| @@ -47,19 +47,8 @@ pub fn init_identity(dir: &Path, common_name: &str) -> Result<SetupReport, Strin | |||
| 47 | run_openssl( | 47 | run_openssl( |
| 48 | dir, | 48 | dir, |
| 49 | &[ | 49 | &[ |
| 50 | "req", | 50 | "req", "-x509", "-newkey", "rsa:2048", "-keyout", "key.pem", "-out", "cert.pem", |
| 51 | "-x509", | 51 | "-days", "3650", "-nodes", "-subj", &subject, |
| 52 | "-newkey", | ||
| 53 | "rsa:2048", | ||
| 54 | "-keyout", | ||
| 55 | "key.pem", | ||
| 56 | "-out", | ||
| 57 | "cert.pem", | ||
| 58 | "-days", | ||
| 59 | "3650", | ||
| 60 | "-nodes", | ||
| 61 | "-subj", | ||
| 62 | &subject, | ||
| 63 | ], | 52 | ], |
| 64 | "generate the certificate", | 53 | "generate the certificate", |
| 65 | &manual, | 54 | &manual, |
| @@ -98,9 +87,8 @@ pub fn init_identity(dir: &Path, common_name: &str) -> Result<SetupReport, Strin | |||
| 98 | .map_err(|error| format!("Could not inspect {}: {error}", path.display()))?; | 87 | .map_err(|error| format!("Could not inspect {}: {error}", path.display()))?; |
| 99 | let mut permissions = metadata.permissions(); | 88 | let mut permissions = metadata.permissions(); |
| 100 | permissions.set_mode(0o600); | 89 | permissions.set_mode(0o600); |
| 101 | std::fs::set_permissions(path, permissions).map_err(|error| { | 90 | std::fs::set_permissions(path, permissions) |
| 102 | format!("Could not protect {}: {error}", path.display()) | 91 | .map_err(|error| format!("Could not protect {}: {error}", path.display()))?; |
| 103 | })?; | ||
| 104 | } | 92 | } |
| 105 | } | 93 | } |
| 106 | 94 | ||
diff --git a/src/slots.rs b/src/slots.rs index 9e41f05..443444e 100644 --- a/src/slots.rs +++ b/src/slots.rs | |||
| @@ -16,14 +16,8 @@ pub const PAYLINES: [[usize; REELS]; 5] = [ | |||
| 16 | pub const LINE_NAMES: [&str; 5] = ["top", "middle", "bottom", "V", "inverted V"]; | 16 | pub const LINE_NAMES: [&str; 5] = ["top", "middle", "bottom", "V", "inverted V"]; |
| 17 | 17 | ||
| 18 | /// Symbol value multipliers. | 18 | /// Symbol value multipliers. |
| 19 | pub const SYMBOL_MULTIPLIERS: [(char, u64); 6] = [ | 19 | pub const SYMBOL_MULTIPLIERS: [(char, u64); 6] = |
| 20 | ('C', 1), | 20 | [('C', 1), ('L', 2), ('O', 3), ('G', 4), ('D', 6), ('S', 10)]; |
| 21 | ('L', 2), | ||
| 22 | ('O', 3), | ||
| 23 | ('G', 4), | ||
| 24 | ('D', 6), | ||
| 25 | ('S', 10), | ||
| 26 | ]; | ||
| 27 | 21 | ||
| 28 | /// (match length, shape multiplier): 3, 4 or 5 of a kind. | 22 | /// (match length, shape multiplier): 3, 4 or 5 of a kind. |
| 29 | pub const SHAPE_MULTIPLIERS: [(usize, u64); 3] = [(3, 1), (4, 3), (5, 14)]; | 23 | pub const SHAPE_MULTIPLIERS: [(usize, u64); 3] = [(3, 1), (4, 3), (5, 14)]; |
diff --git a/src/tofu.rs b/src/tofu.rs index 6308813..15f38d1 100644 --- a/src/tofu.rs +++ b/src/tofu.rs | |||
| @@ -17,10 +17,7 @@ pub enum TrustDecision { | |||
| 17 | /// Stored fingerprint matches: proceed. | 17 | /// Stored fingerprint matches: proceed. |
| 18 | Trusted, | 18 | Trusted, |
| 19 | /// Key changed since first use: reject with details. | 19 | /// Key changed since first use: reject with details. |
| 20 | Mismatch { | 20 | Mismatch { expected: String, actual: String }, |
| 21 | expected: String, | ||
| 22 | actual: String, | ||
| 23 | }, | ||
| 24 | } | 21 | } |
| 25 | 22 | ||
| 26 | /// Hex-encoded SHA-256 of the SPKI inside a DER-encoded X.509 certificate. | 23 | /// Hex-encoded SHA-256 of the SPKI inside a DER-encoded X.509 certificate. |
