fix(engine,server): safe area clamp, analytics batch, achievement save order, daily rollover, replay validation, leaderboard opt-in (#56, #60, #61, #62, #66, #68)
Build and Deploy / build-and-push (push) Successful in 3m54s
Build and Deploy / build-and-push (push) Successful in 3m54s
- #66: Clamp safe-area insets to 25% of window height with warn!() on excess - #68: Move fire_flush outside per-event loop in analytics (batch flush once) - #56: Persist progress before marking reward_granted to prevent XP loss on crash - #60: Add DateRolloverTimer + check_date_rollover system for midnight seed refresh - #62: Add validate_header() in replay upload with mode/draw_mode allowlists - #61: Restore two-query leaderboard opt-in check (SELECT then UPDATE); original queries already in .sqlx cache; EXISTS variant would require sqlx prepare Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -30,7 +30,9 @@ fn suit_color(suit: u8) -> [u8; 4] {
|
||||
}
|
||||
|
||||
fn rank_str(rank: u8) -> &'static str {
|
||||
["A","2","3","4","5","6","7","8","9","10","J","Q","K"][rank as usize]
|
||||
[
|
||||
"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K",
|
||||
][rank as usize]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -86,13 +88,15 @@ impl Canvas {
|
||||
}
|
||||
|
||||
fn set(&mut self, x: i32, y: i32, c: [u8; 4]) {
|
||||
if x < 0 || y < 0 || x >= W as i32 || y >= H as i32 { return; }
|
||||
if x < 0 || y < 0 || x >= W as i32 || y >= H as i32 {
|
||||
return;
|
||||
}
|
||||
let i = (y as u32 * W + x as u32) as usize * 4;
|
||||
let a = c[3] as f32 / 255.0;
|
||||
if a >= 0.99 {
|
||||
self.data[i..i + 4].copy_from_slice(&c);
|
||||
} else if a > 0.01 {
|
||||
self.data[i] = (self.data[i] as f32 * (1.0 - a) + c[0] as f32 * a) as u8;
|
||||
self.data[i] = (self.data[i] as f32 * (1.0 - a) + c[0] as f32 * a) as u8;
|
||||
self.data[i + 1] = (self.data[i + 1] as f32 * (1.0 - a) + c[1] as f32 * a) as u8;
|
||||
self.data[i + 2] = (self.data[i + 2] as f32 * (1.0 - a) + c[2] as f32 * a) as u8;
|
||||
self.data[i + 3] = 255;
|
||||
@@ -172,27 +176,36 @@ fn draw_heart(cv: &mut Canvas, cx: f32, cy: f32, sz: f32, c: [u8; 4]) {
|
||||
let oy = cy - sz * 0.04;
|
||||
cv.circle(cx - sz * 0.22, oy, r, c);
|
||||
cv.circle(cx + sz * 0.22, oy, r, c);
|
||||
cv.triangle([
|
||||
(cx - sz * 0.52, oy + r * 0.4),
|
||||
(cx + sz * 0.52, oy + r * 0.4),
|
||||
(cx, cy + sz * 0.52),
|
||||
], c);
|
||||
cv.triangle(
|
||||
[
|
||||
(cx - sz * 0.52, oy + r * 0.4),
|
||||
(cx + sz * 0.52, oy + r * 0.4),
|
||||
(cx, cy + sz * 0.52),
|
||||
],
|
||||
c,
|
||||
);
|
||||
}
|
||||
|
||||
fn draw_spade(cv: &mut Canvas, cx: f32, cy: f32, sz: f32, c: [u8; 4]) {
|
||||
cv.triangle([
|
||||
(cx, cy - sz * 0.52),
|
||||
(cx - sz * 0.52, cy + sz * 0.1),
|
||||
(cx + sz * 0.52, cy + sz * 0.1),
|
||||
], c);
|
||||
cv.triangle(
|
||||
[
|
||||
(cx, cy - sz * 0.52),
|
||||
(cx - sz * 0.52, cy + sz * 0.1),
|
||||
(cx + sz * 0.52, cy + sz * 0.1),
|
||||
],
|
||||
c,
|
||||
);
|
||||
cv.circle(cx - sz * 0.22, cy + sz * 0.06, sz * 0.3, c);
|
||||
cv.circle(cx + sz * 0.22, cy + sz * 0.06, sz * 0.3, c);
|
||||
// stem + base
|
||||
cv.triangle([
|
||||
(cx, cy + sz * 0.12),
|
||||
(cx - sz * 0.13, cy + sz * 0.5),
|
||||
(cx + sz * 0.13, cy + sz * 0.5),
|
||||
], c);
|
||||
cv.triangle(
|
||||
[
|
||||
(cx, cy + sz * 0.12),
|
||||
(cx - sz * 0.13, cy + sz * 0.5),
|
||||
(cx + sz * 0.13, cy + sz * 0.5),
|
||||
],
|
||||
c,
|
||||
);
|
||||
cv.fill_rect(
|
||||
(cx - sz * 0.26) as i32,
|
||||
(cy + sz * 0.43) as i32,
|
||||
@@ -231,7 +244,15 @@ fn draw_club(cv: &mut Canvas, cx: f32, cy: f32, sz: f32, c: [u8; 4]) {
|
||||
// Text rendering via ab_glyph
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn draw_text(cv: &mut Canvas, font: &FontRef<'_>, text: &str, px: f32, left: f32, top: f32, c: [u8; 4]) {
|
||||
fn draw_text(
|
||||
cv: &mut Canvas,
|
||||
font: &FontRef<'_>,
|
||||
text: &str,
|
||||
px: f32,
|
||||
left: f32,
|
||||
top: f32,
|
||||
c: [u8; 4],
|
||||
) {
|
||||
let scale = PxScale::from(px);
|
||||
let baseline = top + font.as_scaled(scale).ascent();
|
||||
let mut x = left;
|
||||
@@ -278,12 +299,63 @@ fn pip_positions(rank: u8) -> &'static [(f32, f32)] {
|
||||
1 => &[(0.5, 0.2), (0.5, 0.8)],
|
||||
2 => &[(0.5, 0.12), (0.5, 0.5), (0.5, 0.88)],
|
||||
3 => &[(0.25, 0.18), (0.75, 0.18), (0.25, 0.82), (0.75, 0.82)],
|
||||
4 => &[(0.25, 0.18), (0.75, 0.18), (0.5, 0.5), (0.25, 0.82), (0.75, 0.82)],
|
||||
5 => &[(0.25, 0.12), (0.75, 0.12), (0.25, 0.5), (0.75, 0.5), (0.25, 0.88), (0.75, 0.88)],
|
||||
6 => &[(0.25, 0.1), (0.75, 0.1), (0.5, 0.31), (0.25, 0.5), (0.75, 0.5), (0.25, 0.9), (0.75, 0.9)],
|
||||
7 => &[(0.25, 0.1), (0.75, 0.1), (0.5, 0.28), (0.25, 0.48), (0.75, 0.48), (0.5, 0.70), (0.25, 0.9), (0.75, 0.9)],
|
||||
8 => &[(0.25, 0.1), (0.75, 0.1), (0.25, 0.35), (0.75, 0.35), (0.5, 0.5), (0.25, 0.65), (0.75, 0.65), (0.25, 0.9), (0.75, 0.9)],
|
||||
9 => &[(0.25, 0.09), (0.75, 0.09), (0.5, 0.27), (0.25, 0.44), (0.75, 0.44), (0.25, 0.56), (0.75, 0.56), (0.5, 0.73), (0.25, 0.91), (0.75, 0.91)],
|
||||
4 => &[
|
||||
(0.25, 0.18),
|
||||
(0.75, 0.18),
|
||||
(0.5, 0.5),
|
||||
(0.25, 0.82),
|
||||
(0.75, 0.82),
|
||||
],
|
||||
5 => &[
|
||||
(0.25, 0.12),
|
||||
(0.75, 0.12),
|
||||
(0.25, 0.5),
|
||||
(0.75, 0.5),
|
||||
(0.25, 0.88),
|
||||
(0.75, 0.88),
|
||||
],
|
||||
6 => &[
|
||||
(0.25, 0.1),
|
||||
(0.75, 0.1),
|
||||
(0.5, 0.31),
|
||||
(0.25, 0.5),
|
||||
(0.75, 0.5),
|
||||
(0.25, 0.9),
|
||||
(0.75, 0.9),
|
||||
],
|
||||
7 => &[
|
||||
(0.25, 0.1),
|
||||
(0.75, 0.1),
|
||||
(0.5, 0.28),
|
||||
(0.25, 0.48),
|
||||
(0.75, 0.48),
|
||||
(0.5, 0.70),
|
||||
(0.25, 0.9),
|
||||
(0.75, 0.9),
|
||||
],
|
||||
8 => &[
|
||||
(0.25, 0.1),
|
||||
(0.75, 0.1),
|
||||
(0.25, 0.35),
|
||||
(0.75, 0.35),
|
||||
(0.5, 0.5),
|
||||
(0.25, 0.65),
|
||||
(0.75, 0.65),
|
||||
(0.25, 0.9),
|
||||
(0.75, 0.9),
|
||||
],
|
||||
9 => &[
|
||||
(0.25, 0.09),
|
||||
(0.75, 0.09),
|
||||
(0.5, 0.27),
|
||||
(0.25, 0.44),
|
||||
(0.75, 0.44),
|
||||
(0.25, 0.56),
|
||||
(0.75, 0.56),
|
||||
(0.5, 0.73),
|
||||
(0.25, 0.91),
|
||||
(0.75, 0.91),
|
||||
],
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
@@ -327,14 +399,28 @@ fn make_card_face(font: &FontRef<'_>, rank: u8, suit: u8) -> Canvas {
|
||||
let tl_x = 6.0f32;
|
||||
let tl_y = 5.0f32;
|
||||
draw_text(&mut cv, font, rank_s, rank_px, tl_x, tl_y, sc);
|
||||
draw_suit(&mut cv, tl_x + suit_sz * 0.62, tl_y + rh + 2.0 + suit_sz * 0.75, suit_sz, suit, sc);
|
||||
draw_suit(
|
||||
&mut cv,
|
||||
tl_x + suit_sz * 0.62,
|
||||
tl_y + rh + 2.0 + suit_sz * 0.75,
|
||||
suit_sz,
|
||||
suit,
|
||||
sc,
|
||||
);
|
||||
|
||||
// Bottom-right corner (right-aligned rank, suit above it)
|
||||
let br_rx = W as f32 - 6.0;
|
||||
let br_by = H as f32 - 5.0;
|
||||
let br_ty = br_by - corner_h;
|
||||
draw_text(&mut cv, font, rank_s, rank_px, br_rx - rw, br_ty, sc);
|
||||
draw_suit(&mut cv, br_rx - suit_sz * 0.62, br_ty + rh + 2.0 + suit_sz * 0.75, suit_sz, suit, sc);
|
||||
draw_suit(
|
||||
&mut cv,
|
||||
br_rx - suit_sz * 0.62,
|
||||
br_ty + rh + 2.0 + suit_sz * 0.75,
|
||||
suit_sz,
|
||||
suit,
|
||||
sc,
|
||||
);
|
||||
|
||||
// Center content
|
||||
if rank >= 10 {
|
||||
@@ -346,7 +432,14 @@ fn make_card_face(font: &FontRef<'_>, rank: u8, suit: u8) -> Canvas {
|
||||
let big_y = H as f32 * 0.28;
|
||||
draw_text(&mut cv, font, rank_s, big_px, big_x, big_y, sc);
|
||||
let sym_sz = 22.0f32;
|
||||
draw_suit(&mut cv, W as f32 * 0.5, big_y + big_h + sym_sz * 1.0, sym_sz, suit, sc);
|
||||
draw_suit(
|
||||
&mut cv,
|
||||
W as f32 * 0.5,
|
||||
big_y + big_h + sym_sz * 1.0,
|
||||
sym_sz,
|
||||
suit,
|
||||
sc,
|
||||
);
|
||||
} else {
|
||||
// Pip cards
|
||||
let pip_sz = if rank == 0 {
|
||||
@@ -375,15 +468,17 @@ fn save_card_png(path: &Path, cv: &Canvas) {
|
||||
}
|
||||
|
||||
fn save_png_wh(path: &Path, data: &[u8], w: u32, h: u32) {
|
||||
let file = File::create(path)
|
||||
.unwrap_or_else(|e| panic!("cannot create {}: {e}", path.display()));
|
||||
let file =
|
||||
File::create(path).unwrap_or_else(|e| panic!("cannot create {}: {e}", path.display()));
|
||||
let mut bw = BufWriter::new(file);
|
||||
let mut enc = png::Encoder::new(&mut bw, w, h);
|
||||
enc.set_color(png::ColorType::Rgba);
|
||||
enc.set_depth(png::BitDepth::Eight);
|
||||
let mut writer = enc.write_header()
|
||||
let mut writer = enc
|
||||
.write_header()
|
||||
.unwrap_or_else(|e| panic!("png header error for {}: {e}", path.display()));
|
||||
writer.write_image_data(data)
|
||||
writer
|
||||
.write_image_data(data)
|
||||
.unwrap_or_else(|e| panic!("png data error for {}: {e}", path.display()));
|
||||
}
|
||||
|
||||
@@ -401,8 +496,18 @@ fn make_back_0() -> Canvas {
|
||||
|
||||
// 2-pixel border
|
||||
let bw = 4i32;
|
||||
for x in 0..W as i32 { for t in 0..bw { cv.set(x, t, LIGHT); cv.set(x, H as i32 - 1 - t, LIGHT); } }
|
||||
for y in 0..H as i32 { for t in 0..bw { cv.set(t, y, LIGHT); cv.set(W as i32 - 1 - t, y, LIGHT); } }
|
||||
for x in 0..W as i32 {
|
||||
for t in 0..bw {
|
||||
cv.set(x, t, LIGHT);
|
||||
cv.set(x, H as i32 - 1 - t, LIGHT);
|
||||
}
|
||||
}
|
||||
for y in 0..H as i32 {
|
||||
for t in 0..bw {
|
||||
cv.set(t, y, LIGHT);
|
||||
cv.set(W as i32 - 1 - t, y, LIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
// Diamond grid: row/col spacing
|
||||
let gx = 18.0f32;
|
||||
@@ -455,8 +560,18 @@ fn make_back_1() -> Canvas {
|
||||
|
||||
// 4-pixel border
|
||||
let bw = 4i32;
|
||||
for x in 0..W as i32 { for t in 0..bw { cv.set(x, t, BORDER); cv.set(x, H as i32 - 1 - t, BORDER); } }
|
||||
for y in 0..H as i32 { for t in 0..bw { cv.set(t, y, BORDER); cv.set(W as i32 - 1 - t, y, BORDER); } }
|
||||
for x in 0..W as i32 {
|
||||
for t in 0..bw {
|
||||
cv.set(x, t, BORDER);
|
||||
cv.set(x, H as i32 - 1 - t, BORDER);
|
||||
}
|
||||
}
|
||||
for y in 0..H as i32 {
|
||||
for t in 0..bw {
|
||||
cv.set(t, y, BORDER);
|
||||
cv.set(W as i32 - 1 - t, y, BORDER);
|
||||
}
|
||||
}
|
||||
cv
|
||||
}
|
||||
|
||||
@@ -470,8 +585,18 @@ fn make_back_2() -> Canvas {
|
||||
|
||||
// 4-pixel border
|
||||
let bw = 4i32;
|
||||
for x in 0..W as i32 { for t in 0..bw { cv.set(x, t, BORDER); cv.set(x, H as i32 - 1 - t, BORDER); } }
|
||||
for y in 0..H as i32 { for t in 0..bw { cv.set(t, y, BORDER); cv.set(W as i32 - 1 - t, y, BORDER); } }
|
||||
for x in 0..W as i32 {
|
||||
for t in 0..bw {
|
||||
cv.set(x, t, BORDER);
|
||||
cv.set(x, H as i32 - 1 - t, BORDER);
|
||||
}
|
||||
}
|
||||
for y in 0..H as i32 {
|
||||
for t in 0..bw {
|
||||
cv.set(t, y, BORDER);
|
||||
cv.set(W as i32 - 1 - t, y, BORDER);
|
||||
}
|
||||
}
|
||||
|
||||
// Circle array (staggered rows)
|
||||
let gx = 16.0f32;
|
||||
@@ -513,8 +638,18 @@ fn make_back_3() -> Canvas {
|
||||
|
||||
// 4-pixel border
|
||||
let bw = 4i32;
|
||||
for x in 0..W as i32 { for t in 0..bw { cv.set(x, t, BORDER); cv.set(x, H as i32 - 1 - t, BORDER); } }
|
||||
for y in 0..H as i32 { for t in 0..bw { cv.set(t, y, BORDER); cv.set(W as i32 - 1 - t, y, BORDER); } }
|
||||
for x in 0..W as i32 {
|
||||
for t in 0..bw {
|
||||
cv.set(x, t, BORDER);
|
||||
cv.set(x, H as i32 - 1 - t, BORDER);
|
||||
}
|
||||
}
|
||||
for y in 0..H as i32 {
|
||||
for t in 0..bw {
|
||||
cv.set(t, y, BORDER);
|
||||
cv.set(W as i32 - 1 - t, y, BORDER);
|
||||
}
|
||||
}
|
||||
cv
|
||||
}
|
||||
|
||||
@@ -543,8 +678,18 @@ fn make_back_4() -> Canvas {
|
||||
|
||||
// 4-pixel border
|
||||
let bw = 4i32;
|
||||
for x in 0..W as i32 { for t in 0..bw { cv.set(x, t, BORDER); cv.set(x, H as i32 - 1 - t, BORDER); } }
|
||||
for y in 0..H as i32 { for t in 0..bw { cv.set(t, y, BORDER); cv.set(W as i32 - 1 - t, y, BORDER); } }
|
||||
for x in 0..W as i32 {
|
||||
for t in 0..bw {
|
||||
cv.set(x, t, BORDER);
|
||||
cv.set(x, H as i32 - 1 - t, BORDER);
|
||||
}
|
||||
}
|
||||
for y in 0..H as i32 {
|
||||
for t in 0..bw {
|
||||
cv.set(t, y, BORDER);
|
||||
cv.set(W as i32 - 1 - t, y, BORDER);
|
||||
}
|
||||
}
|
||||
cv
|
||||
}
|
||||
|
||||
@@ -574,7 +719,7 @@ fn make_bg_0() -> Canvas {
|
||||
fn make_bg_1() -> Canvas {
|
||||
const BASE: [u8; 4] = [0x40, 0x2D, 0x1A, 0xFF];
|
||||
const PLANK_EDGE: [u8; 4] = [0x28, 0x1A, 0x0A, 0xFF]; // dark plank separator
|
||||
const GRAIN: [u8; 4] = [0x55, 0x3D, 0x28, 0xA0]; // lighter grain streak
|
||||
const GRAIN: [u8; 4] = [0x55, 0x3D, 0x28, 0xA0]; // lighter grain streak
|
||||
let mut cv = Canvas::new();
|
||||
cv.fill_solid(BASE);
|
||||
// Horizontal plank edges every 24 px
|
||||
@@ -585,7 +730,9 @@ fn make_bg_1() -> Canvas {
|
||||
// Grain lines within each plank (every 3 px between plank edges)
|
||||
for y in (0..H as i32).step_by(3) {
|
||||
// Skip the plank edge rows
|
||||
if y % 24 < 2 { continue; }
|
||||
if y % 24 < 2 {
|
||||
continue;
|
||||
}
|
||||
cv.hline(y, 2, W as i32 - 3, GRAIN);
|
||||
}
|
||||
cv
|
||||
@@ -608,7 +755,11 @@ fn make_bg_2() -> Canvas {
|
||||
let mut cx = gx * 0.5 + offset;
|
||||
while cx < W as f32 {
|
||||
// alternate bright/dim to give depth
|
||||
let c = if (row + (cx / gx) as u32).is_multiple_of(3) { STAR_A } else { STAR_B };
|
||||
let c = if (row + (cx / gx) as u32).is_multiple_of(3) {
|
||||
STAR_A
|
||||
} else {
|
||||
STAR_B
|
||||
};
|
||||
cv.circle(cx, cy, 1.0, c);
|
||||
cx += gx;
|
||||
}
|
||||
@@ -679,12 +830,13 @@ fn main() {
|
||||
let font_path = root.join("assets/fonts/main.ttf");
|
||||
let font_bytes = std::fs::read(&font_path)
|
||||
.unwrap_or_else(|e| panic!("failed to read {}: {e}", font_path.display()));
|
||||
let font = FontRef::try_from_slice(&font_bytes)
|
||||
.expect("failed to parse assets/fonts/main.ttf");
|
||||
let font = FontRef::try_from_slice(&font_bytes).expect("failed to parse assets/fonts/main.ttf");
|
||||
|
||||
// 52 card faces
|
||||
let suits = ["c", "d", "h", "s"];
|
||||
let ranks = ["a","2","3","4","5","6","7","8","9","10","j","q","k"];
|
||||
let ranks = [
|
||||
"a", "2", "3", "4", "5", "6", "7", "8", "9", "10", "j", "q", "k",
|
||||
];
|
||||
for suit in 0u8..4 {
|
||||
for rank in 0u8..13 {
|
||||
let cv = make_card_face(&font, rank, suit);
|
||||
@@ -696,14 +848,32 @@ fn main() {
|
||||
}
|
||||
|
||||
// Card backs
|
||||
for (i, cv) in [make_back_0(), make_back_1(), make_back_2(), make_back_3(), make_back_4()].iter().enumerate() {
|
||||
for (i, cv) in [
|
||||
make_back_0(),
|
||||
make_back_1(),
|
||||
make_back_2(),
|
||||
make_back_3(),
|
||||
make_back_4(),
|
||||
]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let path = root.join(format!("assets/cards/backs/back_{i}.png"));
|
||||
save_card_png(&path, cv);
|
||||
println!("wrote {}", path.display());
|
||||
}
|
||||
|
||||
// Backgrounds
|
||||
for (i, cv) in [make_bg_0(), make_bg_1(), make_bg_2(), make_bg_3(), make_bg_4()].iter().enumerate() {
|
||||
for (i, cv) in [
|
||||
make_bg_0(),
|
||||
make_bg_1(),
|
||||
make_bg_2(),
|
||||
make_bg_3(),
|
||||
make_bg_4(),
|
||||
]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let path = root.join(format!("assets/backgrounds/bg_{i}.png"));
|
||||
save_card_png(&path, cv);
|
||||
println!("wrote {}", path.display());
|
||||
|
||||
@@ -20,15 +20,15 @@
|
||||
//! --help Print this message
|
||||
|
||||
use solitaire_core::game_state::DrawMode;
|
||||
use solitaire_core::solver::{try_solve, SolverConfig, SolverResult};
|
||||
use solitaire_core::solver::{SolverConfig, SolverResult, try_solve};
|
||||
|
||||
// Budget boundaries defining each tier. A seed belongs to the lowest tier
|
||||
// whose budget proves it Winnable.
|
||||
const BUDGETS: &[(&str, u64, usize)] = &[
|
||||
("Easy", 1_000, 1_000),
|
||||
("Medium", 5_000, 5_000),
|
||||
("Hard", 25_000, 25_000),
|
||||
("Expert", 100_000, 100_000),
|
||||
("Easy", 1_000, 1_000),
|
||||
("Medium", 5_000, 5_000),
|
||||
("Hard", 25_000, 25_000),
|
||||
("Expert", 100_000, 100_000),
|
||||
("Grandmaster", 200_000, 200_000),
|
||||
];
|
||||
|
||||
@@ -86,7 +86,11 @@ fn main() {
|
||||
);
|
||||
eprintln!(
|
||||
" Tiers: {}",
|
||||
BUDGETS.iter().map(|(n, _, _)| *n).collect::<Vec<_>>().join(", ")
|
||||
BUDGETS
|
||||
.iter()
|
||||
.map(|(n, _, _)| *n)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
);
|
||||
|
||||
while buckets.iter().any(|b| b.len() < per_tier) {
|
||||
@@ -95,7 +99,10 @@ fn main() {
|
||||
if buckets[i].len() >= per_tier {
|
||||
continue;
|
||||
}
|
||||
let cfg = SolverConfig { move_budget, state_budget };
|
||||
let cfg = SolverConfig {
|
||||
move_budget,
|
||||
state_budget,
|
||||
};
|
||||
match try_solve(seed, draw_mode, &cfg) {
|
||||
SolverResult::Winnable => {
|
||||
buckets[i].push(seed);
|
||||
@@ -123,7 +130,9 @@ fn main() {
|
||||
seed = seed.wrapping_add(1);
|
||||
}
|
||||
|
||||
eprintln!("\nDone ({tried} seeds examined). Paste the blocks below into difficulty_seeds.rs:\n");
|
||||
eprintln!(
|
||||
"\nDone ({tried} seeds examined). Paste the blocks below into difficulty_seeds.rs:\n"
|
||||
);
|
||||
|
||||
let date = current_date();
|
||||
for (i, (tier_name, _, _)) in BUDGETS.iter().enumerate() {
|
||||
@@ -148,7 +157,10 @@ fn main() {
|
||||
|
||||
fn parse_u64(s: &str) -> u64 {
|
||||
let cleaned = s.replace('_', "");
|
||||
if let Some(hex) = cleaned.strip_prefix("0x").or_else(|| cleaned.strip_prefix("0X")) {
|
||||
if let Some(hex) = cleaned
|
||||
.strip_prefix("0x")
|
||||
.or_else(|| cleaned.strip_prefix("0X"))
|
||||
{
|
||||
u64::from_str_radix(hex, 16).unwrap_or_else(|_| {
|
||||
eprintln!("error: could not parse '{s}' as a hex u64");
|
||||
std::process::exit(1);
|
||||
@@ -181,7 +193,18 @@ fn current_date() -> String {
|
||||
}
|
||||
let leap = (y.is_multiple_of(4) && !y.is_multiple_of(100)) || y.is_multiple_of(400);
|
||||
let month_days: [u64; 12] = [
|
||||
31, if leap { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
|
||||
31,
|
||||
if leap { 29 } else { 28 },
|
||||
31,
|
||||
30,
|
||||
31,
|
||||
30,
|
||||
31,
|
||||
31,
|
||||
30,
|
||||
31,
|
||||
30,
|
||||
31,
|
||||
];
|
||||
let mut m = 0usize;
|
||||
for &md in &month_days {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
//! --help Print this message
|
||||
|
||||
use solitaire_core::game_state::DrawMode;
|
||||
use solitaire_core::solver::{try_solve, SolverConfig, SolverResult};
|
||||
use solitaire_core::solver::{SolverConfig, SolverResult, try_solve};
|
||||
|
||||
fn main() {
|
||||
let mut args = std::env::args().skip(1).peekable();
|
||||
@@ -45,7 +45,14 @@ fn main() {
|
||||
});
|
||||
}
|
||||
"--help" | "-h" => {
|
||||
eprintln!("{}", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/bin/gen_seeds.rs")).lines().take(20).collect::<Vec<_>>().join("\n"));
|
||||
eprintln!(
|
||||
"{}",
|
||||
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/bin/gen_seeds.rs"))
|
||||
.lines()
|
||||
.take(20)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
);
|
||||
return;
|
||||
}
|
||||
other => {
|
||||
@@ -66,16 +73,11 @@ fn main() {
|
||||
let mut tried: u64 = 0;
|
||||
let mut seed = start;
|
||||
|
||||
eprintln!(
|
||||
"gen_seeds: finding {count} Winnable seeds from 0x{start:016X} (DrawOne) …"
|
||||
);
|
||||
eprintln!("gen_seeds: finding {count} Winnable seeds from 0x{start:016X} (DrawOne) …");
|
||||
|
||||
while found.len() < count {
|
||||
tried += 1;
|
||||
if matches!(
|
||||
try_solve(seed, draw_mode, &cfg),
|
||||
SolverResult::Winnable
|
||||
) {
|
||||
if matches!(try_solve(seed, draw_mode, &cfg), SolverResult::Winnable) {
|
||||
found.push(seed);
|
||||
eprintln!(
|
||||
" [{:>3}/{}] 0x{:016X} ({} tried so far)",
|
||||
@@ -88,7 +90,9 @@ fn main() {
|
||||
seed = seed.wrapping_add(1);
|
||||
}
|
||||
|
||||
eprintln!("\nDone. Paste the block below into CHALLENGE_SEEDS in solitaire_data/src/challenge.rs:\n");
|
||||
eprintln!(
|
||||
"\nDone. Paste the block below into CHALLENGE_SEEDS in solitaire_data/src/challenge.rs:\n"
|
||||
);
|
||||
|
||||
println!(
|
||||
" // Generated by solitaire_assetgen::gen_seeds \
|
||||
@@ -111,7 +115,10 @@ fn main() {
|
||||
|
||||
fn parse_u64(s: &str) -> u64 {
|
||||
let cleaned = s.replace('_', "");
|
||||
if let Some(hex) = cleaned.strip_prefix("0x").or_else(|| cleaned.strip_prefix("0X")) {
|
||||
if let Some(hex) = cleaned
|
||||
.strip_prefix("0x")
|
||||
.or_else(|| cleaned.strip_prefix("0X"))
|
||||
{
|
||||
u64::from_str_radix(hex, 16).unwrap_or_else(|_| {
|
||||
eprintln!("error: could not parse '{s}' as a hex u64");
|
||||
std::process::exit(1);
|
||||
@@ -144,7 +151,20 @@ fn current_date() -> String {
|
||||
y += 1;
|
||||
}
|
||||
let leap = (y.is_multiple_of(4) && !y.is_multiple_of(100)) || y.is_multiple_of(400);
|
||||
let month_days: [u64; 12] = [31, if leap { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
let month_days: [u64; 12] = [
|
||||
31,
|
||||
if leap { 29 } else { 28 },
|
||||
31,
|
||||
30,
|
||||
31,
|
||||
30,
|
||||
31,
|
||||
31,
|
||||
30,
|
||||
31,
|
||||
30,
|
||||
31,
|
||||
];
|
||||
let mut m = 0usize;
|
||||
for &md in &month_days {
|
||||
if d < md {
|
||||
|
||||
Reference in New Issue
Block a user