Rhai
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/target
|
||||
.DS_Store
|
||||
277
Cargo.lock
generated
277
Cargo.lock
generated
@@ -1,6 +1,26 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"const-random",
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
@@ -56,6 +76,12 @@ version = "1.0.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -120,6 +146,35 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom 0.2.12",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-error"
|
||||
version = "0.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efcdb2972eb64230b4c50646d8498ff73f5128d196a90c7236eec4cbe8619b8f"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.5"
|
||||
@@ -145,12 +200,30 @@ version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.12"
|
||||
@@ -162,12 +235,44 @@ dependencies = [
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.12.1"
|
||||
@@ -179,9 +284,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
version = "0.2.177"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
@@ -203,15 +314,50 @@ dependencies = [
|
||||
"itertools",
|
||||
"rand",
|
||||
"rayon",
|
||||
"rhai",
|
||||
"termion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "no-std-compat"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numtoa"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
@@ -236,6 +382,12 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
@@ -263,7 +415,7 @@ version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.2.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -301,6 +453,67 @@ version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"
|
||||
|
||||
[[package]]
|
||||
name = "rhai"
|
||||
version = "1.23.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527390cc333a8d2cd8237890e15c36518c26f8b54c903d86fc59f42f08d25594"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bitflags 2.4.2",
|
||||
"core-error",
|
||||
"hashbrown",
|
||||
"instant",
|
||||
"libm",
|
||||
"no-std-compat",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"rhai_codegen",
|
||||
"smallvec",
|
||||
"smartstring",
|
||||
"thin-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_codegen"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4322a2a4e8cf30771dd9f27f7f37ca9ac8fe812dddd811096a98483080dabe6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "smartstring"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"static_assertions",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.0"
|
||||
@@ -330,6 +543,21 @@ dependencies = [
|
||||
"redox_termios",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thin-vec"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d"
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
@@ -342,12 +570,27 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
@@ -413,3 +656,29 @@ name = "windows_x86_64_msvc"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
@@ -9,4 +9,13 @@ clap = { version = "4.5.1", features = ["derive"] }
|
||||
itertools = "0.12.1"
|
||||
rand = "0.8.5"
|
||||
rayon = "1.9.0"
|
||||
rhai = { version = "1.23.4", features = [
|
||||
"sync",
|
||||
"no_time",
|
||||
"no_module",
|
||||
"no_custom_syntax",
|
||||
"no_std",
|
||||
"only_i64",
|
||||
"f32_float",
|
||||
] }
|
||||
termion = "3.0.0"
|
||||
|
||||
133
src/agents.rhai/chase.rhai
Normal file
133
src/agents.rhai/chase.rhai
Normal file
@@ -0,0 +1,133 @@
|
||||
fn random_action(board) {
|
||||
let symb = rand_symb();
|
||||
let pos = rand_int(0, 10);
|
||||
let action = Action(symb, pos);
|
||||
|
||||
while !board.can_play(action) {
|
||||
let symb = rand_symb();
|
||||
let pos = rand_int(0, 10);
|
||||
action = Action(symb, pos);
|
||||
}
|
||||
|
||||
return action
|
||||
}
|
||||
|
||||
|
||||
/// Returns an array of (idx, influence) for each empty slot in the board.
|
||||
/// - idx is the index of this slot
|
||||
/// - f32 is the influence of this slot
|
||||
fn free_slots_by_influence(board) {
|
||||
// TODO: EDGE CASE
|
||||
// We fail if we have ___/-_ (div by zero)
|
||||
|
||||
// Fill all empty slots with fives and compute starting value
|
||||
//
|
||||
// This should always result in an evaluatable expression,
|
||||
// since parenthesis do not exist.
|
||||
// The only way to divide by zero is by doing something like /(5-2+3).
|
||||
let filled = board;
|
||||
for i in filled.free_spots_idx() {
|
||||
filled[i] = 5;
|
||||
}
|
||||
|
||||
let base = filled.evaluate();
|
||||
|
||||
// Test each slot:
|
||||
// Increase its value by 1, and record the effect on the
|
||||
// expression's total value.
|
||||
// This isn't a perfect metric, but it's pretty good.
|
||||
let slots = [];
|
||||
for i in 0..board.size() {
|
||||
let slot = board[i];
|
||||
if slot != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
let b = filled;
|
||||
b[i] = 6;
|
||||
|
||||
if b.evaluate() == () {
|
||||
print(b)
|
||||
}
|
||||
|
||||
slots.push([i, b.evaluate() - base]);
|
||||
}
|
||||
|
||||
slots.sort(|a, b| b[0].abs() - a[0].abs());
|
||||
return slots;
|
||||
}
|
||||
|
||||
// Main step function (shared between min and max)
|
||||
fn chase_step(board, minimize) {
|
||||
let available_numbers = {
|
||||
let available = [];
|
||||
for i in 0..10 {
|
||||
if !board.contains(i) {
|
||||
available.push(i);
|
||||
}
|
||||
}
|
||||
available
|
||||
};
|
||||
|
||||
// For the code below, we must guarantee that
|
||||
// min_slots + max_slots <= available_numbers.len
|
||||
let n_free = board.free_spots();
|
||||
if available_numbers.len() < n_free || n_free >= 10 {
|
||||
return random_action(board);
|
||||
}
|
||||
|
||||
let slots = free_slots_by_influence(board);
|
||||
if slots.len() == 0 {
|
||||
return random_action(board);
|
||||
}
|
||||
|
||||
|
||||
// Get the most influential position
|
||||
let pos = slots[0][0];
|
||||
let val = slots[0][1];
|
||||
|
||||
// Choose next number if we can't make the move.
|
||||
// Prevents division by zero.
|
||||
// This isn't perfect, and may fail if we run out of numbers
|
||||
// (This is, however, very unlikely)
|
||||
let selected_symbol = ();
|
||||
let offset = 0;
|
||||
while selected_symbol == () || offset < available_numbers.len() {
|
||||
selected_symbol = {
|
||||
if minimize {
|
||||
if val >= 0.0 {
|
||||
available_numbers[offset]
|
||||
} else {
|
||||
available_numbers[available_numbers.len() - 1 - offset]
|
||||
}
|
||||
} else {
|
||||
if val <= 0.0 {
|
||||
available_numbers[offset]
|
||||
} else {
|
||||
available_numbers[available_numbers.len() - 1 - offset]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let action = Action(selected_symbol, pos);
|
||||
if board.can_play(action) {
|
||||
return action;
|
||||
}
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
// Fallback to random if we can't find a valid move
|
||||
return random_action(board);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Minimizer step
|
||||
fn step_min(board) {
|
||||
chase_step(board, true)
|
||||
}
|
||||
|
||||
// Maximizer step
|
||||
fn step_max(board) {
|
||||
chase_step(board, false)
|
||||
}
|
||||
109
src/agents.rhai/diffuse.rhai
Normal file
109
src/agents.rhai/diffuse.rhai
Normal file
@@ -0,0 +1,109 @@
|
||||
fn random_action(board) {
|
||||
let symb = rand_symb();
|
||||
let pos = rand_int(0, 10);
|
||||
let action = Action(symb, pos);
|
||||
|
||||
while !board.can_play(action) {
|
||||
let symb = rand_symb();
|
||||
let pos = rand_int(0, 10);
|
||||
action = Action(symb, pos);
|
||||
}
|
||||
|
||||
return action
|
||||
}
|
||||
|
||||
fn step_symb(board, symb) {
|
||||
if board.contains(symb) {
|
||||
print("ERROR: called `step_symb` with a symbol that's already on the board!");
|
||||
return random_action(board);
|
||||
}
|
||||
|
||||
let board_size = board.size();
|
||||
let dist = [];
|
||||
|
||||
// Initialize distance array with large values (board size + 1)
|
||||
for i in 0..11 {
|
||||
dist.push(board_size + 1);
|
||||
}
|
||||
|
||||
// Set boundary conditions
|
||||
dist[0] = 1;
|
||||
dist[10] = 1;
|
||||
|
||||
// Set distances to 0 for positions with operators
|
||||
for i in 0..11 {
|
||||
let cell = board[i];
|
||||
if cell != () && cell.is_op() {
|
||||
dist[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Diffusion algorithm - propagate distances
|
||||
let did_something = true;
|
||||
while did_something {
|
||||
did_something = false;
|
||||
for i in 1..10 {
|
||||
let left_dist = dist[i - 1];
|
||||
let right_dist = dist[i + 1];
|
||||
let new_dist = min(left_dist + 1, right_dist + 1);
|
||||
|
||||
if new_dist < dist[i] {
|
||||
did_something = true;
|
||||
dist[i] = new_dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find maximum distance
|
||||
let max_dist = 0;
|
||||
for d in dist {
|
||||
if d > max_dist {
|
||||
max_dist = d;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to place at positions with maximum distance
|
||||
loop {
|
||||
for pos in 0..11 {
|
||||
if dist[pos] >= max_dist {
|
||||
let action = Action(symb, pos);
|
||||
if board.can_play(action) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if max_dist == 0 {
|
||||
return random_action(board);
|
||||
}
|
||||
|
||||
max_dist -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn step_min(board) {
|
||||
let operators = ["+", "-", "*", "/"];
|
||||
operators = rand_shuffle(operators);
|
||||
|
||||
for op in operators {
|
||||
if !board.contains(op) {
|
||||
return step_symb(board, op);
|
||||
}
|
||||
}
|
||||
|
||||
return random_action(board);
|
||||
}
|
||||
|
||||
fn step_max(board) {
|
||||
let operators = ["+", "-", "*", "/"];
|
||||
operators = rand_shuffle(operators);
|
||||
|
||||
for op in operators {
|
||||
if !board.contains(op) {
|
||||
return step_symb(board, op);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: chase
|
||||
return random_action(board);
|
||||
}
|
||||
21
src/agents.rhai/random.rhai
Normal file
21
src/agents.rhai/random.rhai
Normal file
@@ -0,0 +1,21 @@
|
||||
fn random_action(board) {
|
||||
let symb = rand_symb();
|
||||
let pos = rand_int(0, 10);
|
||||
let action = Action(symb, pos);
|
||||
|
||||
while !board.can_play(action) {
|
||||
let symb = rand_symb();
|
||||
let pos = rand_int(0, 10);
|
||||
action = Action(symb, pos);
|
||||
}
|
||||
|
||||
return action
|
||||
}
|
||||
|
||||
fn step_min(board) {
|
||||
random_action(board)
|
||||
}
|
||||
|
||||
fn step_max(board) {
|
||||
random_action(board)
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
use std::{cmp::Ordering, iter};
|
||||
|
||||
use anyhow::Result;
|
||||
use itertools::Itertools;
|
||||
use rand::{seq::SliceRandom, thread_rng};
|
||||
use rayon::iter::{ParallelBridge, ParallelIterator};
|
||||
use std::{cmp::Ordering, iter};
|
||||
|
||||
use super::{Agent, Chase, MaximizerAgent, MinimizerAgent};
|
||||
use super::{Agent, MaximizerAgent, MinimizerAgent};
|
||||
use crate::{
|
||||
agents::{util::best_board_noop, Diffuse},
|
||||
agents::util::best_board_noop,
|
||||
board::{Board, PlayerAction},
|
||||
util::{Player, Symb},
|
||||
};
|
||||
@@ -28,11 +27,8 @@ impl Brutus {
|
||||
.collect_vec();
|
||||
|
||||
if symbols.is_empty() {
|
||||
return if minimize {
|
||||
Chase::new(self.player).step_min(board)
|
||||
} else {
|
||||
Chase::new(self.player).step_max(board)
|
||||
};
|
||||
// TODO: only valid (chase)
|
||||
return Ok(PlayerAction::new_random(&mut rand::thread_rng(), board));
|
||||
}
|
||||
|
||||
// Number of free slots
|
||||
@@ -86,11 +82,8 @@ impl Brutus {
|
||||
// TODO: why can `items` be empty?
|
||||
// We shouldn't need this escape hatch
|
||||
if items.is_empty() {
|
||||
return if minimize {
|
||||
Diffuse::new(self.player).step_min(board)
|
||||
} else {
|
||||
Diffuse::new(self.player).step_max(board)
|
||||
};
|
||||
// TODO: only valid (diffuse)
|
||||
return Ok(PlayerAction::new_random(&mut rand::thread_rng(), board));
|
||||
}
|
||||
|
||||
let (t, _) = items.first().unwrap();
|
||||
@@ -111,11 +104,8 @@ impl Brutus {
|
||||
|
||||
// Final escape hatch, if we didn't decide to place any symbols
|
||||
// (which is possible, since we add one to free_spots above!)
|
||||
if minimize {
|
||||
Chase::new(self.player).step_min(board)
|
||||
} else {
|
||||
Chase::new(self.player).step_max(board)
|
||||
}
|
||||
// TODO: only valid (chase)
|
||||
return Ok(PlayerAction::new_random(&mut rand::thread_rng(), board));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
use anyhow::{bail, Result};
|
||||
use std::num::NonZeroU8;
|
||||
|
||||
use super::{Agent, MaximizerAgent, MinimizerAgent, Random};
|
||||
use crate::{
|
||||
agents::util::free_slots_by_influence,
|
||||
board::{Board, PlayerAction},
|
||||
util::{Player, Symb},
|
||||
};
|
||||
|
||||
pub struct Chase {
|
||||
player: Player,
|
||||
}
|
||||
|
||||
impl Chase {
|
||||
pub fn new(player: Player) -> Self {
|
||||
Self { player }
|
||||
}
|
||||
|
||||
fn step(&mut self, board: &Board, minimize: bool) -> Result<PlayerAction> {
|
||||
let available_numbers = (0..=9)
|
||||
.map(|x| match x {
|
||||
0 => Symb::Zero,
|
||||
x => Symb::Number(NonZeroU8::new(x).unwrap()),
|
||||
})
|
||||
.filter(|x| !board.contains(*x))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// For the code below, we must guarantee that
|
||||
// min_slots + max_slots <= available_numbers.len
|
||||
let n_free = board.get_board().iter().filter(|x| x.is_none()).count();
|
||||
if available_numbers.len() < n_free || n_free >= 10 {
|
||||
return Random::new(self.player).step_min(board);
|
||||
}
|
||||
|
||||
let t = free_slots_by_influence(board);
|
||||
if t.is_none() {
|
||||
bail!("could not compute next move!")
|
||||
}
|
||||
let t = t.unwrap();
|
||||
|
||||
if t.is_empty() {
|
||||
return Random::new(self.player).step_min(board);
|
||||
}
|
||||
|
||||
let (pos, val) = t[0];
|
||||
|
||||
// Choose next number if we can't make the a move.
|
||||
// Prevents division by zero.
|
||||
// This isn't perfect, and may fail if we run out of numbers
|
||||
// (This is, however, very unlikely)
|
||||
let mut symb = None;
|
||||
let mut offset = 0;
|
||||
while symb.is_none()
|
||||
|| !board.can_play(&PlayerAction {
|
||||
symb: symb.unwrap(),
|
||||
pos,
|
||||
}) {
|
||||
symb = Some({
|
||||
if minimize {
|
||||
if val >= 0.0 {
|
||||
available_numbers[offset]
|
||||
} else {
|
||||
available_numbers[available_numbers.len() - 1 - offset]
|
||||
}
|
||||
} else if val <= 0.0 {
|
||||
available_numbers[offset]
|
||||
} else {
|
||||
available_numbers[available_numbers.len() - 1 - offset]
|
||||
}
|
||||
});
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
Ok(PlayerAction {
|
||||
symb: symb.unwrap(),
|
||||
pos,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Agent for Chase {
|
||||
fn name(&self) -> &'static str {
|
||||
"Chase"
|
||||
}
|
||||
|
||||
fn player(&self) -> Player {
|
||||
self.player
|
||||
}
|
||||
}
|
||||
|
||||
impl MinimizerAgent for Chase {
|
||||
fn step_min(&mut self, board: &Board) -> Result<PlayerAction> {
|
||||
self.step(board, true)
|
||||
}
|
||||
}
|
||||
|
||||
impl MaximizerAgent for Chase {
|
||||
fn step_max(&mut self, board: &Board) -> Result<PlayerAction> {
|
||||
self.step(board, false)
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use rand::{seq::SliceRandom, thread_rng};
|
||||
|
||||
use super::{Agent, Chase, MaximizerAgent, MinimizerAgent, Random};
|
||||
use crate::{
|
||||
board::{Board, PlayerAction},
|
||||
util::{Player, Symb},
|
||||
};
|
||||
|
||||
/// A simple "operator diffusion" MINIMIZER agent.
|
||||
///
|
||||
/// Tries to keep operators as far apart as possible, denying large numbers.
|
||||
/// Places numbers using the same algorithm as chase.
|
||||
pub struct Diffuse {
|
||||
player: Player,
|
||||
}
|
||||
|
||||
impl Diffuse {
|
||||
pub fn new(player: Player) -> Self {
|
||||
Self { player }
|
||||
}
|
||||
|
||||
/// Place a symbol on the board.
|
||||
/// Assumes `symb` is not already on the board
|
||||
fn step_symb(&self, board: &Board, symb: Symb) -> PlayerAction {
|
||||
if board.contains(symb) {
|
||||
panic!("Called `step_symb` with a symbol that's already on the board!")
|
||||
}
|
||||
|
||||
// Fill distance array with largest possible value
|
||||
let mut dist = [board.size() + 1; 11];
|
||||
|
||||
// Set up initial distances
|
||||
dist[0] = 1;
|
||||
*dist.last_mut().unwrap() = 1;
|
||||
for (i, o) in board.get_board().iter().enumerate() {
|
||||
if let Some(s) = o {
|
||||
if s.is_op() {
|
||||
dist[i] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut did_something = true;
|
||||
while did_something {
|
||||
did_something = false;
|
||||
for i in 1..(dist.len() - 1) {
|
||||
let l = dist[i - 1];
|
||||
let r = dist[i + 1];
|
||||
|
||||
let new = (l + 1).min(r + 1);
|
||||
if new < dist[i] {
|
||||
did_something = true;
|
||||
dist[i] = new;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut max_dist = *dist.iter().max().unwrap();
|
||||
|
||||
loop {
|
||||
for (pos, d) in dist.iter().enumerate() {
|
||||
if *d >= max_dist {
|
||||
let action = PlayerAction { symb, pos };
|
||||
if board.can_play(&action) {
|
||||
return action;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if max_dist == 0 {
|
||||
return Random::new(self.player).step_max(board).unwrap();
|
||||
}
|
||||
|
||||
max_dist -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Agent for Diffuse {
|
||||
fn name(&self) -> &'static str {
|
||||
"Diffuse"
|
||||
}
|
||||
|
||||
fn player(&self) -> Player {
|
||||
self.player
|
||||
}
|
||||
}
|
||||
|
||||
impl MinimizerAgent for Diffuse {
|
||||
fn step_min(&mut self, board: &Board) -> Result<PlayerAction> {
|
||||
let mut x = [Symb::Minus, Symb::Times, Symb::Plus, Symb::Div];
|
||||
x.shuffle(&mut thread_rng());
|
||||
let symb = x.iter().find(|x| !board.contains(**x));
|
||||
|
||||
if let Some(symb) = symb {
|
||||
Ok(self.step_symb(board, *symb))
|
||||
} else {
|
||||
// No symbols available, play a random number
|
||||
Chase::new(self.player).step_min(board)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MaximizerAgent for Diffuse {
|
||||
fn step_max(&mut self, board: &Board) -> Result<PlayerAction> {
|
||||
let mut x = [Symb::Minus, Symb::Times, Symb::Plus, Symb::Div];
|
||||
x.shuffle(&mut thread_rng());
|
||||
let symb = x.iter().find(|x| !board.contains(**x));
|
||||
|
||||
if let Some(symb) = symb {
|
||||
Ok(self.step_symb(board, *symb))
|
||||
} else {
|
||||
// No symbols available, play a random number
|
||||
Chase::new(self.player).step_max(board)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,7 +107,7 @@ impl Human {
|
||||
// Cursor
|
||||
" ".repeat(self.cursor),
|
||||
color::Fg(self.player.color()),
|
||||
if board.is_done() {
|
||||
if board.is_full() {
|
||||
' '
|
||||
} else {
|
||||
self.symbol_selector.current()
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
mod brutus;
|
||||
mod chase;
|
||||
mod diffuse;
|
||||
mod human;
|
||||
mod random;
|
||||
mod rhai;
|
||||
pub mod util;
|
||||
|
||||
pub use brutus::Brutus;
|
||||
pub use chase::Chase;
|
||||
pub use diffuse::Diffuse;
|
||||
pub use human::Human;
|
||||
pub use random::Random;
|
||||
pub use rhai::Rhai;
|
||||
|
||||
use crate::{
|
||||
board::{Board, PlayerAction},
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
use crate::{
|
||||
board::{Board, PlayerAction},
|
||||
util::{Player, Symb},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use rand::Rng;
|
||||
use std::num::NonZeroU8;
|
||||
|
||||
use super::{Agent, MaximizerAgent, MinimizerAgent};
|
||||
|
||||
pub struct Random {
|
||||
player: Player,
|
||||
}
|
||||
|
||||
impl Random {
|
||||
pub fn new(player: Player) -> Self {
|
||||
Self { player }
|
||||
}
|
||||
|
||||
fn random_action(&self, board: &Board) -> PlayerAction {
|
||||
let mut rng = rand::thread_rng();
|
||||
let n = board.size();
|
||||
|
||||
let pos = rng.gen_range(0..n);
|
||||
let symb = match rng.gen_range(0..4) {
|
||||
0 => {
|
||||
let n = rng.gen_range(0..=9);
|
||||
if n == 0 {
|
||||
Symb::Zero
|
||||
} else {
|
||||
Symb::Number(NonZeroU8::new(n).unwrap())
|
||||
}
|
||||
}
|
||||
1 => Symb::Div,
|
||||
2 => Symb::Minus,
|
||||
3 => Symb::Plus,
|
||||
4 => Symb::Times,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
PlayerAction { symb, pos }
|
||||
}
|
||||
}
|
||||
|
||||
impl Agent for Random {
|
||||
fn name(&self) -> &'static str {
|
||||
"Random"
|
||||
}
|
||||
|
||||
fn player(&self) -> Player {
|
||||
self.player
|
||||
}
|
||||
}
|
||||
|
||||
impl MinimizerAgent for Random {
|
||||
fn step_min(&mut self, board: &Board) -> Result<PlayerAction> {
|
||||
let mut action = self.random_action(board);
|
||||
while !board.can_play(&action) {
|
||||
action = self.random_action(board);
|
||||
}
|
||||
Ok(action)
|
||||
}
|
||||
}
|
||||
|
||||
impl MaximizerAgent for Random {
|
||||
fn step_max(&mut self, board: &Board) -> Result<PlayerAction> {
|
||||
let mut action = self.random_action(board);
|
||||
while !board.can_play(&action) {
|
||||
action = self.random_action(board);
|
||||
}
|
||||
Ok(action)
|
||||
}
|
||||
}
|
||||
176
src/agents/rhai.rs
Normal file
176
src/agents/rhai.rs
Normal file
@@ -0,0 +1,176 @@
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use itertools::{Itertools, Permutations};
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use rhai::{
|
||||
packages::{
|
||||
ArithmeticPackage, BasicArrayPackage, BasicFnPackage, BasicIteratorPackage,
|
||||
BasicMathPackage, BasicStringPackage, LanguageCorePackage, LogicPackage, MoreStringPackage,
|
||||
Package,
|
||||
},
|
||||
CustomType, Dynamic, Engine, EvalAltResult, Position, Scope, TypeBuilder, AST,
|
||||
};
|
||||
use std::{sync::Arc, vec::IntoIter};
|
||||
|
||||
use super::{Agent, MaximizerAgent, MinimizerAgent};
|
||||
use crate::{
|
||||
board::{Board, PlayerAction},
|
||||
util::{Player, Symb},
|
||||
};
|
||||
|
||||
pub struct RhaiPer<T: Clone, I: Iterator<Item = T>> {
|
||||
inner: Arc<Permutations<I>>,
|
||||
}
|
||||
|
||||
impl<T: Clone, I: Clone + Iterator<Item = T>> IntoIterator for RhaiPer<T, I> {
|
||||
type Item = Vec<T>;
|
||||
type IntoIter = Permutations<I>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
(*self.inner).clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone, I: Iterator<Item = T>> Clone for RhaiPer<T, I> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Send + Sync + 'static, I: Clone + Iterator<Item = T> + Sync + Send + 'static>
|
||||
CustomType for RhaiPer<T, I>
|
||||
{
|
||||
fn build(mut builder: TypeBuilder<Self>) {
|
||||
builder
|
||||
.with_name("Perutations")
|
||||
.is_iterable()
|
||||
.with_fn("to_string", |_s: &mut Self| "Permutation {}".to_owned())
|
||||
.with_fn("to_debug", |_s: &mut Self| "Permutation {}".to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
pub struct Rhai {
|
||||
player: Player,
|
||||
engine: Engine,
|
||||
script: AST,
|
||||
scope: Scope<'static>,
|
||||
}
|
||||
|
||||
impl Rhai {
|
||||
pub fn new(player: Player, script: &str) -> Self {
|
||||
let engine = {
|
||||
let mut engine = Engine::new_raw();
|
||||
|
||||
engine.set_max_expr_depths(100, 100);
|
||||
engine.set_max_strings_interned(1024);
|
||||
engine.on_print(|text| println!("{text}"));
|
||||
engine.on_debug(|text, source, pos| match (source, pos) {
|
||||
(Some(source), Position::NONE) => println!("{source} | {text}"),
|
||||
(Some(source), pos) => println!("{source} @ {pos:?} | {text}"),
|
||||
(None, Position::NONE) => println!("{text}"),
|
||||
(None, pos) => println!("{pos:?} | {text}"),
|
||||
});
|
||||
|
||||
LanguageCorePackage::new().register_into_engine(&mut engine);
|
||||
ArithmeticPackage::new().register_into_engine(&mut engine);
|
||||
BasicIteratorPackage::new().register_into_engine(&mut engine);
|
||||
LogicPackage::new().register_into_engine(&mut engine);
|
||||
BasicStringPackage::new().register_into_engine(&mut engine);
|
||||
MoreStringPackage::new().register_into_engine(&mut engine);
|
||||
BasicMathPackage::new().register_into_engine(&mut engine);
|
||||
BasicArrayPackage::new().register_into_engine(&mut engine);
|
||||
BasicFnPackage::new().register_into_engine(&mut engine);
|
||||
|
||||
engine
|
||||
.register_fn("rand_int", |from: i64, to: i64| {
|
||||
rand::thread_rng().gen_range(from..=to)
|
||||
})
|
||||
.register_fn("rand_bool", |p: f32| rand::thread_rng().gen_bool(p as f64))
|
||||
.register_fn("rand_symb", || {
|
||||
Symb::new_random(&mut rand::thread_rng()).to_string()
|
||||
})
|
||||
.register_fn("rand_action", |board: Board| {
|
||||
PlayerAction::new_random(&mut rand::thread_rng(), &board)
|
||||
})
|
||||
.register_fn("rand_shuffle", |mut vec: Vec<Dynamic>| {
|
||||
vec.shuffle(&mut rand::thread_rng());
|
||||
vec
|
||||
})
|
||||
.register_fn("is_op", |s: &str| {
|
||||
Symb::from_str(s).map(|x| x.is_op()).unwrap_or(false)
|
||||
})
|
||||
.register_fn(
|
||||
"permutations",
|
||||
|v: Vec<Dynamic>, size: i64| -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let size: usize = match size.try_into() {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return Err(format!("Invalid permutation size {size}").into());
|
||||
}
|
||||
};
|
||||
|
||||
let per = RhaiPer {
|
||||
inner: v.into_iter().permutations(size).into(),
|
||||
};
|
||||
|
||||
Ok(Dynamic::from(per))
|
||||
},
|
||||
);
|
||||
|
||||
engine
|
||||
.build_type::<Board>()
|
||||
.build_type::<PlayerAction>()
|
||||
.build_type::<RhaiPer<Dynamic, IntoIter<Dynamic>>>();
|
||||
engine
|
||||
};
|
||||
|
||||
let script = engine.compile(script).unwrap();
|
||||
let scope = Scope::new();
|
||||
|
||||
Self {
|
||||
player,
|
||||
engine,
|
||||
script,
|
||||
scope,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Agent for Rhai {
|
||||
fn name(&self) -> &'static str {
|
||||
"Rhai"
|
||||
}
|
||||
|
||||
fn player(&self) -> Player {
|
||||
self.player
|
||||
}
|
||||
}
|
||||
|
||||
impl MinimizerAgent for Rhai {
|
||||
fn step_min(&mut self, board: &Board) -> Result<PlayerAction> {
|
||||
let result = self
|
||||
.engine
|
||||
.call_fn::<PlayerAction>(&mut self.scope, &self.script, "step_min", (board.clone(),))
|
||||
.map_err(|x| anyhow!(x.to_string()))
|
||||
.context("while running rhai step_min")?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl MaximizerAgent for Rhai {
|
||||
fn step_max(&mut self, board: &Board) -> Result<PlayerAction> {
|
||||
let result = self
|
||||
.engine
|
||||
.call_fn::<PlayerAction>(&mut self.scope, &self.script, "step_max", (board.clone(),))
|
||||
.map_err(|x| anyhow!(x.to_string()))
|
||||
.context("while running rhai step_min")?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ use crate::{board::Board, util::Symb};
|
||||
/// - coords are the coordinate of this slot's partial
|
||||
/// - char_idx is the index of this slot in its partial
|
||||
/// - f32 is the influence of this slot
|
||||
pub fn free_slots_by_influence(board: &Board) -> Option<Vec<(usize, f32)>> {
|
||||
fn free_slots_by_influence(board: &Board) -> Option<Vec<(usize, f32)>> {
|
||||
// Fill all empty slots with fives and compute starting value
|
||||
let filled = {
|
||||
// This should always result in an evaluatable expression,
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
use anyhow::Result;
|
||||
use itertools::Itertools;
|
||||
use rhai::Array;
|
||||
use rhai::CustomType;
|
||||
use rhai::Dynamic;
|
||||
use rhai::EvalAltResult;
|
||||
use rhai::Position;
|
||||
use rhai::TypeBuilder;
|
||||
use std::fmt::{Debug, Display, Write};
|
||||
use termion::color::{self, Color};
|
||||
|
||||
@@ -117,7 +123,7 @@ impl Board {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_done(&self) -> bool {
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.free_spots == 0
|
||||
}
|
||||
|
||||
@@ -417,3 +423,167 @@ impl Board {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Board {
|
||||
type Item = String;
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.board
|
||||
.iter()
|
||||
.map(|x| x.map(|x| x.to_string()).unwrap_or_default())
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomType for Board {
|
||||
fn build(mut builder: TypeBuilder<Self>) {
|
||||
builder
|
||||
.with_name("Board")
|
||||
.is_iterable()
|
||||
.with_fn("to_string", |s: &mut Self| format!("{}", s))
|
||||
.with_fn("to_debug", |s: &mut Self| format!("{:?}", s))
|
||||
.with_fn("size", |s: &mut Self| s.board.len() as i64)
|
||||
.with_fn("len", |s: &mut Self| s.board.len() as i64)
|
||||
.with_fn("is_full", |s: &mut Self| s.is_full())
|
||||
.with_fn("free_spots", |s: &mut Self| s.free_spots)
|
||||
.with_fn("play", |s: &mut Self, act: PlayerAction| {
|
||||
s.play(act, Player::Red) // Player doesn't matter
|
||||
})
|
||||
.with_fn("ith_free_slot", |s: &mut Self, idx: usize| {
|
||||
s.ith_empty_slot(idx).map(|x| x as i64).unwrap_or(-1)
|
||||
})
|
||||
.with_fn("can_play", |s: &mut Self, act: PlayerAction| {
|
||||
s.can_play(&act)
|
||||
})
|
||||
.with_fn("contains", |s: &mut Self, sym: &str| {
|
||||
match Symb::from_str(sym) {
|
||||
None => false,
|
||||
Some(x) => s.contains(x),
|
||||
}
|
||||
})
|
||||
.with_fn("contains", |s: &mut Self, sym: i64| {
|
||||
let sym = sym.to_string();
|
||||
match Symb::from_str(&sym) {
|
||||
None => false,
|
||||
Some(x) => s.contains(x),
|
||||
}
|
||||
})
|
||||
.with_fn("evaluate", |s: &mut Self| -> Dynamic {
|
||||
s.evaluate().map(|x| x.into()).unwrap_or(().into())
|
||||
})
|
||||
.with_fn("free_spots_idx", |s: &mut Self| -> Array {
|
||||
s.board
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, x)| x.is_none())
|
||||
.map(|(i, _)| i as i64)
|
||||
.map(|x| x.into())
|
||||
.collect::<Vec<Dynamic>>()
|
||||
})
|
||||
.with_indexer_get(
|
||||
|s: &mut Self, idx: i64| -> Result<String, Box<EvalAltResult>> {
|
||||
if idx as usize >= s.board.len() {
|
||||
return Err(
|
||||
EvalAltResult::ErrorIndexNotFound(idx.into(), Position::NONE).into(),
|
||||
);
|
||||
}
|
||||
|
||||
let idx = idx as usize;
|
||||
return Ok(s.board[idx].map(|x| x.to_string()).unwrap_or_default());
|
||||
},
|
||||
)
|
||||
.with_indexer_set(
|
||||
|s: &mut Self, idx: i64, val: String| -> Result<(), Box<EvalAltResult>> {
|
||||
let idx: usize = match idx.try_into() {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return Err(EvalAltResult::ErrorIndexNotFound(
|
||||
idx.into(),
|
||||
Position::NONE,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
};
|
||||
|
||||
if idx >= s.board.len() {
|
||||
return Err(EvalAltResult::ErrorIndexNotFound(
|
||||
(idx as i64).into(),
|
||||
Position::NONE,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
match Symb::from_str(&val) {
|
||||
None => return Err(format!("Invalid symbol {val}").into()),
|
||||
Some(x) => {
|
||||
s.board[idx] = Some(x);
|
||||
s.placed_by[idx] = Some(Player::Red); // Arbitrary
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
},
|
||||
)
|
||||
.with_indexer_set(
|
||||
|s: &mut Self, idx: i64, _val: ()| -> Result<(), Box<EvalAltResult>> {
|
||||
let idx: usize = match idx.try_into() {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return Err(EvalAltResult::ErrorIndexNotFound(
|
||||
idx.into(),
|
||||
Position::NONE,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
};
|
||||
|
||||
if idx >= s.board.len() {
|
||||
return Err(EvalAltResult::ErrorIndexNotFound(
|
||||
(idx as i64).into(),
|
||||
Position::NONE,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
s.board[idx] = None;
|
||||
s.placed_by[idx] = None;
|
||||
|
||||
return Ok(());
|
||||
},
|
||||
)
|
||||
.with_indexer_set(
|
||||
|s: &mut Self, idx: i64, val: i64| -> Result<(), Box<EvalAltResult>> {
|
||||
let idx: usize = match idx.try_into() {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return Err(EvalAltResult::ErrorIndexNotFound(
|
||||
idx.into(),
|
||||
Position::NONE,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
};
|
||||
|
||||
if idx >= s.board.len() {
|
||||
return Err(EvalAltResult::ErrorIndexNotFound(
|
||||
(idx as i64).into(),
|
||||
Position::NONE,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
match Symb::from_str(&val.to_string()) {
|
||||
None => return Err(format!("Invalid symbol {val}").into()),
|
||||
Some(x) => {
|
||||
s.board[idx] = Some(x);
|
||||
s.placed_by[idx] = Some(Player::Red); // Arbitrary
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
mod board;
|
||||
mod tree;
|
||||
|
||||
use rand::Rng;
|
||||
use rhai::{CustomType, EvalAltResult, TypeBuilder};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub use board::Board;
|
||||
@@ -20,3 +22,56 @@ impl Display for PlayerAction {
|
||||
write!(f, "{} at {}", self.symb, self.pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl PlayerAction {
|
||||
pub fn new_random<R: Rng>(rng: &mut R, board: &Board) -> Self {
|
||||
let n = board.size();
|
||||
let pos = rng.gen_range(0..n);
|
||||
let symb = Symb::new_random(rng);
|
||||
PlayerAction { symb, pos }
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomType for PlayerAction {
|
||||
fn build(mut builder: TypeBuilder<Self>) {
|
||||
builder
|
||||
.with_name("Action")
|
||||
.with_fn(
|
||||
"Action",
|
||||
|symb: &str, pos: i64| -> Result<Self, Box<EvalAltResult>> {
|
||||
let symb = match Symb::from_str(symb) {
|
||||
Some(x) => x,
|
||||
None => return Err(format!("Invalid symbol {symb:?}").into()),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
symb,
|
||||
pos: pos as usize,
|
||||
})
|
||||
},
|
||||
)
|
||||
.with_fn(
|
||||
"Action",
|
||||
|symb: i64, pos: i64| -> Result<Self, Box<EvalAltResult>> {
|
||||
let symb = symb.to_string();
|
||||
let symb = match Symb::from_str(&symb) {
|
||||
Some(x) => x,
|
||||
None => return Err(format!("Invalid symbol {symb:?}").into()),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
symb,
|
||||
pos: pos as usize,
|
||||
})
|
||||
},
|
||||
)
|
||||
.with_fn("to_string", |s: &mut Self| -> String {
|
||||
format!("Action {{{} at {}}}", s.symb, s.pos)
|
||||
})
|
||||
.with_fn("to_debug", |s: &mut Self| -> String {
|
||||
format!("Action {{{} at {}}}", s.symb, s.pos)
|
||||
})
|
||||
.with_get("symb", |s: &mut Self| s.symb.to_string())
|
||||
.with_get("pos", |s: &mut Self| s.pos);
|
||||
}
|
||||
}
|
||||
|
||||
17
src/cli.rs
17
src/cli.rs
@@ -1,9 +1,8 @@
|
||||
use clap::{Parser, ValueEnum};
|
||||
use std::fmt::Display;
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
|
||||
use crate::{
|
||||
agents::{Brutus, Chase, Diffuse, Human, MaximizerAgent, MinimizerAgent, Random},
|
||||
agents::{Brutus, Human, MaximizerAgent, MinimizerAgent, Rhai},
|
||||
util::Player,
|
||||
};
|
||||
|
||||
@@ -43,9 +42,9 @@ pub enum AgentSelector {
|
||||
impl AgentSelector {
|
||||
pub fn get_maximizer(&self, player: Player) -> Box<dyn MaximizerAgent> {
|
||||
match self {
|
||||
Self::Random => Box::new(Random::new(player)),
|
||||
Self::Chase => Box::new(Chase::new(player)),
|
||||
Self::Diffuse => Box::new(Diffuse::new(player)),
|
||||
Self::Random => Box::new(Rhai::new(player, include_str!("agents.rhai/random.rhai"))),
|
||||
Self::Diffuse => Box::new(Rhai::new(player, include_str!("agents.rhai/diffuse.rhai"))),
|
||||
Self::Chase => Box::new(Rhai::new(player, include_str!("agents.rhai/chase.rhai"))),
|
||||
Self::Brutus => Box::new(Brutus::new(player)),
|
||||
Self::Human => Box::new(Human::new(player)),
|
||||
}
|
||||
@@ -53,9 +52,9 @@ impl AgentSelector {
|
||||
|
||||
pub fn get_minimizer(&self, player: Player) -> Box<dyn MinimizerAgent> {
|
||||
match self {
|
||||
Self::Random => Box::new(Random::new(player)),
|
||||
Self::Chase => Box::new(Chase::new(player)),
|
||||
Self::Diffuse => Box::new(Diffuse::new(player)),
|
||||
Self::Random => Box::new(Rhai::new(player, include_str!("agents.rhai/random.rhai"))),
|
||||
Self::Diffuse => Box::new(Rhai::new(player, include_str!("agents.rhai/diffuse.rhai"))),
|
||||
Self::Chase => Box::new(Rhai::new(player, include_str!("agents.rhai/chase.rhai"))),
|
||||
Self::Brutus => Box::new(Brutus::new(player)),
|
||||
Self::Human => Box::new(Human::new(player)),
|
||||
}
|
||||
|
||||
14
src/main.rs
14
src/main.rs
@@ -22,7 +22,7 @@ fn play_silent(
|
||||
let mut board = Board::new();
|
||||
let mut is_maxi_turn = true;
|
||||
|
||||
while !board.is_done() {
|
||||
while !board.is_full() {
|
||||
// Take action
|
||||
let action = if is_maxi_turn {
|
||||
maxi.step_max(&board)?
|
||||
@@ -70,7 +70,7 @@ fn play(
|
||||
color::Fg(color::Reset)
|
||||
);
|
||||
|
||||
while !board.is_done() {
|
||||
while !board.is_full() {
|
||||
// Print board
|
||||
println!(
|
||||
"\r{}{}{}{}",
|
||||
@@ -136,6 +136,9 @@ fn main() -> Result<()> {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
println!("Error: {e}");
|
||||
for e in e.chain() {
|
||||
println!("{e}");
|
||||
}
|
||||
return None;
|
||||
}
|
||||
};
|
||||
@@ -146,6 +149,9 @@ fn main() -> Result<()> {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
println!("Error: {e}");
|
||||
for e in e.chain() {
|
||||
println!("{e}");
|
||||
}
|
||||
return None;
|
||||
}
|
||||
};
|
||||
@@ -181,7 +187,7 @@ fn main() -> Result<()> {
|
||||
let mut mini = cli.blue.get_minimizer(Player::Blue);
|
||||
let red_board = play(&mut *maxi, &mut *mini)?;
|
||||
|
||||
if red_board.is_done() {
|
||||
if red_board.is_full() {
|
||||
println!(
|
||||
"\r\n{}{} score:{} {:.2}\n\n",
|
||||
color::Fg(maxi.player().color()),
|
||||
@@ -202,7 +208,7 @@ fn main() -> Result<()> {
|
||||
let mut mini = cli.red.get_minimizer(Player::Red);
|
||||
let blue_board = play(&mut *maxi, &mut *mini)?;
|
||||
|
||||
if blue_board.is_done() {
|
||||
if blue_board.is_full() {
|
||||
println!(
|
||||
"\r\n{}{} score:{} {:.2}\n\n",
|
||||
color::Fg(maxi.player().color()),
|
||||
|
||||
28
src/util.rs
28
src/util.rs
@@ -3,6 +3,7 @@ use std::{
|
||||
num::NonZeroU8,
|
||||
};
|
||||
|
||||
use rand::Rng;
|
||||
use termion::color::{self, Color};
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
@@ -31,6 +32,7 @@ impl Display for Player {
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Hash)]
|
||||
|
||||
pub enum Symb {
|
||||
Number(NonZeroU8),
|
||||
Zero,
|
||||
@@ -69,6 +71,24 @@ impl Symb {
|
||||
self == &Self::Minus
|
||||
}
|
||||
|
||||
pub fn new_random<R: Rng>(rng: &mut R) -> Self {
|
||||
match rng.gen_range(0..4) {
|
||||
0 => {
|
||||
let n = rng.gen_range(0..=9);
|
||||
if n == 0 {
|
||||
Symb::Zero
|
||||
} else {
|
||||
Symb::Number(NonZeroU8::new(n).unwrap())
|
||||
}
|
||||
}
|
||||
1 => Symb::Div,
|
||||
2 => Symb::Minus,
|
||||
3 => Symb::Plus,
|
||||
4 => Symb::Times,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn get_char(&self) -> Option<char> {
|
||||
match self {
|
||||
Self::Plus => Some('+'),
|
||||
@@ -91,6 +111,14 @@ impl Symb {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(s: &str) -> Option<Self> {
|
||||
if s.chars().count() != 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Self::from_char(s.chars().next()?)
|
||||
}
|
||||
|
||||
pub const fn from_char(c: char) -> Option<Self> {
|
||||
match c {
|
||||
'1' => Some(Self::Number(unsafe { NonZeroU8::new_unchecked(1) })),
|
||||
|
||||
Reference in New Issue
Block a user