generated from PlexSheep/rs-base
Compare commits
4 commits
feat/naive
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
ad9b0197c8 | ||
fd164fc978 | |||
a7147d50ac | |||
|
2c5ebf25a3 |
17 changed files with 136 additions and 361 deletions
7
.github/dependabot.yaml
vendored
Normal file
7
.github/dependabot.yaml
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
# Check for updates every Monday
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
12
.github/workflows/cargo.yaml
vendored
12
.github/workflows/cargo.yaml
vendored
|
@ -1,10 +1,16 @@
|
||||||
name: cargo devel CI
|
name: Rust CI
|
||||||
on:
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- '**'
|
- '**'
|
||||||
# - '!master'
|
# - '!master'
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
CI:
|
CI:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -38,10 +44,10 @@ jobs:
|
||||||
- name: cargo fmt
|
- name: cargo fmt
|
||||||
run: cargo fmt --all
|
run: cargo fmt --all
|
||||||
- name: cargo test
|
- name: cargo test
|
||||||
run: cargo test --all-features --all-targets --workspace && cargo test --all-features --workspace --doc
|
run: cargo test --all-features --all-targets --workspace
|
||||||
- name: commit back to repository
|
- name: commit back to repository
|
||||||
uses: stefanzweifel/git-auto-commit-action@v5
|
uses: stefanzweifel/git-auto-commit-action@v5
|
||||||
with:
|
with:
|
||||||
# Optional. Commit message for the created commit.
|
# Optional. Commit message for the created commit.
|
||||||
# Defaults to "Apply automatic changes"
|
# Defaults to "Apply automatic changes"
|
||||||
commit_message: automatic cargo CI changes
|
commit_message: "ci: automatic Rust CI changes"
|
||||||
|
|
54
.github/workflows/release.yaml
vendored
Normal file
54
.github/workflows/release.yaml
vendored
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
name: Release-plz
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
# Release unpublished packages.
|
||||||
|
release-plz-release:
|
||||||
|
name: Release-plz release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Run release-plz
|
||||||
|
uses: MarcoIeni/release-plz-action@v0.5
|
||||||
|
with:
|
||||||
|
command: release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
|
|
||||||
|
# Create a PR with the new versions and changelog, preparing the next release.
|
||||||
|
release-plz-pr:
|
||||||
|
name: Release-plz PR
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
concurrency:
|
||||||
|
group: release-plz-${{ github.ref }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Run release-plz
|
||||||
|
uses: MarcoIeni/release-plz-action@v0.5
|
||||||
|
with:
|
||||||
|
command: release-pr
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
|
@ -64,11 +64,3 @@ test-log = { version = "0.2.16", default-features = false, features = [
|
||||||
"color",
|
"color",
|
||||||
"trace",
|
"trace",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "solver_naive"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "solver_stupid"
|
|
||||||
harness = false
|
|
||||||
|
|
|
@ -26,6 +26,5 @@ have to guess words by slowly guessing the letters contained in it.
|
||||||
Included in this repository are the following wordlists:
|
Included in this repository are the following wordlists:
|
||||||
|
|
||||||
<!-- TODO: make sure this is properly cited -->
|
<!-- TODO: make sure this is properly cited -->
|
||||||
* [`./data/wordlists/en_US_3b1b_freq_map.json`](./data/wordlists/en_US_3b1b_freq_map.json) --- [3Blue1Brown Top English words](https://github.com/3b1b/videos/tree/master/_2022/wordle/data)
|
* [3Blue1Brown Top English words](./data/wordlists/german_SUBTLEX-DE.json) --- [`./data/wordlists/en_US_3b1b_freq_map.json`](https://github.com/3b1b/videos/tree/master/_2022/wordle/data)
|
||||||
* [`./data/wordlists/german_SUBTLEX-DE_full.json`](./data/wordlists/german_SUBTLEX-DE_full.json) --- [SUBTLEX-DE ~33.000 Common German Words](https://osf.io/py9ba/files/osfstorage)
|
* [~33.000 Common German Words](./data/wordlists/german_SUBTLEX-DE.json) --- [SUBTLEX-DE](https://osf.io/py9ba/files/osfstorage)
|
||||||
* [`./data/wordlists/german_SUBTLEX-DE_small.json`](./data/wordlists/german_SUBTLEX-DE_small.json) --- [SUBTLEX-DE ~33.000 Common German Words (reduced to most common)](https://osf.io/py9ba/files/osfstorage)
|
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
use wordle_analyzer::bench::builtin::BuiltinBenchmark;
|
|
||||||
use wordle_analyzer::bench::Benchmark;
|
|
||||||
use wordle_analyzer::game::{self, GameBuilder};
|
|
||||||
use wordle_analyzer::solve::{NaiveSolver, Solver};
|
|
||||||
use wordle_analyzer::wlist::builtin::BuiltinWList;
|
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
|
||||||
let wl = BuiltinWList::english(5);
|
|
||||||
let builder: GameBuilder<'_, BuiltinWList> = game::Game::builder(&wl)
|
|
||||||
.length(5)
|
|
||||||
.max_steps(6)
|
|
||||||
.precompute(true);
|
|
||||||
let solver: NaiveSolver<_> = NaiveSolver::build(&wl)?;
|
|
||||||
let bench = BuiltinBenchmark::build(&wl, solver, builder, 16)?;
|
|
||||||
bench.start(2000, &bench.builder())?;
|
|
||||||
println!("{}", bench.report());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
use wordle_analyzer::bench::builtin::BuiltinBenchmark;
|
|
||||||
use wordle_analyzer::bench::Benchmark;
|
|
||||||
use wordle_analyzer::game::{self, GameBuilder};
|
|
||||||
use wordle_analyzer::solve::Solver;
|
|
||||||
use wordle_analyzer::solve::StupidSolver;
|
|
||||||
use wordle_analyzer::wlist::builtin::BuiltinWList;
|
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
|
||||||
let wl = BuiltinWList::english(5);
|
|
||||||
let builder: GameBuilder<'_, BuiltinWList> = game::Game::builder(&wl)
|
|
||||||
.length(5)
|
|
||||||
.max_steps(6)
|
|
||||||
.precompute(true);
|
|
||||||
let solver: StupidSolver<_> = StupidSolver::build(&wl)?;
|
|
||||||
let bench = BuiltinBenchmark::build(&wl, solver, builder, 16)?;
|
|
||||||
bench.start(2000, &bench.builder())?;
|
|
||||||
println!("{}", bench.report());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
cargo check --all-features
|
|
||||||
echo ">>>>>>>> PUBLISHING RELEASE FOR REPO"
|
|
||||||
bash scripts/release.sh
|
|
||||||
echo ">>>>>>>> PUBLISHING TO CRATES.IO NEXT"
|
|
||||||
sleep 2
|
|
||||||
cargo publish
|
|
||||||
echo ">>>>>>>> PUBLISHING TO CSCHERR.DE NEXT"
|
|
||||||
sleep 2
|
|
||||||
cargo publish --registry cscherr
|
|
|
@ -1,24 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
TOKEN=$(cat ~/.git-credentials | grep 'git.cscherr.de' | grep -P '(?:)[^:]*(?=@)' -o)
|
|
||||||
NEW_VERSION=$(cat Cargo.toml | rg '^\s*version\s*=\s*"([^"]*)"\s*$' -or '$1')
|
|
||||||
GIT_COMMIT_SHA=$(git rev-parse HEAD)
|
|
||||||
REPO=${PWD##*/} # name of cwd
|
|
||||||
BODY="
|
|
||||||
$(git log $(git describe --tags --abbrev=0)..HEAD --pretty="- %s" --oneline --decorate)
|
|
||||||
"
|
|
||||||
USER=PlexSheep
|
|
||||||
git tag "v$NEW_VERSION" || echo "could not tag"
|
|
||||||
curl -X 'POST' \
|
|
||||||
'https://git.cscherr.de/api/v1/repos/PlexSheep/'$REPO'/releases' \
|
|
||||||
-H 'accept: application/json' \
|
|
||||||
-H "Authorization: token $TOKEN" \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-d '{
|
|
||||||
"body": "'"$BODY"'",
|
|
||||||
"draft": false,
|
|
||||||
"name": "v'$NEW_VERSION'",
|
|
||||||
"prerelease": true,
|
|
||||||
"tag_name": "v'$NEW_VERSION'",
|
|
||||||
"target_commitish": "'$GIT_COMMIT_SHA'"
|
|
||||||
}' | python -m json.tool
|
|
||||||
git push || echo "could not push"
|
|
|
@ -1,7 +1,3 @@
|
||||||
use std::fmt::Display;
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
use colored::Colorize;
|
|
||||||
use libpt::cli::console::{style, StyledObject};
|
use libpt::cli::console::{style, StyledObject};
|
||||||
|
|
||||||
use crate::wlist::word::Word;
|
use crate::wlist::word::Word;
|
||||||
|
@ -19,6 +15,21 @@ pub struct Evaluation {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Evaluation {
|
impl Evaluation {
|
||||||
|
/// Display the evaluation color coded
|
||||||
|
pub fn colorized_display(&self) -> Vec<StyledObject<String>> {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
for e in self.inner.iter() {
|
||||||
|
let mut c = style(e.0.to_string());
|
||||||
|
if e.1 == Status::Matched {
|
||||||
|
c = c.green();
|
||||||
|
} else if e.1 == Status::Exists {
|
||||||
|
c = c.yellow();
|
||||||
|
}
|
||||||
|
buf.push(c);
|
||||||
|
}
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
/// The first string is the word the evaluation is for, The second string defines how the
|
/// The first string is the word the evaluation is for, The second string defines how the
|
||||||
/// characters of the first string match the solution.
|
/// characters of the first string match the solution.
|
||||||
///
|
///
|
||||||
|
@ -97,20 +108,3 @@ impl From<&Evaluation> for Word {
|
||||||
Word::from(value.inner.iter().map(|v| v.0).collect::<String>())
|
Word::from(value.inner.iter().map(|v| v.0).collect::<String>())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Evaluation {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
for s in &self.inner {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}",
|
|
||||||
match s.1 {
|
|
||||||
Status::None => s.0.to_string().into(),
|
|
||||||
Status::Exists => s.0.to_string().yellow(),
|
|
||||||
Status::Matched => s.0.to_string().green(),
|
|
||||||
}
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
std::fmt::Result::Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ where
|
||||||
solution: Option<WordData>,
|
solution: Option<WordData>,
|
||||||
wordlist: &'wl WL,
|
wordlist: &'wl WL,
|
||||||
responses: Vec<GuessResponse>,
|
responses: Vec<GuessResponse>,
|
||||||
|
// TODO: keep track of the letters the user has tried
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'wl, WL: WordList> Game<'wl, WL> {
|
impl<'wl, WL: WordList> Game<'wl, WL> {
|
||||||
|
@ -126,40 +127,20 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
|
||||||
|
|
||||||
/// Generates an [Evaluation] for a given solution and guess.
|
/// Generates an [Evaluation] for a given solution and guess.
|
||||||
pub(crate) fn evaluate(solution: WordData, guess: &Word) -> Evaluation {
|
pub(crate) fn evaluate(solution: WordData, guess: &Word) -> Evaluation {
|
||||||
let solution = solution.0;
|
let mut evaluation = Vec::new();
|
||||||
let mut evaluation: Vec<(char, Status)> = vec![('!', Status::None); solution.len()];
|
|
||||||
let mut status: Status;
|
let mut status: Status;
|
||||||
let mut buf: Vec<char> = solution.chars().collect();
|
let mut buf = solution.0.clone();
|
||||||
|
for ((idx, c_guess), c_sol) in guess.chars().enumerate().zip(solution.0.chars()) {
|
||||||
// first the correct solutions
|
|
||||||
for ((idx, c_guess), c_sol) in guess.chars().enumerate().zip(solution.chars()) {
|
|
||||||
if c_guess == c_sol {
|
if c_guess == c_sol {
|
||||||
status = Status::Matched;
|
status = Status::Matched;
|
||||||
buf[idx] = '!';
|
buf.replace_range(idx..idx + 1, "_");
|
||||||
evaluation[idx] = (c_guess, status);
|
} else if buf.contains(c_guess) {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// then check if the char exists, but was not guessed to be at the correct position
|
|
||||||
//
|
|
||||||
// We split this up, because finding the "exists" chars at the same time as the "correct"
|
|
||||||
// chars causes bugs
|
|
||||||
for ((idx, c_guess), c_sol) in guess.chars().enumerate().zip(solution.chars()) {
|
|
||||||
if c_guess == c_sol {
|
|
||||||
continue;
|
|
||||||
} else if buf.contains(&c_guess) {
|
|
||||||
status = Status::Exists;
|
status = Status::Exists;
|
||||||
// replace that char in the buffer to signal that is has been paired with the
|
buf = buf.replacen(c_guess, "_", 1);
|
||||||
// current char
|
|
||||||
let idx_of_a_match = buf
|
|
||||||
.iter()
|
|
||||||
.position(|c| *c == c_guess)
|
|
||||||
.expect("did not find a character in a string even though we know it exists");
|
|
||||||
buf[idx_of_a_match] = '!';
|
|
||||||
} else {
|
} else {
|
||||||
status = Status::None;
|
status = Status::None
|
||||||
}
|
}
|
||||||
evaluation[idx] = (c_guess, status);
|
evaluation.push((c_guess, status));
|
||||||
}
|
}
|
||||||
evaluation.into()
|
evaluation.into()
|
||||||
}
|
}
|
||||||
|
@ -368,7 +349,7 @@ impl<'wl, WL: WordList> GameBuilder<'wl, WL> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'wl, WL: WordList> Display for Game<'wl, WL> {
|
impl<WL: WordList> Display for Game<'_, WL> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
// TODO: make this actually useful
|
// TODO: make this actually useful
|
||||||
// TODO: make this actually fancy
|
// TODO: make this actually fancy
|
||||||
|
@ -378,8 +359,16 @@ impl<'wl, WL: WordList> Display for Game<'wl, WL> {
|
||||||
self.step(),
|
self.step(),
|
||||||
self.solution(),
|
self.solution(),
|
||||||
)?;
|
)?;
|
||||||
for s in self.responses() {
|
for s in self
|
||||||
write!(f, "\"{s}\",")?;
|
.responses()
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.evaluation().to_owned().colorized_display())
|
||||||
|
{
|
||||||
|
write!(f, "\"")?;
|
||||||
|
for si in s {
|
||||||
|
write!(f, "{si}")?;
|
||||||
|
}
|
||||||
|
write!(f, "\", ")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,6 +92,17 @@ impl GuessResponse {
|
||||||
|
|
||||||
impl Display for GuessResponse {
|
impl Display for GuessResponse {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}", self.evaluation())
|
for s in self.evaluation.clone().into_iter() {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match s.1 {
|
||||||
|
Status::None => s.0.to_string().into(),
|
||||||
|
Status::Exists => s.0.to_string().yellow(),
|
||||||
|
Status::Matched => s.0.to_string().green(),
|
||||||
|
}
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
std::fmt::Result::Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ pub struct Summary<'wl, WL: WordList> {
|
||||||
data: Vec<&'wl Game<'wl, WL>>,
|
data: Vec<&'wl Game<'wl, WL>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'wl, WL: WordList> Default for Summary<'wl, WL> {
|
impl<WL: WordList> Default for Summary<'_, WL> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,15 @@ use std::collections::HashMap;
|
||||||
use libpt::log::{debug, error, info, trace};
|
use libpt::log::{debug, error, info, trace};
|
||||||
|
|
||||||
use crate::error::{SolverError, WResult};
|
use crate::error::{SolverError, WResult};
|
||||||
use crate::game::evaluation::{Evaluation, EvaluationUnit};
|
use crate::game::evaluation::Evaluation;
|
||||||
use crate::game::response::Status;
|
|
||||||
use crate::wlist::word::{Word, WordData};
|
use crate::wlist::word::{Word, WordData};
|
||||||
use crate::wlist::WordList;
|
use crate::wlist::WordList;
|
||||||
|
|
||||||
|
use super::{AnyBuiltinSolver, Solver, Status};
|
||||||
|
|
||||||
mod states;
|
mod states;
|
||||||
use states::*;
|
use states::*;
|
||||||
|
|
||||||
use super::{AnyBuiltinSolver, Solver};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct NaiveSolver<'wl, WL> {
|
pub struct NaiveSolver<'wl, WL> {
|
||||||
wl: &'wl WL,
|
wl: &'wl WL,
|
||||||
|
@ -38,10 +37,9 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> {
|
||||||
let mut state: SolverState = SolverState::new();
|
let mut state: SolverState = SolverState::new();
|
||||||
let responses = game.responses().iter().enumerate();
|
let responses = game.responses().iter().enumerate();
|
||||||
for (_idx, response) in responses {
|
for (_idx, response) in responses {
|
||||||
let mut abs_freq: HashMap<char, usize> = HashMap::new();
|
let mut already_found_amounts: HashMap<char, usize> = HashMap::new();
|
||||||
let evaluation: &Evaluation = response.evaluation();
|
let evaluation: &Evaluation = response.evaluation();
|
||||||
for (idx, p) in evaluation.clone().into_iter().enumerate() {
|
for (idx, p) in evaluation.clone().into_iter().enumerate() {
|
||||||
state.start_step();
|
|
||||||
match p.1 {
|
match p.1 {
|
||||||
Status::Matched => {
|
Status::Matched => {
|
||||||
pattern.replace_range(idx..idx + 1, &p.0.to_string());
|
pattern.replace_range(idx..idx + 1, &p.0.to_string());
|
||||||
|
@ -51,7 +49,7 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> {
|
||||||
.entry(p.0)
|
.entry(p.0)
|
||||||
.or_insert(CharInfo::new(game.length()))
|
.or_insert(CharInfo::new(game.length()))
|
||||||
.found_at(idx);
|
.found_at(idx);
|
||||||
*abs_freq.entry(p.0).or_default() += 1;
|
*already_found_amounts.entry(p.0).or_default() += 1;
|
||||||
}
|
}
|
||||||
Status::Exists => {
|
Status::Exists => {
|
||||||
let cinfo = state
|
let cinfo = state
|
||||||
|
@ -59,19 +57,16 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> {
|
||||||
.entry(p.0)
|
.entry(p.0)
|
||||||
.or_insert(CharInfo::new(game.length()));
|
.or_insert(CharInfo::new(game.length()));
|
||||||
cinfo.tried_but_failed(idx);
|
cinfo.tried_but_failed(idx);
|
||||||
*abs_freq.entry(p.0).or_default() += 1;
|
*already_found_amounts.entry(p.0).or_default() += 1;
|
||||||
|
cinfo.min_occurences(already_found_amounts[&p.0]);
|
||||||
}
|
}
|
||||||
Status::None => {
|
Status::None => state
|
||||||
let cinfo = state
|
|
||||||
.char_map_mut()
|
.char_map_mut()
|
||||||
.entry(p.0)
|
.entry(p.0)
|
||||||
.or_insert(CharInfo::new(game.length()));
|
.or_insert(CharInfo::new(game.length()))
|
||||||
cinfo.tried_but_failed(idx);
|
.max_occurences(*already_found_amounts.entry(p.0).or_default()),
|
||||||
abs_freq.entry(p.0).or_default();
|
|
||||||
}
|
}
|
||||||
}
|
trace!("absolute frequencies: {already_found_amounts:?}");
|
||||||
trace!("absolute frequencies: {abs_freq:?}");
|
|
||||||
state.finish_step(&abs_freq);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,9 +87,6 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> {
|
||||||
.filter(|solution_candidate| {
|
.filter(|solution_candidate| {
|
||||||
if !game.responses().is_empty()
|
if !game.responses().is_empty()
|
||||||
&& !state.has_all_known_contained(&solution_candidate.0)
|
&& !state.has_all_known_contained(&solution_candidate.0)
|
||||||
// we need these sometimes,
|
|
||||||
// because we can't just input gibberish
|
|
||||||
//&& !state.has_wrong_chars(&solution_candidate.0)
|
|
||||||
{
|
{
|
||||||
trace!("known cont:{:#?}", state.get_all_known_contained());
|
trace!("known cont:{:#?}", state.get_all_known_contained());
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -33,17 +33,10 @@ impl SolverState {
|
||||||
&mut self.char_map
|
&mut self.char_map
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_all_known_bad(&self) -> Vec<(&char, &CharInfo)> {
|
|
||||||
self.char_map
|
|
||||||
.iter()
|
|
||||||
.filter(|(_key, value)| value.not_in_solution())
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_all_known_contained(&self) -> Vec<(&char, &CharInfo)> {
|
pub(crate) fn get_all_known_contained(&self) -> Vec<(&char, &CharInfo)> {
|
||||||
self.char_map
|
self.char_map
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_key, value)| value.known_part_of_solution())
|
.filter(|(key, value)| value.part_of_solution())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,37 +48,6 @@ impl SolverState {
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn start_step(&mut self) {}
|
|
||||||
|
|
||||||
pub(crate) fn finish_step(&mut self, abs_freq: &HashMap<char, usize>) {
|
|
||||||
for (k, v) in abs_freq {
|
|
||||||
if *v == 0 {
|
|
||||||
self.char_map
|
|
||||||
.get_mut(k)
|
|
||||||
.expect(
|
|
||||||
"char in abs_freq was not added to the char_map before finalizing the step",
|
|
||||||
)
|
|
||||||
.max_occurences(0);
|
|
||||||
} else {
|
|
||||||
self.char_map
|
|
||||||
.get_mut(k)
|
|
||||||
.expect(
|
|
||||||
"char in abs_freq was not added to the char_map before finalizing the step",
|
|
||||||
)
|
|
||||||
.min_occurences(*v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn has_wrong_chars(&self, guess: &Word) -> bool {
|
|
||||||
for needed_char in self.get_all_known_bad() {
|
|
||||||
if guess.contains(*needed_char.0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CharInfo {
|
impl CharInfo {
|
||||||
|
@ -115,12 +77,7 @@ impl CharInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn not_in_solution(&self) -> bool {
|
pub fn part_of_solution(&self) -> bool {
|
||||||
self.occurences_amount.end == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn known_part_of_solution(&self) -> bool {
|
|
||||||
self.occurences_amount.start > 0 && self.occurences_amount.end > 0
|
self.occurences_amount.start > 0 && self.occurences_amount.end > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +129,7 @@ impl CharInfo {
|
||||||
|
|
||||||
impl Debug for CharInfo {
|
impl Debug for CharInfo {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
if !self.not_in_solution() {
|
if self.part_of_solution() {
|
||||||
f.debug_struct("CharInfo")
|
f.debug_struct("CharInfo")
|
||||||
.field("correct_idxs", &self.confirmed_indexes)
|
.field("correct_idxs", &self.confirmed_indexes)
|
||||||
.field("amnt_occ", &self.occurences_amount)
|
.field("amnt_occ", &self.occurences_amount)
|
||||||
|
|
135
tests/game.rs
135
tests/game.rs
|
@ -1,135 +0,0 @@
|
||||||
use test_log::test; // set the log level with an envvar: `RUST_LOG=trace cargo test`
|
|
||||||
|
|
||||||
use libpt::log::info;
|
|
||||||
use wordle_analyzer::game::evaluation::Evaluation;
|
|
||||||
use wordle_analyzer::wlist::builtin::BuiltinWList;
|
|
||||||
use wordle_analyzer::wlist::WordList;
|
|
||||||
|
|
||||||
use wordle_analyzer::wlist::word::Word;
|
|
||||||
use wordle_analyzer::{self, game};
|
|
||||||
|
|
||||||
fn wordlist() -> impl WordList {
|
|
||||||
BuiltinWList::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_eval_simple() -> anyhow::Result<()> {
|
|
||||||
let wl = wordlist();
|
|
||||||
let builder = game::Game::builder(&wl)
|
|
||||||
.length(5)
|
|
||||||
.max_steps(6)
|
|
||||||
.solution(Some(wl.get_word(&Word::from("crate")).unwrap()))
|
|
||||||
.precompute(false);
|
|
||||||
|
|
||||||
let mut game = builder.build()?;
|
|
||||||
let guess = Word::from("slate");
|
|
||||||
game.guess(&guess, None)?;
|
|
||||||
let correct = Evaluation::build(&guess, "xxccc")?;
|
|
||||||
info!(
|
|
||||||
"{} =? {}",
|
|
||||||
*game.last_response().unwrap().evaluation(),
|
|
||||||
correct
|
|
||||||
);
|
|
||||||
assert_eq!(*game.last_response().unwrap().evaluation(), correct);
|
|
||||||
|
|
||||||
let mut game = builder.build()?;
|
|
||||||
let guess = Word::from("about");
|
|
||||||
game.guess(&guess, None)?;
|
|
||||||
let correct = Evaluation::build(&guess, "fxxxf")?;
|
|
||||||
info!(
|
|
||||||
"{} =? {}",
|
|
||||||
*game.last_response().unwrap().evaluation(),
|
|
||||||
correct
|
|
||||||
);
|
|
||||||
assert_eq!(*game.last_response().unwrap().evaluation(), correct);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_eval_reoccuring_char0() -> anyhow::Result<()> {
|
|
||||||
let wl = wordlist();
|
|
||||||
let builder = game::Game::builder(&wl)
|
|
||||||
.solution(Some(wl.get_word(&Word::from("nines")).unwrap()))
|
|
||||||
.precompute(false);
|
|
||||||
info!("solution=nines");
|
|
||||||
|
|
||||||
let mut game = builder.build()?;
|
|
||||||
let guess = Word::from("pines");
|
|
||||||
game.guess(&guess, None)?;
|
|
||||||
let correct = Evaluation::build(&guess, "xcccc")?;
|
|
||||||
info!(
|
|
||||||
"{} =? {}",
|
|
||||||
*game.last_response().unwrap().evaluation(),
|
|
||||||
correct
|
|
||||||
);
|
|
||||||
assert_eq!(*game.last_response().unwrap().evaluation(), correct);
|
|
||||||
|
|
||||||
let mut game = builder.build()?;
|
|
||||||
let guess = Word::from("sides");
|
|
||||||
game.guess(&guess, None)?;
|
|
||||||
let correct = Evaluation::build(&guess, "xcxcc")?;
|
|
||||||
info!(
|
|
||||||
"{} =? {}",
|
|
||||||
*game.last_response().unwrap().evaluation(),
|
|
||||||
correct
|
|
||||||
);
|
|
||||||
assert_eq!(*game.last_response().unwrap().evaluation(), correct);
|
|
||||||
|
|
||||||
let mut game = builder.build()?;
|
|
||||||
let guess = Word::from("ninja");
|
|
||||||
game.guess(&guess, None)?;
|
|
||||||
let correct = Evaluation::build(&guess, "cccxx")?;
|
|
||||||
info!(
|
|
||||||
"{} =? {}",
|
|
||||||
*game.last_response().unwrap().evaluation(),
|
|
||||||
correct
|
|
||||||
);
|
|
||||||
assert_eq!(*game.last_response().unwrap().evaluation(), correct);
|
|
||||||
|
|
||||||
let mut game = builder.build()?;
|
|
||||||
let guess = Word::from("which");
|
|
||||||
game.guess(&guess, None)?;
|
|
||||||
let correct = Evaluation::build(&guess, "xxfxx")?;
|
|
||||||
info!(
|
|
||||||
"{} =? {}",
|
|
||||||
*game.last_response().unwrap().evaluation(),
|
|
||||||
correct
|
|
||||||
);
|
|
||||||
assert_eq!(*game.last_response().unwrap().evaluation(), correct);
|
|
||||||
|
|
||||||
let mut game = builder.build()?;
|
|
||||||
let guess = Word::from("indie");
|
|
||||||
game.guess(&guess, None)?;
|
|
||||||
let correct = Evaluation::build(&guess, "ffxxf")?;
|
|
||||||
info!(
|
|
||||||
"{} =? {}",
|
|
||||||
*game.last_response().unwrap().evaluation(),
|
|
||||||
correct
|
|
||||||
);
|
|
||||||
assert_eq!(*game.last_response().unwrap().evaluation(), correct);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_eval_reoccuring_char1() -> anyhow::Result<()> {
|
|
||||||
let wl = wordlist();
|
|
||||||
let builder = game::Game::builder(&wl)
|
|
||||||
.solution(Some(wl.get_word(&Word::from("fatty")).unwrap()))
|
|
||||||
.precompute(false);
|
|
||||||
info!("solution=fatty");
|
|
||||||
|
|
||||||
let mut game = builder.build()?;
|
|
||||||
let guess = Word::from("state");
|
|
||||||
game.guess(&guess, None)?;
|
|
||||||
let correct = Evaluation::build(&guess, "xffcx")?;
|
|
||||||
info!(
|
|
||||||
"{} =? {}",
|
|
||||||
*game.last_response().unwrap().evaluation(),
|
|
||||||
correct
|
|
||||||
);
|
|
||||||
assert_eq!(*game.last_response().unwrap().evaluation(), correct);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,12 +1,9 @@
|
||||||
use test_log::test; // set the log level with an envvar: `RUST_LOG=trace cargo test`
|
use test_log::test; // set the log level with an envvar: `RUST_LOG=trace cargo test`
|
||||||
|
|
||||||
use wordle_analyzer::game::Game;
|
|
||||||
use wordle_analyzer::solve::{AnyBuiltinSolver, NaiveSolver, Solver, StupidSolver};
|
use wordle_analyzer::solve::{AnyBuiltinSolver, NaiveSolver, Solver, StupidSolver};
|
||||||
use wordle_analyzer::wlist::builtin::BuiltinWList;
|
use wordle_analyzer::wlist::builtin::BuiltinWList;
|
||||||
use wordle_analyzer::wlist::WordList;
|
use wordle_analyzer::wlist::WordList;
|
||||||
|
|
||||||
use rayon::prelude::*;
|
|
||||||
|
|
||||||
fn wordlist() -> impl WordList {
|
fn wordlist() -> impl WordList {
|
||||||
BuiltinWList::default()
|
BuiltinWList::default()
|
||||||
}
|
}
|
||||||
|
@ -19,19 +16,3 @@ fn test_build_builtin_solvers() {
|
||||||
let _naive_solver =
|
let _naive_solver =
|
||||||
AnyBuiltinSolver::Naive(NaiveSolver::build(&wl).expect("could not build naive solver"));
|
AnyBuiltinSolver::Naive(NaiveSolver::build(&wl).expect("could not build naive solver"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_naive_win_games() -> anyhow::Result<()> {
|
|
||||||
let wl = wordlist();
|
|
||||||
let sl =
|
|
||||||
AnyBuiltinSolver::Naive(NaiveSolver::build(&wl).expect("could not build naive solver"));
|
|
||||||
let builder = Game::builder(&wl);
|
|
||||||
|
|
||||||
{ 0..50 }.into_par_iter().for_each(|_round| {
|
|
||||||
let mut game = builder.build().expect("could not make game");
|
|
||||||
sl.play(&mut game).expect("could not play game");
|
|
||||||
assert!(game.finished());
|
|
||||||
assert!(game.won());
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue