1
Fork 0

Open one field by default

This commit is contained in:
Tobias Berger 2024-05-23 09:36:23 +02:00
parent c82cd98302
commit 1889a170c8
Signed by: toby
GPG key ID: 2D05EFAB764D6A88
5 changed files with 87 additions and 12107 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "minesweeper-nojs" name = "minesweeper-nojs"
version = "0.1.3" version = "1.4.0"
edition = "2021" edition = "2021"
[profile.release] [profile.release]

View file

@ -8,11 +8,11 @@
"rust-analyzer-src": "rust-analyzer-src" "rust-analyzer-src": "rust-analyzer-src"
}, },
"locked": { "locked": {
"lastModified": 1714631076, "lastModified": 1716359173,
"narHash": "sha256-at4+1R9gx3CGvX0ZJo9GwDZyt3RzOft7qDCTsYHjI4M=", "narHash": "sha256-pYcjP6Gy7i6jPWrjiWAVV0BCQp+DdmGaI/k65lBb/kM=",
"owner": "nix-community", "owner": "nix-community",
"repo": "fenix", "repo": "fenix",
"rev": "22a9eb3f20dd340d084cee4426f386a90b1351ca", "rev": "b6fc5035b28e36a98370d0eac44f4ef3fd323df6",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -23,16 +23,16 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1714253743, "lastModified": 1716312448,
"narHash": "sha256-mdTQw2XlariysyScCv2tTE45QSU9v/ezLcHJ22f0Nxc=", "narHash": "sha256-PH3w5av8d+TdwCkiWN4UPBTxrD9MpxIQPDVWctlomVo=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "58a1abdbae3217ca6b702f03d3b35125d88a2994", "rev": "e381a1288138aceda0ac63db32c7be545b446921",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nixos", "owner": "nixos",
"ref": "nixos-unstable", "ref": "nixpkgs-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
@ -47,11 +47,11 @@
"rust-analyzer-src": { "rust-analyzer-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1714572655, "lastModified": 1716107283,
"narHash": "sha256-xjD8vmit0Nz1qaSSSpeXOK3saSvAZtOGHS2SHZE75Ek=", "narHash": "sha256-NJgrwLiLGHDrCia5AeIvZUHUY7xYGVryee0/9D3Ir1I=",
"owner": "rust-lang", "owner": "rust-lang",
"repo": "rust-analyzer", "repo": "rust-analyzer",
"rev": "cfce2bb46da62950a8b70ddb0b2a12332da1b1e1", "rev": "21ec8f523812b88418b2bfc64240c62b3dd967bd",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -68,11 +68,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1714058656, "lastModified": 1715940852,
"narHash": "sha256-Qv4RBm4LKuO4fNOfx9wl40W2rBbv5u5m+whxRYUMiaA=", "narHash": "sha256-wJqHMg/K6X3JGAE9YLM0LsuKrKb4XiBeVaoeMNlReZg=",
"owner": "numtide", "owner": "numtide",
"repo": "treefmt-nix", "repo": "treefmt-nix",
"rev": "c6aaf729f34a36c445618580a9f95a48f5e4e03f", "rev": "2fba33a182602b9d49f0b2440513e5ee091d838b",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -2,7 +2,7 @@
description = "A Nix-flake-based Rust development environment"; description = "A Nix-flake-based Rust development environment";
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
treefmt-nix = { treefmt-nix = {
url = "github:numtide/treefmt-nix"; url = "github:numtide/treefmt-nix";

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,4 @@
mod html_prealloc_sizes; // If changed, also change error message
const MAX_SIZE: usize = 50; const MAX_SIZE: usize = 50;
const PORT: u16 = 55782; const PORT: u16 = 55782;
const STYLESHEET: &[u8; 776] = include_bytes!("../style.css"); const STYLESHEET: &[u8; 776] = include_bytes!("../style.css");
@ -30,11 +29,12 @@ pub fn random_numbers() -> std::iter::RepeatWith<impl FnMut() -> usize> {
.as_nanos() as usize; .as_nanos() as usize;
std::iter::repeat_with(move || { std::iter::repeat_with(move || {
random ^= random << 13; random ^= random << 13;
random ^= random >> 17; random ^= random >> (if usize::BITS <= 32 { 17 } else { 7 });
random ^= random << 5; random ^= random << (if usize::BITS <= 32 { 5 } else { 17 });
random random
}) })
} }
fn not_found() -> Response<Full<Bytes>> { fn not_found() -> Response<Full<Bytes>> {
Response::builder() Response::builder()
.status(StatusCode::NOT_FOUND) .status(StatusCode::NOT_FOUND)
@ -65,7 +65,15 @@ async fn favicon() -> Response<Full<Bytes>> {
.unwrap() .unwrap()
} }
#[derive(Default, Clone, Copy)]
struct Field {
mine: bool,
open: bool,
neighbor_mines: u8,
}
async fn create_board(width: usize, height: usize, mines: usize) -> Response<Full<Bytes>> { async fn create_board(width: usize, height: usize, mines: usize) -> Response<Full<Bytes>> {
let start = SystemTime::now();
if width == 0 || height == 0 || mines == 0 { if width == 0 || height == 0 || mines == 0 {
Response::builder() Response::builder()
.status(StatusCode::RANGE_NOT_SATISFIABLE) .status(StatusCode::RANGE_NOT_SATISFIABLE)
@ -76,7 +84,7 @@ async fn create_board(width: usize, height: usize, mines: usize) -> Response<Ful
Response::builder() Response::builder()
.status(StatusCode::RANGE_NOT_SATISFIABLE) .status(StatusCode::RANGE_NOT_SATISFIABLE)
.header("Cache-Control", "no-store") .header("Cache-Control", "no-store")
.body(Full::new(Bytes::from_static(b"FIELD TOO BIG"))) .body(Full::new(Bytes::from_static(b"FIELD TOO BIG (MAX 50)")))
.unwrap() .unwrap()
} else if mines >= width * height { } else if mines >= width * height {
Response::builder() Response::builder()
@ -87,88 +95,96 @@ async fn create_board(width: usize, height: usize, mines: usize) -> Response<Ful
))) )))
.unwrap() .unwrap()
} else { } else {
let mut fields = vec![false; width * height]; let mut fields = vec![Field::default(); width * height];
let mut safe_indices = (0..width * height).collect::<Vec<_>>(); let mut safe_indices = (0..width * height).collect::<Vec<_>>();
for (placed_mines, random) in (0..mines).zip(random_numbers()) { let get_neighbor_indices = |idx: usize| -> Vec<usize> {
let mine_index = safe_indices.swap_remove(random % ((width * height) - placed_mines));
fields[mine_index] = true;
}
let get_neighbors = |idx: usize| -> u8 {
let mut neighbor_count = 0;
let left_edge = (idx % width) == 0; let left_edge = (idx % width) == 0;
let top_edge = idx < width; let top_edge = idx < width;
let bottom_edge = idx >= ((height - 1) * width); let bottom_edge = idx >= ((height - 1) * width);
let right_edge = (idx % width) == (width - 1); let right_edge = (idx % width) == (width - 1);
if !left_edge && fields[idx - 1] { let mut indices = Vec::with_capacity(8);
neighbor_count += 1;
if !left_edge {
indices.push(idx - 1);
} }
if !left_edge && !top_edge && fields[idx - 1 - width] { if !left_edge && !top_edge {
neighbor_count += 1; indices.push(idx - 1 - width);
} }
if !left_edge && !bottom_edge && fields[idx - 1 + width] { if !left_edge && !bottom_edge {
neighbor_count += 1; indices.push(idx - 1 + width);
} }
if !right_edge && fields[idx + 1] { if !right_edge {
neighbor_count += 1; indices.push(idx + 1);
} }
if !right_edge && !top_edge && fields[idx + 1 - width] { if !right_edge && !top_edge {
neighbor_count += 1; indices.push(idx + 1 - width);
} }
if !right_edge && !bottom_edge && fields[idx + 1 + width] { if !right_edge && !bottom_edge {
neighbor_count += 1; indices.push(idx + 1 + width);
} }
if !top_edge && fields[idx - width] { if !top_edge {
neighbor_count += 1; indices.push(idx - width);
} }
if !bottom_edge && fields[idx + width] { if !bottom_edge {
neighbor_count += 1; indices.push(idx + width);
} }
neighbor_count indices
}; };
for (placed_mines, random) in (0..mines).zip(random_numbers()) {
let mine_index = safe_indices.swap_remove(random % ((width * height) - placed_mines));
fields[mine_index].mine = true;
for neighbor_index in get_neighbor_indices(mine_index) {
fields[neighbor_index].neighbor_mines += 1;
}
}
// SAFETY: random_numbers returns a RepeatWith, which will always have a next element
let mut open_indices = Vec::with_capacity(fields.len() - mines);
open_indices.push(
safe_indices
[unsafe { random_numbers().next().unwrap_unchecked() } % safe_indices.len()],
);
while let Some(open_index) = open_indices.pop() {
if !fields[open_index].open {
fields[open_index].open = true;
if fields[open_index].neighbor_mines == 0 {
open_indices.append(&mut get_neighbor_indices(open_index));
}
}
}
// Can't figure out how to do regression, otherwise I'd make these into functions to calculate the // Can't figure out how to do regression, otherwise I'd make these into functions to calculate the
// allocated size based on width and height // allocated size based on width and height
let sizes = html_prealloc_sizes::get_sizes(width, height); let mut style_string = String::new();
let mut inputs_string = String::new();
let mut table_string = String::new();
let mut style_string = String::with_capacity(sizes.style); for (field_index, &field) in fields.iter().enumerate() {
let mut inputs_string = String::with_capacity(sizes.inputs);
let mut table_string = String::with_capacity(sizes.table);
for (field_index, &field_is_mine) in fields.iter().enumerate() {
table_string += format!("<td><label for=input_{field_index}></label></td>").as_str(); table_string += format!("<td><label for=input_{field_index}></label></td>").as_str();
if (field_index % width) == (width - 1) && field_index != width * height - 1 { if (field_index % width) == (width - 1) && field_index != width * height - 1 {
table_string += "</tr><tr>"; table_string += "</tr><tr>";
} }
inputs_string += format!( inputs_string += format!(
"<input id=input_{field_index} type=checkbox data-{}></input>", "<input id=input_{field_index} type=checkbox data-{}{}></input>",
if field_is_mine { "mine" } else { "safe" } if field.mine { "mine" } else { "safe" },
if field.open { " checked" } else { "" },
) )
.as_str(); .as_str();
style_string += format!( style_string += format!(
"#input_{field_index}:checked ~ main label[for=\"input_{field_index}\"]::before {{ content: \"{}\"; }}\n#input_{field_index}:checked ~ main label[for=\"input_{field_index}\"] {{ pointer-events: none; }}", "#input_{field_index}:checked ~ main label[for=\"input_{field_index}\"]::before {{ content: \"{}\"; }}\n#input_{field_index}:checked ~ main label[for=\"input_{field_index}\"] {{ pointer-events: none; }}",
if field_is_mine { "X".into() } else { get_neighbors(field_index).to_string() }) if field.mine { "X".into() } else { field.neighbor_mines.to_string() })
.as_str(); .as_str();
} }
if style_string.capacity() > sizes.style {
dbg!(style_string.capacity(), style_string.len(), sizes.style);
}
if inputs_string.capacity() > sizes.inputs {
dbg!(inputs_string.capacity(), inputs_string.len(), sizes.inputs);
}
if table_string.capacity() > sizes.table {
dbg!(table_string.capacity(), table_string.len(), sizes.table);
}
let response_string = format!( let response_string = format!(
"<!DOCTYPE html><html><head><link rel=stylesheet href=style.css><link rel=icon type=\"image/x-icon\" href=favicon.ico><style>{}</style></head><body>{}<main><table><tbody><tr>{}</tr></tbody></table></main><footer><span id=lost>HAHA you lost >:)</span><span id=won>YAY, you WON!!!</span></footer></body></html>", "<!DOCTYPE html><html><head><link rel=stylesheet href=style.css><link rel=icon type=\"image/x-icon\" href=favicon.ico><style>{}</style></head><body>{}<main><table><tbody><tr>{}</tr></tbody></table></main><footer><span id=lost>HAHA you lost >:)</span><span id=won>YAY, you WON!!!</span></footer></body></html>",
style_string, style_string,
@ -176,19 +192,16 @@ async fn create_board(width: usize, height: usize, mines: usize) -> Response<Ful
table_string table_string
); );
// To debug sizes for allocation let time = SystemTime::now()
// let response_string = format!( .duration_since(start)
// "{{\"style\":{},\"inputs\":{},\"table\":{}}}", .map(|x| x.as_nanos().to_string())
// style_string.len(), .unwrap_or("Filed to track time".to_string());
// inputs_string.len(), dbg!(time);
// table_string.len()
// );
Response::builder() Response::builder()
.status(StatusCode::OK) .status(StatusCode::OK)
.header("Cache-Control", "no-store") .header("Cache-Control", "no-store")
.body(Full::from(response_string)) .body(Full::from(response_string))
.unwrap() .expect("Failed to respond?")
} }
} }