//! Pure presentation maths. Anything that touches a `Ui` is covered by the //! manual screenshot pass; these are the calculations a pixel test cannot //! express. use std::f32::consts::TAU; use eframe::egui::{Pos2, Rect, pos2, vec2}; use zagan_bet::client::view::{SlotsPhase, SlotsView}; use zagan_bet::roulette::{BetTarget, Outside}; use zagan_bet_gui::roulette::{Felt, number_under, size_for, target_name}; use zagan_bet_gui::slots::winning_columns; use zagan_bet_gui::theme::{group_digits, money}; #[test] fn money_is_grouped_in_threes() { assert_eq!(group_digits(0), "0"); assert_eq!(group_digits(999), "999"); assert_eq!(group_digits(1000), "1 000"); assert_eq!(group_digits(1234567), "1 234 567"); assert_eq!(group_digits(-1500), "-1 500"); assert_eq!(money(1000), "1 000 Kr"); } #[test] fn the_wheel_pointer_reads_the_number_at_the_top() { let segment = TAU / 37.0; assert_eq!(number_under(0.0), 0); // Rotating by -i segments puts wheel entry i under the pointer. for number in [0u8, 32, 17, 26] { let index = zagan_bet::roulette::WHEEL .iter() .position(|candidate| *candidate == number) .expect("number is on the wheel"); let rotation = -(index as f32) * segment; assert_eq!(number_under(rotation), number); } // One full turn changes nothing. assert_eq!(number_under(-TAU), 0); } #[test] fn table_size_keeps_the_felt_aspect_and_fits() { let available = eframe::egui::vec2(1300.0, 900.0); let size = size_for(available); assert!(size.x <= available.x + 0.01, "{size:?} overflows"); assert!(size.y <= available.y + 0.01, "{size:?} overflows"); // 13 cells wide and 4.89 cells tall, plus the felt border on every side. let cell = (size.x - 12.0) / 13.0; assert!((cell - (size.y - 12.0) / 4.89).abs() < 0.01); } fn felt() -> Felt { Felt::new(Rect::from_min_size( Pos2::ZERO, size_for(vec2(1300.0, 900.0)), )) } #[test] fn the_pointer_snaps_to_straights_splits_and_corners() { let felt = felt(); let cell = felt.cell_size(); let center = felt.number_rect(17).center(); // The inner half of a cell is a straight, even slightly off centre. assert_eq!( felt.target_at(center + vec2(cell * 0.2, 0.0)), Some(BetTarget::Straight { number: 17 }) ); // A quarter cell from the centre is the line: right of 17 is 20, above is 18. assert_eq!( felt.target_at(center + vec2(cell * 0.3, 0.0)), Some(BetTarget::Split { a: 17, b: 20 }) ); assert_eq!( felt.target_at(center + vec2(0.0, -cell * 0.3)), Some(BetTarget::Split { a: 17, b: 18 }) ); // Near both lines at once is the corner of the four cells. assert_eq!( felt.target_at(center + vec2(cell * 0.3, -cell * 0.3)), Some(BetTarget::Corner { numbers: [17, 18, 20, 21] }) ); // Zero is a straight and nothing else. assert_eq!( felt.target_at(felt.number_rect(0).center()), Some(BetTarget::Straight { number: 0 }) ); } #[test] fn the_pointer_finds_the_wide_bets_below_the_grid() { let felt = felt(); let cell = felt.cell_size(); let x = felt.number_rect(17).center().x; let grid_bottom = felt.number_rect(16).bottom(); // 17 sits in the second dozen (13-24). assert_eq!( felt.target_at(pos2(x, grid_bottom + cell * 0.3)), Some(BetTarget::Dozen { index: 1 }) ); // Below the dozens is the column row; 17 is in column 2 (2,5,...,17). assert_eq!( felt.target_at(pos2(x, grid_bottom + cell * (0.72 + 0.3))), Some(BetTarget::Column { index: 1 }) ); // And below that, the outside row: 17's column lands on RED. assert_eq!( felt.target_at(pos2(x, grid_bottom + cell * (0.72 + 0.55 + 0.3))), Some(BetTarget::Outside { kind: Outside::Red }) ); // Outside the board is not a bet. assert_eq!(felt.target_at(pos2(-1.0, -1.0)), None); } #[test] fn targets_read_as_bet_slips() { assert_eq!( target_name(&BetTarget::Straight { number: 17 }), "Straight 17" ); assert_eq!( target_name(&BetTarget::Split { a: 26, b: 29 }), "Split 26/29" ); assert_eq!( target_name(&BetTarget::Corner { numbers: [17, 18, 20, 21] }), "Corner 17/18/20/21" ); assert_eq!(target_name(&BetTarget::Dozen { index: 1 }), "2nd 12"); assert_eq!(target_name(&BetTarget::Column { index: 2 }), "Column 3"); assert_eq!( target_name(&BetTarget::Outside { kind: Outside::High }), "19-36" ); assert_eq!( target_name(&BetTarget::Outside { kind: Outside::Black }), "Black" ); } fn slots_view(win_cells: Vec, win_lines: Vec) -> SlotsView { SlotsView { bet: 10, balance: Some(1000), reels: "OOOOODDDDDOOOOOD".chars().collect(), win_cells, win_lines, win_type: "win".to_string(), winnings: Some(30), phase: SlotsPhase::Result, error: None, } } #[test] fn a_payline_stops_at_the_last_matching_reel() { // Middle line (line 2), four Os from reel 0: cells 1, 4, 7, 10. let view = slots_view(vec![1, 4, 7, 10], vec![2]); assert_eq!(winning_columns(&view, 2), vec![0, 1, 2, 3]); // Three of a kind stops after reel 2. let view = slots_view(vec![1, 4, 7], vec![2]); assert_eq!(winning_columns(&view, 2), vec![0, 1, 2]); // Cells on another line do not extend this one. let view = slots_view(vec![0, 1, 4, 7], vec![2]); assert_eq!(winning_columns(&view, 2), vec![0, 1, 2]); // A line number that does not exist draws nothing. let view = slots_view(vec![1, 4, 7], vec![9]); assert!(winning_columns(&view, 9).is_empty()); }