commit
5290b281bb
5 changed files with 1699 additions and 0 deletions
@ -0,0 +1,120 @@ |
|||||
|
# This file is automatically @generated by Cargo. |
||||
|
# It is not intended for manual editing. |
||||
|
[[package]] |
||||
|
name = "bitvec" |
||||
|
version = "0.19.5" |
||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" |
||||
|
dependencies = [ |
||||
|
"funty", |
||||
|
"radium", |
||||
|
"tap", |
||||
|
"wyz", |
||||
|
] |
||||
|
|
||||
|
[[package]] |
||||
|
name = "cfg-if" |
||||
|
version = "1.0.0" |
||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" |
||||
|
|
||||
|
[[package]] |
||||
|
name = "funty" |
||||
|
version = "1.1.0" |
||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" |
||||
|
|
||||
|
[[package]] |
||||
|
name = "getrandom" |
||||
|
version = "0.2.3" |
||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" |
||||
|
dependencies = [ |
||||
|
"cfg-if", |
||||
|
"libc", |
||||
|
"wasi", |
||||
|
] |
||||
|
|
||||
|
[[package]] |
||||
|
name = "libc" |
||||
|
version = "0.2.101" |
||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" |
||||
|
|
||||
|
[[package]] |
||||
|
name = "ppv-lite86" |
||||
|
version = "0.2.10" |
||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" |
||||
|
|
||||
|
[[package]] |
||||
|
name = "radium" |
||||
|
version = "0.5.3" |
||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" |
||||
|
|
||||
|
[[package]] |
||||
|
name = "rand" |
||||
|
version = "0.8.4" |
||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" |
||||
|
dependencies = [ |
||||
|
"libc", |
||||
|
"rand_chacha", |
||||
|
"rand_core", |
||||
|
"rand_hc", |
||||
|
] |
||||
|
|
||||
|
[[package]] |
||||
|
name = "rand_chacha" |
||||
|
version = "0.3.1" |
||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" |
||||
|
dependencies = [ |
||||
|
"ppv-lite86", |
||||
|
"rand_core", |
||||
|
] |
||||
|
|
||||
|
[[package]] |
||||
|
name = "rand_core" |
||||
|
version = "0.6.3" |
||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" |
||||
|
dependencies = [ |
||||
|
"getrandom", |
||||
|
] |
||||
|
|
||||
|
[[package]] |
||||
|
name = "rand_hc" |
||||
|
version = "0.3.1" |
||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" |
||||
|
dependencies = [ |
||||
|
"rand_core", |
||||
|
] |
||||
|
|
||||
|
[[package]] |
||||
|
name = "tap" |
||||
|
version = "1.0.1" |
||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" |
||||
|
|
||||
|
[[package]] |
||||
|
name = "wasi" |
||||
|
version = "0.10.2+wasi-snapshot-preview1" |
||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" |
||||
|
|
||||
|
[[package]] |
||||
|
name = "whist" |
||||
|
version = "0.1.0" |
||||
|
dependencies = [ |
||||
|
"bitvec", |
||||
|
"rand", |
||||
|
] |
||||
|
|
||||
|
[[package]] |
||||
|
name = "wyz" |
||||
|
version = "0.2.0" |
||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||
|
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" |
@ -0,0 +1,14 @@ |
|||||
|
[package] |
||||
|
name = "whist" |
||||
|
version = "0.1.0" |
||||
|
authors = ["e-dt"] |
||||
|
edition = "2018" |
||||
|
|
||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||
|
|
||||
|
[dependencies] |
||||
|
bitvec = "0.19.5" |
||||
|
rand = "0.8.4" |
||||
|
|
||||
|
[profile.release] |
||||
|
debug = true |
@ -0,0 +1,514 @@ |
|||||
|
#![allow(non_snake_case)] |
||||
|
|
||||
|
use bitvec::prelude::*; |
||||
|
use rand::prelude::*; |
||||
|
use std::cmp::max; |
||||
|
use std::io; |
||||
|
|
||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)] |
||||
|
enum Suit { |
||||
|
Spades, |
||||
|
Hearts, |
||||
|
Diamonds, |
||||
|
Clubs, |
||||
|
} |
||||
|
|
||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)] |
||||
|
struct Card { |
||||
|
suit: Suit, |
||||
|
rank: u8, // 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, A <- 2 is 0, ... A is 12 |
||||
|
} |
||||
|
|
||||
|
#[derive(Debug, Clone)] |
||||
|
struct GameState { |
||||
|
player: u8, |
||||
|
handPlayer: u8, |
||||
|
trump: Option<Suit>, |
||||
|
hand: [Option<Card>; 13], // hand of handPlayer |
||||
|
ruffs: bitarr!(for 16), |
||||
|
played: bitarr!(for 52), |
||||
|
trick: [Option<Card>; 3], |
||||
|
tricksWonBy0: u8, |
||||
|
sizes: [u8; 4], |
||||
|
} |
||||
|
|
||||
|
#[derive(Debug, Clone)] |
||||
|
struct Node { |
||||
|
visited: u64, |
||||
|
value: i64, |
||||
|
state: GameState, |
||||
|
outcomes: [Option<Box<Node>>; 52], |
||||
|
} |
||||
|
fn rankOf(c: Card) -> u8 { |
||||
|
c.rank |
||||
|
} |
||||
|
fn suitOf(c: Card) -> Suit { |
||||
|
c.suit |
||||
|
} |
||||
|
fn toSuit(n: u8) -> Suit { |
||||
|
match n { |
||||
|
0 => Suit::Spades, |
||||
|
1 => Suit::Hearts, |
||||
|
2 => Suit::Diamonds, |
||||
|
3 => Suit::Clubs, |
||||
|
_ => panic!("ERR in toSuit - it was given too big a number. We don't use 'fairy suits' in Whist, you goddamn idiot!"), |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn fromSuit(s: Suit) -> u8 { |
||||
|
match s { |
||||
|
Suit::Spades => 0, |
||||
|
Suit::Hearts => 1, |
||||
|
Suit::Diamonds => 2, |
||||
|
Suit::Clubs => 3, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn numToCard(num: u8) -> Card { |
||||
|
// this assumes the number is legal. |
||||
|
let rank: u8 = num % 13; |
||||
|
let suit: Suit = toSuit(num / 13); |
||||
|
Card { suit, rank } |
||||
|
} |
||||
|
|
||||
|
fn cardToNum(card: Card) -> u8 { |
||||
|
// this assumes card rank is legal. |
||||
|
let suitN: u8 = fromSuit(suitOf(card)); |
||||
|
suitN * 13 + rankOf(card) |
||||
|
} |
||||
|
|
||||
|
fn state_transit(state: &GameState, action: Card) -> GameState { |
||||
|
// this assumes the action is legal. Please don't do illegal action? |
||||
|
let hand: [Option<Card>; 13] = if state.player == state.handPlayer { |
||||
|
let mut new: [Option<Card>; 13] = state.hand; |
||||
|
for item in &mut new { |
||||
|
// this assumes action is in hand |
||||
|
if let Some(x) = item { |
||||
|
if *x == action { |
||||
|
*item = None; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
new |
||||
|
} else { |
||||
|
state.hand |
||||
|
}; |
||||
|
|
||||
|
let mut played: bitarr!(for 52) = state.played; |
||||
|
played.set(cardToNum(action).into(), true); |
||||
|
|
||||
|
let ruffs: bitarr!(for 16) = match state.trick[0] { |
||||
|
Some(firstCard) => { |
||||
|
if suitOf(action) != suitOf(firstCard) { |
||||
|
let mut new: bitarr!(for 16) = state.ruffs; |
||||
|
new.set( |
||||
|
(state.player * 4 + fromSuit(suitOf(firstCard))).into(), |
||||
|
true, |
||||
|
); |
||||
|
new |
||||
|
} else { |
||||
|
state.ruffs |
||||
|
} |
||||
|
} |
||||
|
None => state.ruffs, |
||||
|
}; |
||||
|
|
||||
|
if state.trick[2] != None { |
||||
|
// Ok, new trick after this. |
||||
|
|
||||
|
let firstCard: Card = state.trick[0].unwrap(); //this assumes that tricks is sane |
||||
|
let mut maxRank: u8 = rankOf(action); |
||||
|
let mut trumped: bool = Some(suitOf(action)) == state.trump; |
||||
|
let mut winner: u8 = state.player; |
||||
|
for (i, card) in state.trick.iter().enumerate() { |
||||
|
match card { |
||||
|
Some(x) => { |
||||
|
if (suitOf(*x) == suitOf(firstCard) && !trumped && rankOf(*x) > maxRank) |
||||
|
|| (Some(suitOf(*x)) == state.trump && (!trumped || rankOf(*x) > maxRank)) |
||||
|
{ |
||||
|
maxRank = rankOf(*x); |
||||
|
winner = (8 + i as u8 - (3 - state.player)) % 4; |
||||
|
if Some(suitOf(*x)) == state.trump { |
||||
|
trumped = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
None => (), |
||||
|
} |
||||
|
} |
||||
|
let newWon: u8 = state.tricksWonBy0 + if winner == 0 || winner == 2 { 1 } else { 0 }; |
||||
|
let mut sizes = state.sizes; |
||||
|
sizes[usize::from(state.player)] -= 1; |
||||
|
GameState { |
||||
|
player: winner, |
||||
|
handPlayer: state.handPlayer, |
||||
|
trump: state.trump, |
||||
|
hand, |
||||
|
ruffs, |
||||
|
played, |
||||
|
trick: [None, None, None], |
||||
|
tricksWonBy0: newWon, |
||||
|
sizes, |
||||
|
} |
||||
|
} else { |
||||
|
let mut trick: [Option<Card>; 3] = state.trick; |
||||
|
for i in 0..3 { |
||||
|
if trick[i] == None { |
||||
|
trick[i] = Some(action); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
let mut sizes = state.sizes; |
||||
|
sizes[usize::from(state.player)] -= 1; |
||||
|
GameState { |
||||
|
player: (state.player + 1) % 4, |
||||
|
handPlayer: state.handPlayer, |
||||
|
trump: state.trump, |
||||
|
hand, |
||||
|
ruffs, |
||||
|
played, |
||||
|
trick, |
||||
|
tricksWonBy0: state.tricksWonBy0, |
||||
|
sizes, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn card(suit: Suit, rank: u8) -> Card { |
||||
|
Card { suit, rank } |
||||
|
} |
||||
|
|
||||
|
fn new_node(state: GameState) -> Node { |
||||
|
Node { |
||||
|
visited: 0, |
||||
|
value: 0, |
||||
|
state, |
||||
|
outcomes: [ |
||||
|
None, None, None, None, None, None, None, None, None, None, None, None, None, None, |
||||
|
None, None, None, None, None, None, None, None, None, None, None, None, None, None, |
||||
|
None, None, None, None, None, None, None, None, None, None, None, None, None, None, |
||||
|
None, None, None, None, None, None, None, None, None, None, |
||||
|
], |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn hands(state: &GameState) -> [[Option<u8>; 13]; 4] { |
||||
|
let mut hand: [[Option<u8>; 13] 4] = [vec![], vec![], vec![], vec![]]; |
||||
|
let mut seen = state.played; |
||||
|
for i in state.hand.iter() { |
||||
|
match i { |
||||
|
None => (), |
||||
|
Some(x) => { |
||||
|
hand[usize::from(state.handPlayer)].push(cardToNum(*x)); |
||||
|
seen.set(cardToNum(*x).into(), true); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
let mut sizes = state.sizes; |
||||
|
let mut allowed: [[bool; 4]; 52] = [[true, true, true, true]; 52]; |
||||
|
let mut n_to_assign = 52 - seen.count_ones(); |
||||
|
let mut n_assignable_to = [0, 0, 0, 0]; |
||||
|
for i in 0..52 { |
||||
|
if seen[i] { |
||||
|
continue; |
||||
|
} |
||||
|
allowed[i][usize::from(state.handPlayer)] = false; |
||||
|
for j in 0..4 { |
||||
|
if state.ruffs[j * 4 + i / 13] == true { |
||||
|
allowed[i][j] = false; |
||||
|
} |
||||
|
} |
||||
|
let mut n = 0; |
||||
|
let mut last = 0; |
||||
|
for j in 0..4 { |
||||
|
if allowed[i][j] { |
||||
|
n += 1; |
||||
|
last = j; |
||||
|
n_assignable_to[j] += 1; |
||||
|
} |
||||
|
} |
||||
|
if n == 1 { |
||||
|
hand[last].push(i as u8); |
||||
|
n_to_assign -= 1; |
||||
|
n_assignable_to[last] -= 1; |
||||
|
sizes[last] -= 1; |
||||
|
seen.set(i, true); |
||||
|
} |
||||
|
} |
||||
|
let mut running = 0; |
||||
|
while n_to_assign > 0 { |
||||
|
let mut any_done = false; |
||||
|
for i in 0..4 { |
||||
|
if sizes[i] > 0 && n_assignable_to[i] == sizes[i] { |
||||
|
for j in 0..52 { |
||||
|
if allowed[j][i] && !seen[j] { |
||||
|
seen.set(j, true); |
||||
|
hand[i].push(j as u8); |
||||
|
sizes[i] -= 1; |
||||
|
n_to_assign -= 1; |
||||
|
for q in 0..4 { |
||||
|
if allowed[j][q] { |
||||
|
n_assignable_to[q] -= 1; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
any_done = true; |
||||
|
} |
||||
|
} |
||||
|
if any_done { |
||||
|
continue; |
||||
|
} |
||||
|
while seen[running] { |
||||
|
running += 1; |
||||
|
} |
||||
|
let mut n: usize = allowed[running] |
||||
|
.iter() |
||||
|
.map(|&x| if x { 1 } else { 0 }) |
||||
|
.sum(); |
||||
|
for j in 0..4 { |
||||
|
if sizes[j] == 0 && allowed[running][j] == true { |
||||
|
n -= 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for j in 0..4 { |
||||
|
if !allowed[running][j] || sizes[j] == 0 { |
||||
|
continue; |
||||
|
} |
||||
|
if random::<usize>() % n == 0 { |
||||
|
hand[j].push(running as u8); |
||||
|
for i in 0..4 { |
||||
|
if allowed[running][i] { |
||||
|
n_assignable_to[i] -= 1; |
||||
|
} |
||||
|
} |
||||
|
sizes[j] -= 1; |
||||
|
n_to_assign -= 1; |
||||
|
break; |
||||
|
} else { |
||||
|
n -= 1; |
||||
|
} |
||||
|
} |
||||
|
seen.set(running, true); |
||||
|
} |
||||
|
hand |
||||
|
} |
||||
|
|
||||
|
fn search(node: &mut Node) -> (Vec<usize>, &mut Node) { |
||||
|
let mut cur: &mut Node = node; |
||||
|
let mut seen: Vec<usize> = vec![]; |
||||
|
loop { |
||||
|
cur.visited += 1; |
||||
|
if cur.state.played.count_ones() == 52 { |
||||
|
// abort |
||||
|
return (seen, cur); |
||||
|
} |
||||
|
let mut score: f64 = -10000.0; |
||||
|
let mut idx: usize = 100000; |
||||
|
let hands = hands(&cur.state); |
||||
|
let mut outin: [bool; 4] = [true; 4]; |
||||
|
for i in hands[usize::from(cur.state.player)].iter() { |
||||
|
outin[usize::from(i / 13)] = false; |
||||
|
} |
||||
|
for i in hands[usize::from(cur.state.player)].iter() { |
||||
|
match cur.state.trick[0] { |
||||
|
None => (), |
||||
|
Some(x) => { |
||||
|
if !(fromSuit(suitOf(x)) == i / 13) |
||||
|
&& !(outin[usize::from(fromSuit(suitOf(x)))]) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
match &cur.outcomes[usize::from(*i)] { |
||||
|
None => { |
||||
|
seen.push((*i).into()); |
||||
|
return (seen, cur); |
||||
|
} |
||||
|
Some(x) => { |
||||
|
let ourscore: f64 = (x.value as f64 / (x.visited as f64 + 1.0)) |
||||
|
+ 6.0 * ((cur.visited as f64).ln() / (x.visited as f64 + 1.0)).sqrt(); |
||||
|
if ourscore > score { |
||||
|
idx = (*i).into(); |
||||
|
score = ourscore; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
match &mut cur.outcomes[idx] { |
||||
|
None => panic!("This will not happen. SEARCH PANIC KW!#"), |
||||
|
Some(x) => { |
||||
|
seen.push(idx); |
||||
|
cur = x; |
||||
|
} |
||||
|
} |
||||
|
//cur.outcomes[idx].clone().unwrap(); |
||||
|
//cur = *(cur.outcomes[idx].unwrap()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn propagate(node: &mut Node, path: Vec<usize>, value: i8) { |
||||
|
let mut cur: &mut Node = node; |
||||
|
let mut sgn = -1; |
||||
|
for i in path { |
||||
|
cur.value += sgn * i64::from(value); |
||||
|
sgn = -sgn; |
||||
|
match &mut cur.outcomes[i] { |
||||
|
None => panic!("This will not happen. SEARCH PANIC KQ!#"), |
||||
|
Some(x) => { |
||||
|
cur = x; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
fn simulate(state: &GameState) -> i8 { |
||||
|
let mut cur: GameState = state.clone(); |
||||
|
loop { |
||||
|
let tricks0: u8 = cur.tricksWonBy0; |
||||
|
if cur.played.count_ones() == 52 { |
||||
|
if cur.handPlayer == 0 || cur.handPlayer == 2 { |
||||
|
return max(tricks0 as i8 - 6, 0) - max(7 - tricks0 as i8, 0); |
||||
|
} else { |
||||
|
return max(7 - tricks0 as i8, 0) - max(tricks0 as i8 - 6, 0); |
||||
|
} |
||||
|
} |
||||
|
let card: Card; |
||||
|
// ok. find action |
||||
|
let hands = hands(&cur); |
||||
|
|
||||
|
let mut outin: [u8; 4] = [0; 4]; |
||||
|
for i in hands[usize::from(cur.player)].iter() { |
||||
|
outin[usize::from(i / 13)] += 1; |
||||
|
} |
||||
|
// i fucked up my life on this one |
||||
|
let mut n: u8 = cur.sizes[usize::from(cur.player)]; |
||||
|
let mut follow: bool = false; |
||||
|
let mut led: u8 = 100; |
||||
|
match cur.trick[0] { |
||||
|
None => (), |
||||
|
Some(x) => { |
||||
|
if outin[usize::from(fromSuit(suitOf(x)))] > 0 { |
||||
|
n = outin[usize::from(fromSuit(suitOf(x)))]; |
||||
|
led = fromSuit(suitOf(x)); |
||||
|
follow = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
let mut p: Card = Card { |
||||
|
suit: Suit::Diamonds, |
||||
|
rank: 105, |
||||
|
}; // see this, something has gone terribly wrong. |
||||
|
for i in hands[usize::from(cur.player)].iter() { |
||||
|
if follow && led != i / 13 { |
||||
|
continue; |
||||
|
} |
||||
|
if random::<u8>() % n == 0 { |
||||
|
p = numToCard(*i); |
||||
|
break; |
||||
|
} else { |
||||
|
n -= 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
card = p; |
||||
|
cur = state_transit(&cur, card); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn main() { |
||||
|
let mut played = bitarr!(0; 52); |
||||
|
played.set(12, true); |
||||
|
played.set(1, true); |
||||
|
played.set(0, true); |
||||
|
let mut node = new_node(GameState { |
||||
|
player: 0, |
||||
|
handPlayer: 0, |
||||
|
trump: Some(Suit::Hearts), |
||||
|
hand: [ |
||||
|
Some(card(Suit::Hearts, 0)), |
||||
|
Some(card(Suit::Hearts, 2)), |
||||
|
Some(card(Suit::Hearts, 7)), |
||||
|
Some(card(Suit::Hearts, 8)), |
||||
|
Some(card(Suit::Hearts, 9)), |
||||
|
Some(card(Suit::Hearts, 10)), |
||||
|
Some(card(Suit::Spades, 2)), |
||||
|
Some(card(Suit::Spades, 7)), |
||||
|
Some(card(Suit::Spades, 10)), |
||||
|
Some(card(Suit::Diamonds, 2)), |
||||
|
Some(card(Suit::Clubs, 1)), |
||||
|
Some(card(Suit::Clubs, 3)), |
||||
|
Some(card(Suit::Clubs, 7)), |
||||
|
], |
||||
|
ruffs: bitarr!(0; 16), |
||||
|
played, |
||||
|
trick: [ |
||||
|
Some(card(Suit::Spades, 12)), |
||||
|
Some(card(Suit::Spades, 1)), |
||||
|
Some(card(Suit::Spades, 0)), |
||||
|
], |
||||
|
tricksWonBy0: 0, |
||||
|
sizes: [13, 12, 12, 12], |
||||
|
}); |
||||
|
loop { |
||||
|
if node.state.player != node.state.handPlayer { |
||||
|
println!("Input the card played by player {}", node.state.player); |
||||
|
let mut n = String::new(); |
||||
|
io::stdin().read_line(&mut n).expect("Input!"); |
||||
|
let card: usize = n.trim().parse().expect("RIGHT!"); |
||||
|
match &node.outcomes[card] { |
||||
|
None => { |
||||
|
node = new_node(state_transit(&node.state, numToCard(card as u8))); |
||||
|
} |
||||
|
Some(x) => { |
||||
|
node = (**x).clone(); |
||||
|
} |
||||
|
} |
||||
|
continue; |
||||
|
} |
||||
|
for _ in 1..10000 { |
||||
|
let pathcur: (Vec<usize>, &mut Node) = search(&mut node); //search! |
||||
|
let path = pathcur.0; |
||||
|
let cur = pathcur.1; |
||||
|
let n: i8; |
||||
|
if cur.state.played.count_ones() == 52 { |
||||
|
let tricks0: u8 = cur.state.tricksWonBy0; |
||||
|
n = if cur.state.handPlayer == 0 || cur.state.handPlayer == 2 { |
||||
|
max(tricks0 as i8 - 6, 0) - max(7 - tricks0 as i8, 0) |
||||
|
} else { |
||||
|
max(7 - tricks0 as i8, 0) - max(tricks0 as i8 - 6, 0) |
||||
|
} |
||||
|
} else { |
||||
|
cur.outcomes[path[path.len() - 1]] = Some(Box::new(new_node(state_transit( |
||||
|
&cur.state, |
||||
|
numToCard(path[path.len() - 1] as u8), |
||||
|
)))); //expand! |
||||
|
n = simulate(&(*cur.outcomes[path[path.len() - 1]].as_ref().unwrap()).state); |
||||
|
//simulate! |
||||
|
} |
||||
|
propagate(&mut node, path, n); |
||||
|
} |
||||
|
let mut highest_value = -4000.0; |
||||
|
let mut best_move = 100; |
||||
|
for i in 0..52 { |
||||
|
match &node.outcomes[i] { |
||||
|
None => (), |
||||
|
Some(x) => { |
||||
|
if x.value as f64 / x.visited as f64 > highest_value { |
||||
|
highest_value = x.value as f64 / x.visited as f64; |
||||
|
best_move = i; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
println!("{:?}", numToCard(best_move as u8)); |
||||
|
break; |
||||
|
match &node.outcomes[best_move] { |
||||
|
None => panic!("ok"), |
||||
|
Some(x) => { |
||||
|
node = (**x).clone(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,537 @@ |
|||||
|
#![allow(non_snake_case)] |
||||
|
|
||||
|
use bitvec::prelude::*; |
||||
|
use rand::prelude::*; |
||||
|
use std::cmp::max; |
||||
|
use std::io; |
||||
|
|
||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)] |
||||
|
enum Suit { |
||||
|
Spades, |
||||
|
Hearts, |
||||
|
Diamonds, |
||||
|
Clubs, |
||||
|
} |
||||
|
|
||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)] |
||||
|
struct Card { |
||||
|
suit: Suit, |
||||
|
rank: u8, // 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, A <- 2 is 0, ... A is 12
|
||||
|
} |
||||
|
|
||||
|
#[derive(Debug, Clone)] |
||||
|
struct GameState { |
||||
|
player: u8, |
||||
|
handPlayer: u8, |
||||
|
trump: Option<Suit>, |
||||
|
hand: [Option<Card>; 13], // hand of handPlayer
|
||||
|
ruffs: bitarr!(for 16), |
||||
|
played: bitarr!(for 52), |
||||
|
trick: [Option<Card>; 3], |
||||
|
tricksWonBy0: u8, |
||||
|
sizes: [u8; 4], |
||||
|
} |
||||
|
|
||||
|
#[derive(Debug, Clone)] |
||||
|
struct Node { |
||||
|
visited: u64, |
||||
|
value: i64, |
||||
|
state: GameState, |
||||
|
outcomes: [Option<Box<Node>>; 52], |
||||
|
} |
||||
|
fn rankOf(c: Card) -> u8 { |
||||
|
c.rank |
||||
|
} |
||||
|
fn suitOf(c: Card) -> Suit { |
||||
|
c.suit |
||||
|
} |
||||
|
fn toSuit(n: u8) -> Suit { |
||||
|
match n { |
||||
|
0 => Suit::Spades, |
||||
|
1 => Suit::Hearts, |
||||
|
2 => Suit::Diamonds, |
||||
|
3 => Suit::Clubs, |
||||
|
_ => panic!("ERR in toSuit - it was given too big a number. We don't use 'fairy suits' in Whist, you goddamn idiot!"), |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn fromSuit(s: Suit) -> u8 { |
||||
|
match s { |
||||
|
Suit::Spades => 0, |
||||
|
Suit::Hearts => 1, |
||||
|
Suit::Diamonds => 2, |
||||
|
Suit::Clubs => 3, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn numToCard(num: u8) -> Card { |
||||
|
// this assumes the number is legal.
|
||||
|
let rank: u8 = num % 13; |
||||
|
let suit: Suit = toSuit(num / 13); |
||||
|
Card { suit, rank } |
||||
|
} |
||||
|
|
||||
|
fn cardToNum(card: Card) -> u8 { |
||||
|
// this assumes card rank is legal.
|
||||
|
let suitN: u8 = fromSuit(suitOf(card)); |
||||
|
suitN * 13 + rankOf(card) |
||||
|
} |
||||
|
|
||||
|
fn state_transit(state: &GameState, action: Card) -> GameState { |
||||
|
// this assumes the action is legal. Please don't do illegal action?
|
||||
|
let hand: [Option<Card>; 13] = if state.player == state.handPlayer { |
||||
|
let mut new: [Option<Card>; 13] = state.hand; |
||||
|
for item in &mut new { |
||||
|
// this assumes action is in hand
|
||||
|
if let Some(x) = item { |
||||
|
if *x == action { |
||||
|
*item = None; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
new |
||||
|
} else { |
||||
|
state.hand |
||||
|
}; |
||||
|
|
||||
|
let mut played: bitarr!(for 52) = state.played; |
||||
|
played.set(cardToNum(action).into(), true); |
||||
|
|
||||
|
let ruffs: bitarr!(for 16) = match state.trick[0] { |
||||
|
Some(firstCard) => { |
||||
|
if suitOf(action) != suitOf(firstCard) { |
||||
|
let mut new: bitarr!(for 16) = state.ruffs; |
||||
|
new.set( |
||||
|
(state.player * 4 + fromSuit(suitOf(firstCard))).into(), |
||||
|
true, |
||||
|
); |
||||
|
new |
||||
|
} else { |
||||
|
state.ruffs |
||||
|
} |
||||
|
} |
||||
|
None => state.ruffs, |
||||
|
}; |
||||
|
|
||||
|
if state.trick[2] != None { |
||||
|
// Ok, new trick after this.
|
||||
|
|
||||
|
let firstCard: Card = state.trick[0].unwrap(); //this assumes that tricks is sane
|
||||
|
let mut maxRank: u8 = rankOf(action); |
||||
|
let mut trumped: bool = Some(suitOf(action)) == state.trump; |
||||
|
let mut winner: u8 = state.player; |
||||
|
for (i, card) in state.trick.iter().enumerate() { |
||||
|
match card { |
||||
|
Some(x) => { |
||||
|
if (suitOf(*x) == suitOf(firstCard) && !trumped && rankOf(*x) > maxRank) |
||||
|
|| (Some(suitOf(*x)) == state.trump && (!trumped || rankOf(*x) > maxRank)) |
||||
|
{ |
||||
|
maxRank = rankOf(*x); |
||||
|
winner = (8 + i as u8 - (3 - state.player)) % 4; |
||||
|
if Some(suitOf(*x)) == state.trump { |
||||
|
trumped = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
None => (), |
||||
|
} |
||||
|
} |
||||
|
let newWon: u8 = state.tricksWonBy0 + if winner == 0 || winner == 2 { 1 } else { 0 }; |
||||
|
let mut sizes = state.sizes; |
||||
|
sizes[usize::from(state.player)] -= 1; |
||||
|
GameState { |
||||
|
player: winner, |
||||
|
handPlayer: state.handPlayer, |
||||
|
trump: state.trump, |
||||
|
hand, |
||||
|
ruffs, |
||||
|
played, |
||||
|
trick: [None, None, None], |
||||
|
tricksWonBy0: newWon, |
||||
|
sizes, |
||||
|
} |
||||
|
} else { |
||||
|
let mut trick: [Option<Card>; 3] = state.trick; |
||||
|
for i in 0..3 { |
||||
|
if trick[i] == None { |
||||
|
trick[i] = Some(action); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
let mut sizes = state.sizes; |
||||
|
sizes[usize::from(state.player)] -= 1; |
||||
|
GameState { |
||||
|
player: (state.player + 1) % 4, |
||||
|
handPlayer: state.handPlayer, |
||||
|
trump: state.trump, |
||||
|
hand, |
||||
|
ruffs, |
||||
|
played, |
||||
|
trick, |
||||
|
tricksWonBy0: state.tricksWonBy0, |
||||
|
sizes, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn card(suit: Suit, rank: u8) -> Card { |
||||
|
Card { suit, rank } |
||||
|
} |
||||
|
|
||||
|
fn new_node(state: GameState) -> Node { |
||||
|
Node { |
||||
|
visited: 0, |
||||
|
value: 0, |
||||
|
state, |
||||
|
outcomes: [ |
||||
|
None, None, None, None, None, None, None, None, None, None, None, None, None, None, |
||||
|
None, None, None, None, None, None, None, None, None, None, None, None, None, None, |
||||
|
None, None, None, None, None, None, None, None, None, None, None, None, None, None, |
||||
|
None, None, None, None, None, None, None, None, None, None, |
||||
|
], |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn hands(state: &GameState) -> [Vec<u8>; 4] { |
||||
|
let mut hand: [Vec<u8>; 4] = [vec![], vec![], vec![], vec![]]; |
||||
|
let mut seen = state.played; |
||||
|
for i in state.hand.iter() { |
||||
|
match i { |
||||
|
None => (), |
||||
|
Some(x) => { |
||||
|
hand[usize::from(state.handPlayer)].push(cardToNum(*x)); |
||||
|
seen.set(cardToNum(*x).into(), true); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
let mut sizes = state.sizes; |
||||
|
let mut allowed: [[bool; 4]; 52] = [[true, true, true, true]; 52]; |
||||
|
let mut n_to_assign = 52 - seen.count_ones(); |
||||
|
let mut n_assignable_to = [0, 0, 0, 0]; |
||||
|
for i in 0..52 { |
||||
|
if seen[i] { |
||||
|
continue; |
||||
|
} |
||||
|
allowed[i][usize::from(state.handPlayer)] = false; |
||||
|
for j in 0..4 { |
||||
|
if state.ruffs[j * 4 + i / 13] == true { |
||||
|
allowed[i][j] = false; |
||||
|
} |
||||
|
} |
||||
|
let mut n = 0; |
||||
|
let mut last = 0; |
||||
|
for j in 0..4 { |
||||
|
if allowed[i][j] { |
||||
|
n += 1; |
||||
|
last = j; |
||||
|
n_assignable_to[j] += 1; |
||||
|
} |
||||
|
} |
||||
|
if n == 1 { |
||||
|
hand[last].push(i as u8); |
||||
|
n_to_assign -= 1; |
||||
|
n_assignable_to[last] -= 1; |
||||
|
sizes[last] -= 1; |
||||
|
seen.set(i, true); |
||||
|
} |
||||
|
} |
||||
|
let mut running = 0; |
||||
|
while n_to_assign > 0 { |
||||
|
let mut any_done = false; |
||||
|
/* for i in 0..52 {
|
||||
|
if seen[i] { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
let mut s = 0; |
||||
|
let mut last = 0; |
||||
|
for j in 0..4 { |
||||
|
if allowed[i][j] { |
||||
|
s += 1; |
||||
|
last = j; |
||||
|
} |
||||
|
} |
||||
|
if s == 1 { |
||||
|
hand[last].push(i as u8); |
||||
|
sizes[last] -= 1; |
||||
|
n_to_assign -= 1; |
||||
|
n_assignable_to[last] -= 1; |
||||
|
seen.set(i, true); |
||||
|
any_done = true; |
||||
|
} |
||||
|
} */ |
||||
|
|
||||
|
any_done = false; |
||||
|
for i in 0..4 { |
||||
|
if sizes[i] > 0 && n_assignable_to[i] == sizes[i] { |
||||
|
for j in 0..52 { |
||||
|
if allowed[j][i] && !seen[j] { |
||||
|
seen.set(j, true); |
||||
|
hand[i].push(j as u8); |
||||
|
sizes[i] -= 1; |
||||
|
n_to_assign -= 1; |
||||
|
for q in 0..4 { |
||||
|
if allowed[j][q] { |
||||
|
n_assignable_to[q] -= 1; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
any_done = true; |
||||
|
} |
||||
|
} |
||||
|
if any_done { |
||||
|
continue; |
||||
|
} |
||||
|
while seen[running] { |
||||
|
running += 1; |
||||
|
} |
||||
|
let mut n: usize = allowed[running] |
||||
|
.iter() |
||||
|
.map(|&x| if x { 1 } else { 0 }) |
||||
|
.sum(); |
||||
|
for j in 0..4 { |
||||
|
if sizes[j] == 0 && allowed[running][j] == true { |
||||
|
n -= 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for j in 0..4 { |
||||
|
if !allowed[running][j] || sizes[j] == 0 { |
||||
|
continue; |
||||
|
} |
||||
|
if random::<usize>() % n == 0 { |
||||
|
hand[j].push(running as u8); |
||||
|
for i in 0..4 { |
||||
|
if allowed[running][i] { |
||||
|
n_assignable_to[i] -= 1; |
||||
|
} |
||||
|
} |
||||
|
sizes[j] -= 1; |
||||
|
n_to_assign -= 1; |
||||
|
break; |
||||
|
} else { |
||||
|
n -= 1; |
||||
|
} |
||||
|
} |
||||
|
seen.set(running, true); |
||||
|
} |
||||
|
hand |
||||
|
} |
||||
|
|
||||
|
fn search(node: &mut Node) -> (Vec<usize>, &mut Node) { |
||||
|
let mut cur: &mut Node = node; |
||||
|
let mut seen: Vec<usize> = vec![]; |
||||
|
loop { |
||||
|
cur.visited += 1; |
||||
|
if cur.state.played.count_ones() == 52 { |
||||
|
// abort
|
||||
|
return (seen, cur); |
||||
|
} |
||||
|
let mut score: f64 = -10000.0; |
||||
|
let mut idx: usize = 100000; |
||||
|
let hands = hands(&cur.state); |
||||
|
let mut outin: [bool; 4] = [true; 4]; |
||||
|
for i in hands[usize::from(cur.state.player)].iter() { |
||||
|
outin[usize::from(i / 13)] = false; |
||||
|
} |
||||
|
for i in hands[usize::from(cur.state.player)].iter() { |
||||
|
match cur.state.trick[0] { |
||||
|
None => (), |
||||
|
Some(x) => { |
||||
|
if !(fromSuit(suitOf(x)) == i / 13) |
||||
|
&& !(outin[usize::from(fromSuit(suitOf(x)))]) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
match &cur.outcomes[usize::from(*i)] { |
||||
|
None => { |
||||
|
seen.push((*i).into()); |
||||
|
return (seen, cur); |
||||
|
} |
||||
|
Some(x) => { |
||||
|
let ourscore: f64 = (x.value as f64 / (x.visited as f64 + 1.0)) |
||||
|
+ 6.0 * ((cur.visited as f64).ln() / (x.visited as f64 + 1.0)).sqrt(); |
||||
|
if ourscore > score { |
||||
|
idx = (*i).into(); |
||||
|
score = ourscore; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
match &mut cur.outcomes[idx] { |
||||
|
None => panic!("This will not happen. SEARCH PANIC KW!#"), |
||||
|
Some(x) => { |
||||
|
seen.push(idx); |
||||
|
cur = x; |
||||
|
} |
||||
|
} |
||||
|
//cur.outcomes[idx].clone().unwrap();
|
||||
|
//cur = *(cur.outcomes[idx].unwrap());
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn propagate(node: &mut Node, path: Vec<usize>, value: i8) { |
||||
|
let mut cur: &mut Node = node; |
||||
|
let mut sgn = -1; |
||||
|
for i in path { |
||||
|
cur.value += sgn * i64::from(value); |
||||
|
sgn = -sgn; |
||||
|
match &mut cur.outcomes[i] { |
||||
|
None => panic!("This will not happen. SEARCH PANIC KQ!#"), |
||||
|
Some(x) => { |
||||
|
cur = x; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
fn simulate(state: &GameState) -> i8 { |
||||
|
let mut cur: GameState = state.clone(); |
||||
|
loop { |
||||
|
let tricks0: u8 = cur.tricksWonBy0; |
||||
|
if cur.played.count_ones() == 52 { |
||||
|
if cur.handPlayer == 0 || cur.handPlayer == 2 { |
||||
|
return max(tricks0 as i8 - 6, 0) - max(7 - tricks0 as i8, 0); |
||||
|
} else { |
||||
|
return max(7 - tricks0 as i8, 0) - max(tricks0 as i8 - 6, 0); |
||||
|
} |
||||
|
} |
||||
|
let card: Card; |
||||
|
// ok. find action
|
||||
|
let hands = hands(&cur); |
||||
|
|
||||
|
let mut outin: [u8; 4] = [0; 4]; |
||||
|
for i in hands[usize::from(cur.player)].iter() { |
||||
|
outin[usize::from(i / 13)] += 1; |
||||
|
} |
||||
|
// i fucked up my life on this one
|
||||
|
let mut n: u8 = cur.sizes[usize::from(cur.player)]; |
||||
|
let mut follow: bool = false; |
||||
|
let mut led: u8 = 100; |
||||
|
match cur.trick[0] { |
||||
|
None => (), |
||||
|
Some(x) => { |
||||
|
if outin[usize::from(fromSuit(suitOf(x)))] > 0 { |
||||
|
n = outin[usize::from(fromSuit(suitOf(x)))]; |
||||
|
led = fromSuit(suitOf(x)); |
||||
|
follow = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
let mut p: Card = Card { |
||||
|
suit: Suit::Diamonds, |
||||
|
rank: 105, |
||||
|
}; // see this, something has gone terribly wrong.
|
||||
|
for i in hands[usize::from(cur.player)].iter() { |
||||
|
if follow && led != i / 13 { |
||||
|
continue; |
||||
|
} |
||||
|
if random::<u8>() % n == 0 { |
||||
|
p = numToCard(*i); |
||||
|
break; |
||||
|
} else { |
||||
|
n -= 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
card = p; |
||||
|
cur = state_transit(&cur, card); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn main() { |
||||
|
let mut played = bitarr!(0; 52); |
||||
|
played.set(12, true); |
||||
|
played.set(1, true); |
||||
|
played.set(0, true); |
||||
|
let mut node = new_node(GameState { |
||||
|
player: 0, |
||||
|
handPlayer: 0, |
||||
|
trump: Some(Suit::Hearts), |
||||
|
hand: [ |
||||
|
Some(card(Suit::Hearts, 0)), |
||||
|
Some(card(Suit::Hearts, 2)), |
||||
|
Some(card(Suit::Hearts, 7)), |
||||
|
Some(card(Suit::Hearts, 8)), |
||||
|
Some(card(Suit::Hearts, 9)), |
||||
|
Some(card(Suit::Hearts, 10)), |
||||
|
Some(card(Suit::Spades, 2)), |
||||
|
Some(card(Suit::Spades, 7)), |
||||
|
Some(card(Suit::Spades, 10)), |
||||
|
Some(card(Suit::Diamonds, 2)), |
||||
|
Some(card(Suit::Clubs, 1)), |
||||
|
Some(card(Suit::Clubs, 3)), |
||||
|
Some(card(Suit::Clubs, 7)), |
||||
|
], |
||||
|
ruffs: bitarr!(0; 16), |
||||
|
played, |
||||
|
trick: [ |
||||
|
Some(card(Suit::Spades, 12)), |
||||
|
Some(card(Suit::Spades, 1)), |
||||
|
Some(card(Suit::Spades, 0)), |
||||
|
], |
||||
|
tricksWonBy0: 0, |
||||
|
sizes: [13, 12, 12, 12], |
||||
|
}); |
||||
|
loop { |
||||
|
if node.state.player != node.state.handPlayer { |
||||
|
println!("Input the card played by player {}", node.state.player); |
||||
|
let mut n = String::new(); |
||||
|
io::stdin().read_line(&mut n).expect("Input!"); |
||||
|
let card: usize = n.trim().parse().expect("RIGHT!"); |
||||
|
match &node.outcomes[card] { |
||||
|
None => { |
||||
|
node = new_node(state_transit(&node.state, numToCard(card as u8))); |
||||
|
} |
||||
|
Some(x) => { |
||||
|
node = (**x).clone(); |
||||
|
} |
||||
|
} |
||||
|
continue; |
||||
|
} |
||||
|
for _ in 1..10000 { |
||||
|
let pathcur: (Vec<usize>, &mut Node) = search(&mut node); //search!
|
||||
|
let path = pathcur.0; |
||||
|
let cur = pathcur.1; |
||||
|
let n: i8; |
||||
|
if cur.state.played.count_ones() == 52 { |
||||
|
let tricks0: u8 = cur.state.tricksWonBy0; |
||||
|
n = if cur.state.handPlayer == 0 || cur.state.handPlayer == 2 { |
||||
|
max(tricks0 as i8 - 6, 0) - max(7 - tricks0 as i8, 0) |
||||
|
} else { |
||||
|
max(7 - tricks0 as i8, 0) - max(tricks0 as i8 - 6, 0) |
||||
|
} |
||||
|
} else { |
||||
|
cur.outcomes[path[path.len() - 1]] = Some(Box::new(new_node(state_transit( |
||||
|
&cur.state, |
||||
|
numToCard(path[path.len() - 1] as u8), |
||||
|
)))); //expand!
|
||||
|
n = simulate(&(*cur.outcomes[path[path.len() - 1]].as_ref().unwrap()).state); |
||||
|
//simulate!
|
||||
|
} |
||||
|
propagate(&mut node, path, n); |
||||
|
} |
||||
|
let mut highest_value = -4000.0; |
||||
|
let mut best_move = 100; |
||||
|
for i in 0..52 { |
||||
|
match &node.outcomes[i] { |
||||
|
None => (), |
||||
|
Some(x) => { |
||||
|
if x.value as f64 / x.visited as f64 > highest_value { |
||||
|
highest_value = x.value as f64 / x.visited as f64; |
||||
|
best_move = i; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
println!("{:?}", numToCard(best_move as u8)); |
||||
|
match &node.outcomes[best_move] { |
||||
|
None => panic!("ok"), |
||||
|
Some(x) => { |
||||
|
node = (**x).clone(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,514 @@ |
|||||
|
#![allow(non_snake_case)] |
||||
|
|
||||
|
use bitvec::prelude::*; |
||||
|
use rand::prelude::*; |
||||
|
use std::cmp::max; |
||||
|
use std::io; |
||||
|
|
||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)] |
||||
|
enum Suit { |
||||
|
Spades, |
||||
|
Hearts, |
||||
|
Diamonds, |
||||
|
Clubs, |
||||
|
} |
||||
|
|
||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)] |
||||
|
struct Card { |
||||
|
suit: Suit, |
||||
|
rank: u8, // 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, A <- 2 is 0, ... A is 12
|
||||
|
} |
||||
|
|
||||
|
#[derive(Debug, Clone)] |
||||
|
struct GameState { |
||||
|
player: u8, |
||||
|
handPlayer: u8, |
||||
|
trump: Option<Suit>, |
||||
|
hand: [Option<Card>; 13], // hand of handPlayer
|
||||
|
ruffs: bitarr!(for 16), |
||||
|
played: bitarr!(for 52), |
||||
|
trick: [Option<Card>; 3], |
||||
|
tricksWonBy0: u8, |
||||
|
sizes: [u8; 4], |
||||
|
} |
||||
|
|
||||
|
#[derive(Debug, Clone)] |
||||
|
struct Node { |
||||
|
visited: u64, |
||||
|
value: i64, |
||||
|
state: GameState, |
||||
|
outcomes: [Option<Box<Node>>; 52], |
||||
|
} |
||||
|
fn rankOf(c: Card) -> u8 { |
||||
|
c.rank |
||||
|
} |
||||
|
fn suitOf(c: Card) -> Suit { |
||||
|
c.suit |
||||
|
} |
||||
|
fn toSuit(n: u8) -> Suit { |
||||
|
match n { |
||||
|
0 => Suit::Spades, |
||||
|
1 => Suit::Hearts, |
||||
|
2 => Suit::Diamonds, |
||||
|
3 => Suit::Clubs, |
||||
|
_ => panic!("ERR in toSuit - it was given too big a number. We don't use 'fairy suits' in Whist, you goddamn idiot!"), |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn fromSuit(s: Suit) -> u8 { |
||||
|
match s { |
||||
|
Suit::Spades => 0, |
||||
|
Suit::Hearts => 1, |
||||
|
Suit::Diamonds => 2, |
||||
|
Suit::Clubs => 3, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn numToCard(num: u8) -> Card { |
||||
|
// this assumes the number is legal.
|
||||
|
let rank: u8 = num % 13; |
||||
|
let suit: Suit = toSuit(num / 13); |
||||
|
Card { suit, rank } |
||||
|
} |
||||
|
|
||||
|
fn cardToNum(card: Card) -> u8 { |
||||
|
// this assumes card rank is legal.
|
||||
|
let suitN: u8 = fromSuit(suitOf(card)); |
||||
|
suitN * 13 + rankOf(card) |
||||
|
} |
||||
|
|
||||
|
fn state_transit(state: &GameState, action: Card) -> GameState { |
||||
|
// this assumes the action is legal. Please don't do illegal action?
|
||||
|
let hand: [Option<Card>; 13] = if state.player == state.handPlayer { |
||||
|
let mut new: [Option<Card>; 13] = state.hand; |
||||
|
for item in &mut new { |
||||
|
// this assumes action is in hand
|
||||
|
if let Some(x) = item { |
||||
|
if *x == action { |
||||
|
*item = None; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
new |
||||
|
} else { |
||||
|
state.hand |
||||
|
}; |
||||
|
|
||||
|
let mut played: bitarr!(for 52) = state.played; |
||||
|
played.set(cardToNum(action).into(), true); |
||||
|
|
||||
|
let ruffs: bitarr!(for 16) = match state.trick[0] { |
||||
|
Some(firstCard) => { |
||||
|
if suitOf(action) != suitOf(firstCard) { |
||||
|
let mut new: bitarr!(for 16) = state.ruffs; |
||||
|
new.set( |
||||
|
(state.player * 4 + fromSuit(suitOf(firstCard))).into(), |
||||
|
true, |
||||
|
); |
||||
|
new |
||||
|
} else { |
||||
|
state.ruffs |
||||
|
} |
||||
|
} |
||||
|
None => state.ruffs, |
||||
|
}; |
||||
|
|
||||
|
if state.trick[2] != None { |
||||
|
// Ok, new trick after this.
|
||||
|
|
||||
|
let firstCard: Card = state.trick[0].unwrap(); //this assumes that tricks is sane
|
||||
|
let mut maxRank: u8 = rankOf(action); |
||||
|
let mut trumped: bool = Some(suitOf(action)) == state.trump; |
||||
|
let mut winner: u8 = state.player; |
||||
|
for (i, card) in state.trick.iter().enumerate() { |
||||
|
match card { |
||||
|
Some(x) => { |
||||
|
if (suitOf(*x) == suitOf(firstCard) && !trumped && rankOf(*x) > maxRank) |
||||
|
|| (Some(suitOf(*x)) == state.trump && (!trumped || rankOf(*x) > maxRank)) |
||||
|
{ |
||||
|
maxRank = rankOf(*x); |
||||
|
winner = (8 + i as u8 - (3 - state.player)) % 4; |
||||
|
if Some(suitOf(*x)) == state.trump { |
||||
|
trumped = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
None => (), |
||||
|
} |
||||
|
} |
||||
|
let newWon: u8 = state.tricksWonBy0 + if winner == 0 || winner == 2 { 1 } else { 0 }; |
||||
|
let mut sizes = state.sizes; |
||||
|
sizes[usize::from(state.player)] -= 1; |
||||
|
GameState { |
||||
|
player: winner, |
||||
|
handPlayer: state.handPlayer, |
||||
|
trump: state.trump, |
||||
|
hand, |
||||
|
ruffs, |
||||
|
played, |
||||
|
trick: [None, None, None], |
||||
|
tricksWonBy0: newWon, |
||||
|
sizes, |
||||
|
} |
||||
|
} else { |
||||
|
let mut trick: [Option<Card>; 3] = state.trick; |
||||
|
for i in 0..3 { |
||||
|
if trick[i] == None { |
||||
|
trick[i] = Some(action); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
let mut sizes = state.sizes; |
||||
|
sizes[usize::from(state.player)] -= 1; |
||||
|
GameState { |
||||
|
player: (state.player + 1) % 4, |
||||
|
handPlayer: state.handPlayer, |
||||
|
trump: state.trump, |
||||
|
hand, |
||||
|
ruffs, |
||||
|
played, |
||||
|
trick, |
||||
|
tricksWonBy0: state.tricksWonBy0, |
||||
|
sizes, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn card(suit: Suit, rank: u8) -> Card { |
||||
|
Card { suit, rank } |
||||
|
} |
||||
|
|
||||
|
fn new_node(state: GameState) -> Node { |
||||
|
Node { |
||||
|
visited: 0, |
||||
|
value: 0, |
||||
|
state, |
||||
|
outcomes: [ |
||||
|
None, None, None, None, None, None, None, None, None, None, None, None, None, None, |
||||
|
None, None, None, None, None, None, None, None, None, None, None, None, None, None, |
||||
|
None, None, None, None, None, None, None, None, None, None, None, None, None, None, |
||||
|
None, None, None, None, None, None, None, None, None, None, |
||||
|
], |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn hands(state: &GameState) -> [[Option<u8>; 13]; 4] { |
||||
|
let mut hand: [[Option<u8>; 13] 4] = [vec![], vec![], vec![], vec![]]; |
||||
|
let mut seen = state.played; |
||||
|
for i in state.hand.iter() { |
||||
|
match i { |
||||
|
None => (), |
||||
|
Some(x) => { |
||||
|
hand[usize::from(state.handPlayer)].push(cardToNum(*x)); |
||||
|
seen.set(cardToNum(*x).into(), true); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
let mut sizes = state.sizes; |
||||
|
let mut allowed: [[bool; 4]; 52] = [[true, true, true, true]; 52]; |
||||
|
let mut n_to_assign = 52 - seen.count_ones(); |
||||
|
let mut n_assignable_to = [0, 0, 0, 0]; |
||||
|
for i in 0..52 { |
||||
|
if seen[i] { |
||||
|
continue; |
||||
|
} |
||||
|
allowed[i][usize::from(state.handPlayer)] = false; |
||||
|
for j in 0..4 { |
||||
|
if state.ruffs[j * 4 + i / 13] == true { |
||||
|
allowed[i][j] = false; |
||||
|
} |
||||
|
} |
||||
|
let mut n = 0; |
||||
|
let mut last = 0; |
||||
|
for j in 0..4 { |
||||
|
if allowed[i][j] { |
||||
|
n += 1; |
||||
|
last = j; |
||||
|
n_assignable_to[j] += 1; |
||||
|
} |
||||
|
} |
||||
|
if n == 1 { |
||||
|
hand[last].push(i as u8); |
||||
|
n_to_assign -= 1; |
||||
|
n_assignable_to[last] -= 1; |
||||
|
sizes[last] -= 1; |
||||
|
seen.set(i, true); |
||||
|
} |
||||
|
} |
||||
|
let mut running = 0; |
||||
|
while n_to_assign > 0 { |
||||
|
let mut any_done = false; |
||||
|
for i in 0..4 { |
||||
|
if sizes[i] > 0 && n_assignable_to[i] == sizes[i] { |
||||
|
for j in 0..52 { |
||||
|
if allowed[j][i] && !seen[j] { |
||||
|
seen.set(j, true); |
||||
|
hand[i].push(j as u8); |
||||
|
sizes[i] -= 1; |
||||
|
n_to_assign -= 1; |
||||
|
for q in 0..4 { |
||||
|
if allowed[j][q] { |
||||
|
n_assignable_to[q] -= 1; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
any_done = true; |
||||
|
} |
||||
|
} |
||||
|
if any_done { |
||||
|
continue; |
||||
|
} |
||||
|
while seen[running] { |
||||
|
running += 1; |
||||
|
} |
||||
|
let mut n: usize = allowed[running] |
||||
|
.iter() |
||||
|
.map(|&x| if x { 1 } else { 0 }) |
||||
|
.sum(); |
||||
|
for j in 0..4 { |
||||
|
if sizes[j] == 0 && allowed[running][j] == true { |
||||
|
n -= 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for j in 0..4 { |
||||
|
if !allowed[running][j] || sizes[j] == 0 { |
||||
|
continue; |
||||
|
} |
||||
|
if random::<usize>() % n == 0 { |
||||
|
hand[j].push(running as u8); |
||||
|
for i in 0..4 { |
||||
|
if allowed[running][i] { |
||||
|
n_assignable_to[i] -= 1; |
||||
|
} |
||||
|
} |
||||
|
sizes[j] -= 1; |
||||
|
n_to_assign -= 1; |
||||
|
break; |
||||
|
} else { |
||||
|
n -= 1; |
||||
|
} |
||||
|
} |
||||
|
seen.set(running, true); |
||||
|
} |
||||
|
hand |
||||
|
} |
||||
|
|
||||
|
fn search(node: &mut Node) -> (Vec<usize>, &mut Node) { |
||||
|
let mut cur: &mut Node = node; |
||||
|
let mut seen: Vec<usize> = vec![]; |
||||
|
loop { |
||||
|
cur.visited += 1; |
||||
|
if cur.state.played.count_ones() == 52 { |
||||
|
// abort
|
||||
|
return (seen, cur); |
||||
|
} |
||||
|
let mut score: f64 = -10000.0; |
||||
|
let mut idx: usize = 100000; |
||||
|
let hands = hands(&cur.state); |
||||
|
let mut outin: [bool; 4] = [true; 4]; |
||||
|
for i in hands[usize::from(cur.state.player)].iter() { |
||||
|
outin[usize::from(i / 13)] = false; |
||||
|
} |
||||
|
for i in hands[usize::from(cur.state.player)].iter() { |
||||
|
match cur.state.trick[0] { |
||||
|
None => (), |
||||
|
Some(x) => { |
||||
|
if !(fromSuit(suitOf(x)) == i / 13) |
||||
|
&& !(outin[usize::from(fromSuit(suitOf(x)))]) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
match &cur.outcomes[usize::from(*i)] { |
||||
|
None => { |
||||
|
seen.push((*i).into()); |
||||
|
return (seen, cur); |
||||
|
} |
||||
|
Some(x) => { |
||||
|
let ourscore: f64 = (x.value as f64 / (x.visited as f64 + 1.0)) |
||||
|
+ 6.0 * ((cur.visited as f64).ln() / (x.visited as f64 + 1.0)).sqrt(); |
||||
|
if ourscore > score { |
||||
|
idx = (*i).into(); |
||||
|
score = ourscore; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
match &mut cur.outcomes[idx] { |
||||
|
None => panic!("This will not happen. SEARCH PANIC KW!#"), |
||||
|
Some(x) => { |
||||
|
seen.push(idx); |
||||
|
cur = x; |
||||
|
} |
||||
|
} |
||||
|
//cur.outcomes[idx].clone().unwrap();
|
||||
|
//cur = *(cur.outcomes[idx].unwrap());
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn propagate(node: &mut Node, path: Vec<usize>, value: i8) { |
||||
|
let mut cur: &mut Node = node; |
||||
|
let mut sgn = -1; |
||||
|
for i in path { |
||||
|
cur.value += sgn * i64::from(value); |
||||
|
sgn = -sgn; |
||||
|
match &mut cur.outcomes[i] { |
||||
|
None => panic!("This will not happen. SEARCH PANIC KQ!#"), |
||||
|
Some(x) => { |
||||
|
cur = x; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
fn simulate(state: &GameState) -> i8 { |
||||
|
let mut cur: GameState = state.clone(); |
||||
|
loop { |
||||
|
let tricks0: u8 = cur.tricksWonBy0; |
||||
|
if cur.played.count_ones() == 52 { |
||||
|
if cur.handPlayer == 0 || cur.handPlayer == 2 { |
||||
|
return max(tricks0 as i8 - 6, 0) - max(7 - tricks0 as i8, 0); |
||||
|
} else { |
||||
|
return max(7 - tricks0 as i8, 0) - max(tricks0 as i8 - 6, 0); |
||||
|
} |
||||
|
} |
||||
|
let card: Card; |
||||
|
// ok. find action
|
||||
|
let hands = hands(&cur); |
||||
|
|
||||
|
let mut outin: [u8; 4] = [0; 4]; |
||||
|
for i in hands[usize::from(cur.player)].iter() { |
||||
|
outin[usize::from(i / 13)] += 1; |
||||
|
} |
||||
|
// i fucked up my life on this one
|
||||
|
let mut n: u8 = cur.sizes[usize::from(cur.player)]; |
||||
|
let mut follow: bool = false; |
||||
|
let mut led: u8 = 100; |
||||
|
match cur.trick[0] { |
||||
|
None => (), |
||||
|
Some(x) => { |
||||
|
if outin[usize::from(fromSuit(suitOf(x)))] > 0 { |
||||
|
n = outin[usize::from(fromSuit(suitOf(x)))]; |
||||
|
led = fromSuit(suitOf(x)); |
||||
|
follow = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
let mut p: Card = Card { |
||||
|
suit: Suit::Diamonds, |
||||
|
rank: 105, |
||||
|
}; // see this, something has gone terribly wrong.
|
||||
|
for i in hands[usize::from(cur.player)].iter() { |
||||
|
if follow && led != i / 13 { |
||||
|
continue; |
||||
|
} |
||||
|
if random::<u8>() % n == 0 { |
||||
|
p = numToCard(*i); |
||||
|
break; |
||||
|
} else { |
||||
|
n -= 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
card = p; |
||||
|
cur = state_transit(&cur, card); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn main() { |
||||
|
let mut played = bitarr!(0; 52); |
||||
|
played.set(12, true); |
||||
|
played.set(1, true); |
||||
|
played.set(0, true); |
||||
|
let mut node = new_node(GameState { |
||||
|
player: 0, |
||||
|
handPlayer: 0, |
||||
|
trump: Some(Suit::Hearts), |
||||
|
hand: [ |
||||
|
Some(card(Suit::Hearts, 0)), |
||||
|
Some(card(Suit::Hearts, 2)), |
||||
|
Some(card(Suit::Hearts, 7)), |
||||
|
Some(card(Suit::Hearts, 8)), |
||||
|
Some(card(Suit::Hearts, 9)), |
||||
|
Some(card(Suit::Hearts, 10)), |
||||
|
Some(card(Suit::Spades, 2)), |
||||
|
Some(card(Suit::Spades, 7)), |
||||
|
Some(card(Suit::Spades, 10)), |
||||
|
Some(card(Suit::Diamonds, 2)), |
||||
|
Some(card(Suit::Clubs, 1)), |
||||
|
Some(card(Suit::Clubs, 3)), |
||||
|
Some(card(Suit::Clubs, 7)), |
||||
|
], |
||||
|
ruffs: bitarr!(0; 16), |
||||
|
played, |
||||
|
trick: [ |
||||
|
Some(card(Suit::Spades, 12)), |
||||
|
Some(card(Suit::Spades, 1)), |
||||
|
Some(card(Suit::Spades, 0)), |
||||
|
], |
||||
|
tricksWonBy0: 0, |
||||
|
sizes: [13, 12, 12, 12], |
||||
|
}); |
||||
|
loop { |
||||
|
if node.state.player != node.state.handPlayer { |
||||
|
println!("Input the card played by player {}", node.state.player); |
||||
|
let mut n = String::new(); |
||||
|
io::stdin().read_line(&mut n).expect("Input!"); |
||||
|
let card: usize = n.trim().parse().expect("RIGHT!"); |
||||
|
match &node.outcomes[card] { |
||||
|
None => { |
||||
|
node = new_node(state_transit(&node.state, numToCard(card as u8))); |
||||
|
} |
||||
|
Some(x) => { |
||||
|
node = (**x).clone(); |
||||
|
} |
||||
|
} |
||||
|
continue; |
||||
|
} |
||||
|
for _ in 1..10000 { |
||||
|
let pathcur: (Vec<usize>, &mut Node) = search(&mut node); //search!
|
||||
|
let path = pathcur.0; |
||||
|
let cur = pathcur.1; |
||||
|
let n: i8; |
||||
|
if cur.state.played.count_ones() == 52 { |
||||
|
let tricks0: u8 = cur.state.tricksWonBy0; |
||||
|
n = if cur.state.handPlayer == 0 || cur.state.handPlayer == 2 { |
||||
|
max(tricks0 as i8 - 6, 0) - max(7 - tricks0 as i8, 0) |
||||
|
} else { |
||||
|
max(7 - tricks0 as i8, 0) - max(tricks0 as i8 - 6, 0) |
||||
|
} |
||||
|
} else { |
||||
|
cur.outcomes[path[path.len() - 1]] = Some(Box::new(new_node(state_transit( |
||||
|
&cur.state, |
||||
|
numToCard(path[path.len() - 1] as u8), |
||||
|
)))); //expand!
|
||||
|
n = simulate(&(*cur.outcomes[path[path.len() - 1]].as_ref().unwrap()).state); |
||||
|
//simulate!
|
||||
|
} |
||||
|
propagate(&mut node, path, n); |
||||
|
} |
||||
|
let mut highest_value = -4000.0; |
||||
|
let mut best_move = 100; |
||||
|
for i in 0..52 { |
||||
|
match &node.outcomes[i] { |
||||
|
None => (), |
||||
|
Some(x) => { |
||||
|
if x.value as f64 / x.visited as f64 > highest_value { |
||||
|
highest_value = x.value as f64 / x.visited as f64; |
||||
|
best_move = i; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
println!("{:?}", numToCard(best_move as u8)); |
||||
|
break; |
||||
|
match &node.outcomes[best_move] { |
||||
|
None => panic!("ok"), |
||||
|
Some(x) => { |
||||
|
node = (**x).clone(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue