-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 6e928e1
Showing
21 changed files
with
5,401 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
[package] | ||
name = "mindsweeper" | ||
version = "1.0.0" | ||
edition = "2021" | ||
|
||
[profile.release] | ||
opt-level = 3 | ||
codegen-units = 1 | ||
lto = true | ||
strip = true | ||
|
||
[dependencies] | ||
rand = "0.8.5" | ||
getrandom = { version = "0.2.10", features = ["js"] } | ||
itertools = "0.12.0" | ||
num = { version = "0.4.1", features = ["rand"] } | ||
rayon = "1.7.0" | ||
yew = { version = "0.21.0", features = ["csr"] } | ||
web-sys = { version = "0.3.64", features = [ | ||
"HtmlSelectElement", | ||
"HtmlDialogElement", | ||
] } | ||
js-sys = "0.3.64" | ||
wasm-bindgen = "0.2.87" | ||
serde = { version = "1.0.188", features = ["derive"] } | ||
serde_json = "1.0.106" | ||
float-ord = "0.3.2" | ||
thiserror = "1.0.48" | ||
gloo = "0.11.0" | ||
tinyvec = "1.6.0" | ||
strum = { version = "0.25.0", features = ["derive"] } | ||
yew-agent = "0.3.0" | ||
|
||
[dev-dependencies] | ||
criterion = { version = "0.5.1", features = ["real_blackbox"] } | ||
|
||
[[bench]] | ||
name = "solvable_bench" | ||
harness = false |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Mindsweeper — a principled take on minesweeper | ||
|
||
To play, visit https://alexbuz.github.io/minesweeper/. Once the page loads, no further internet connection is required. | ||
|
||
## Background | ||
|
||
Traditional minesweeper is a game of logical deduction—until it's not. Sometimes, you end up in a situation where there is not enough information to find a tile that is definitely safe to reveal. In such cases, guessing is required to proceed, and that often leads to the loss of an otherwise smooth-sailing game. However, there's no reason it has to be that way. It's merely a consequence of the random manner in which mines are typically arranged at the start of the game. Some mine arrangements happen to necessitate guessing, while others do not. This is a matter of luck, and it's not a particularly fun aspect of a game that is otherwise about logic. | ||
|
||
Eliminating the need for guesswork, then, is a matter of modifying the mine arrangement algorithm. Rather than simply placing each mine under a random tile, it should instead consider each mine arrangement as a whole, choosing a random mine arrangement from the set of mine arrangements that would allow a perfect logician to win without guessing. Ideally, it should sample uniformly from that set of mine arrangements, ensuring that every such arrangement is equally likely to be chosen. That is precisely what mindsweeper is designed to do, and it accomplishes this within a matter of milliseconds after you make your first click. | ||
|
||
## Features | ||
|
||
1. Guessing is *never* necessary | ||
- There's no need to toggle a setting. Mindsweeper is a game of pure skill, always. | ||
2. Guess punishment | ||
- Since guessing is already unnecessary, it's only natural to take the idea of "no guessing" a step further and forbid guessing entirely. This feature, enabled by default, effectively rids the game of all remaining luck aspects. If you click on a tile that *can* be a mine, then it *will* be a mine, guaranteed. | ||
3. Unrestricted first click | ||
- Mindsweeper does not obligate you to click a particular tile to start the game. The mine arrangement algorithm works on demand, and is fast enough to avoid introducing any delay. | ||
4. Uniform sampling | ||
- All mine arrangements are viable, except those that necessitate guessing. If a particular mine arrangement is solvable by a perfect logician without guessing, then it's just as likely to be picked by the algorithm as every other viable arrangement. | ||
5. Post-mortem analysis | ||
- If you reveal a mine and lose the game, you'll get feedback that helps you improve. You get to see which flags you misplaced (if any), as well as which tiles you could (and could not) have safely revealed. Tiles are color-coded to show this information at a glance, and you can also hover over any tile to see this explained in words. | ||
6. High performance | ||
- Mindsweeper is written in Rust and compiles to WASM. When you make your first click, the mine arrangement algorithm generally finishes running before you even release the mouse button, so there is no first-click delay. | ||
7. Completely offline | ||
- Mindsweeper does not depend on a server. All of the code runs locally in your browser. | ||
|
||
## Building from source | ||
|
||
Install [Trunk](https://trunkrs.dev/), and then run `trunk build` in the project directory: | ||
|
||
```sh | ||
git clone https://github.com/alexbuz/mindsweeper.git | ||
cd mindsweeper | ||
trunk build | ||
``` | ||
|
||
The built files will be placed in the `dist`, the contents of which is served by GitHub Pages when you visit https://alexbuz.github.io/minesweeper/ to play the game. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
[build] | ||
release = true | ||
public_url = "./" | ||
filehash = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
use criterion::{criterion_group, criterion_main, Criterion}; | ||
use mindsweeper::server::{local::LocalGame, GameConfig, GameMode, GridConfig, Oracle}; | ||
|
||
fn criterion_benchmark(c: &mut Criterion) { | ||
c.bench_function("new_expert_normal", |b| { | ||
let game_config = GameConfig { | ||
grid_config: GridConfig::expert(), | ||
mode: GameMode::Normal, | ||
punish_guessing: true, | ||
}; | ||
b.iter(|| LocalGame::new(game_config, game_config.grid_config.random_tile_id())) | ||
}); | ||
c.bench_function("new_expert_mindless", |b| { | ||
let game_config = GameConfig { | ||
grid_config: GridConfig::expert(), | ||
mode: GameMode::Mindless, | ||
punish_guessing: true, | ||
}; | ||
b.iter(|| LocalGame::new(game_config, game_config.grid_config.random_tile_id())) | ||
}); | ||
} | ||
|
||
criterion_group! { | ||
name = benches; | ||
config = Criterion::default().sample_size(1_000); | ||
targets = criterion_benchmark | ||
} | ||
criterion_main!(benches); |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<!DOCTYPE html> | ||
<html lang="en" oncontextmenu="return false"> | ||
<head> | ||
<meta charset="utf-8"> | ||
<title>Mindsweeper</title> | ||
<link data-trunk rel="icon" href="favicon.png"> | ||
<link data-trunk rel="icon" href="favicon.svg"> | ||
<link data-trunk rel="css" href="main.css"> | ||
</head> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
body { | ||
font-family: Arial, Helvetica, sans-serif; | ||
} | ||
|
||
h2 { | ||
margin-bottom: 0; | ||
padding-right: 30px; | ||
} | ||
|
||
dialog :not(h2) { | ||
line-height: 1.25; | ||
} | ||
|
||
li { | ||
margin-top: 5px; | ||
} | ||
|
||
li li { | ||
font-size: 85%; | ||
} | ||
|
||
dialog { | ||
border: none; | ||
box-shadow: 0 0 16px gray; | ||
border-radius: 20px; | ||
max-width: calc(min(80ch, 90vw)); | ||
max-height: 90vh; | ||
padding: 0; | ||
} | ||
|
||
dialog>div { | ||
padding: 0 20px 5px 20px; | ||
} | ||
|
||
dialog::backdrop { | ||
background: rgba(0, 0, 0, 0.2); | ||
} | ||
|
||
label { | ||
-webkit-user-select: none; | ||
user-select: none; | ||
} | ||
|
||
button#close-dialog { | ||
position: absolute; | ||
width: 28px; | ||
height: 28px; | ||
top: 15px; | ||
right: 15px; | ||
} | ||
|
||
table { | ||
margin: auto; | ||
border-collapse: collapse; | ||
-webkit-user-select: none; | ||
user-select: none; | ||
cursor: default; | ||
} | ||
|
||
thead td>div { | ||
display: flex; | ||
justify-content: space-evenly; | ||
} | ||
|
||
thead td { | ||
height: 30px; | ||
font-size: 16px; | ||
} | ||
|
||
.timer { | ||
padding: 1px 3px; | ||
border-radius: 3px; | ||
} | ||
|
||
tbody:not(.punish-guessing) { | ||
--shadow-red: 128; | ||
} | ||
|
||
tbody.autopilot { | ||
--shadow-green: 128; | ||
} | ||
|
||
tbody.mindless { | ||
--shadow-blue: 128; | ||
} | ||
|
||
tbody { | ||
box-shadow: 8px 8px 8px rgba(var(--shadow-red, 0), var(--shadow-green, 0), var(--shadow-blue, 0), 0.2); | ||
} | ||
|
||
.tile { | ||
height: 36px; | ||
width: 36px; | ||
text-align: center; | ||
border: 1px solid black; | ||
font-size: 24px; | ||
font-weight: bold; | ||
font-family: 'Courier New', Courier, monospace; | ||
background-color: #eee; | ||
} | ||
|
||
.hidden { | ||
display: none; | ||
} | ||
|
||
.bg-red { | ||
background-color: pink; | ||
} | ||
|
||
.bg-green { | ||
background-color: lightgreen; | ||
} | ||
|
||
.bg-blue { | ||
background-color: lightblue; | ||
} | ||
|
||
.bg-orange { | ||
background-color: #ffcc99; | ||
} | ||
|
||
.bg-yellow { | ||
background-color: #ffff99; | ||
} | ||
|
||
.text-faded { | ||
color: rgba(0, 0, 0, 0.5); | ||
} | ||
|
||
.text-red { | ||
color: red; | ||
} | ||
|
||
.revealed { | ||
background-color: #ccc; | ||
} | ||
|
||
.number-1 { | ||
color: blue; | ||
} | ||
|
||
.number-2 { | ||
color: green; | ||
} | ||
|
||
.number-3 { | ||
color: red; | ||
} | ||
|
||
.number-4 { | ||
color: purple; | ||
} | ||
|
||
.number-5 { | ||
color: maroon; | ||
} | ||
|
||
.number-6 { | ||
color: teal; | ||
} | ||
|
||
.number-7 { | ||
color: black; | ||
} | ||
|
||
.number-8 { | ||
color: gray; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[toolchain] | ||
channel = "nightly" |
Oops, something went wrong.