aboutsummaryrefslogtreecommitdiff
path: root/src/train.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/train.rs')
-rw-r--r--src/train.rs53
1 files changed, 21 insertions, 32 deletions
diff --git a/src/train.rs b/src/train.rs
index e6ed822..1e68080 100644
--- a/src/train.rs
+++ b/src/train.rs
@@ -1,6 +1,6 @@
use std::{collections::VecDeque, fs, path::{Path, PathBuf}, sync::atomic::{AtomicUsize, Ordering}, time::{Duration, Instant}};
-use crate::{board::{GameBoard, Move, Turn, SYMMETRIES}, claude_player::ClaudePlayer, network::{Adam, Network, Rng, Sample}, neural_player::NeuralPlayer, player::{MinimaxPlayer, Player}};
+use crate::{board::{repetitions, GameBoard, Move, Outcome, Turn, REPETITION_DRAW, SYMMETRIES}, claude_player::ClaudePlayer, network::{Adam, Network, Rng, Sample}, neural_player::NeuralPlayer, player::{MinimaxPlayer, Player}};
/// Settings for `run`.
#[derive(Clone, Debug)]
@@ -58,25 +58,6 @@ impl Default for Config
}
}
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum Outcome
-{
- Tiger,
- Sheep,
- Draw,
-}
-
-impl Outcome
-{
- const ALL: [Outcome; 3] = [Outcome::Tiger, Outcome::Sheep, Outcome::Draw];
-
- /// +1 for a tiger win, -1 for a sheep win, matching the sign of the scores.
- fn value(self) -> f32
- {
- match self { Outcome::Tiger => 1.0, Outcome::Sheep => -1.0, Outcome::Draw => 0.0 }
- }
-}
-
/// A position from a game, with the searching player's score for it (clamped to -1..1).
#[derive(Clone, Copy, Debug)]
struct Ply
@@ -88,24 +69,29 @@ struct Ply
random: bool,
}
-/// Plays one game from the start. `choose` gives the move and score for a position; for the
-/// first `random_plies` plies, and with chance `epsilon` after, a random move is played instead
-/// (the position keeps `choose`'s score, and is marked `random` unless the two moves are the same).
-fn play(mut choose: impl FnMut(GameBoard, Turn) -> (Move, f32), ply_cap: usize, random_plies: usize, epsilon: f32, rng: &mut Rng)
+/// Plays one game from the start. `choose` gives the move and score for a position, given the
+/// boards before it; for the first `random_plies` plies, and with chance `epsilon` after, a random
+/// move is played instead (the position keeps `choose`'s score, and is marked `random` unless the
+/// two moves are the same). A threefold repetition, or reaching `ply_cap`, is a draw.
+fn play(mut choose: impl FnMut(GameBoard, Turn, &[GameBoard]) -> (Move, f32), ply_cap: usize, random_plies: usize, epsilon: f32, rng: &mut Rng)
-> (Outcome, Vec<Ply>)
{
let mut board = GameBoard::default();
let mut turn = Turn::Sheep;
let mut plies = Vec::new();
+ let mut previous = Vec::new();
while plies.len() < ply_cap
{
if board.check_win(turn)
{
// The side that just moved has won
- let winner = match turn { Turn::Sheep => Outcome::Tiger, Turn::Tiger => Outcome::Sheep };
- return (winner, plies);
+ return (Outcome::win(turn.next_turn()), plies);
+ }
+ if repetitions(&previous, &board) >= REPETITION_DRAW
+ {
+ return (Outcome::Draw, plies);
}
- let (searched, score) = choose(board, turn);
+ let (searched, score) = choose(board, turn, &previous);
let mut m = searched;
if plies.len() < random_plies || rng.next_f32() < epsilon
{
@@ -113,6 +99,7 @@ fn play(mut choose: impl FnMut(GameBoard, Turn) -> (Move, f32), ply_cap: usize,
m = moves[rng.below(moves.len())];
}
plies.push(Ply { board, turn, score: score.clamp(-1.0, 1.0), random: m != searched });
+ previous.push(board);
board.make_move_unchecked(m);
turn = turn.next_turn();
}
@@ -180,9 +167,9 @@ fn claude(depth: usize) -> ClaudePlayer
}
/// The move a minimax player picks, and the score it gave it.
-fn search(player: &mut (impl MinimaxPlayer + Player), board: GameBoard, turn: Turn) -> (Move, f32)
+fn search(player: &mut (impl MinimaxPlayer + Player), board: GameBoard, turn: Turn, previous: &[GameBoard]) -> (Move, f32)
{
- let m = player.pick_move(board, turn).expect("the game is not over, so there is a move");
+ let m = player.pick_move(board, turn, previous).expect("the game is not over, so there is a move");
(m, player.last_search().map_or(0.0, |r| r.score))
}
@@ -191,7 +178,7 @@ fn self_play(net: &Network, cfg: &Config, seed: u64) -> (Outcome, Vec<Sample>)
{
let mut player = neural(net, cfg.depth);
let mut rng = Rng::new(seed);
- let (outcome, plies) = play(|b, t| search(&mut player, b, t), cfg.ply_cap, cfg.random_plies, cfg.epsilon, &mut rng);
+ let (outcome, plies) = play(|b, t, p| search(&mut player, b, t, p), cfg.ply_cap, cfg.random_plies, cfg.epsilon, &mut rng);
let targets = td_targets(&plies, outcome, cfg.lambda);
let samples = plies.iter().zip(targets).map(|(p, target)| Sample { board: p.board, turn: p.turn, target }).collect();
(outcome, samples)
@@ -208,7 +195,7 @@ fn match_score<P: MinimaxPlayer + Player>(net: &Network, opponent: impl Fn() ->
let mut them = opponent();
let neural_side = if i % 2 == 0 { Turn::Sheep } else { Turn::Tiger };
let mut rng = Rng::new(seed ^ (i / 2) as u64);
- let (outcome, _) = play(|b, t| if t == neural_side { search(&mut me, b, t) } else { search(&mut them, b, t) },
+ let (outcome, _) = play(|b, t, p| if t == neural_side { search(&mut me, b, t, p) } else { search(&mut them, b, t, p) },
cfg.ply_cap, 4, 0.0, &mut rng);
match (outcome, neural_side)
{
@@ -389,9 +376,11 @@ mod tests
assert!(w[0].board.get_moves(w[0].turn).iter().any(|&m| w[0].board.after_move_unchecked(m) == w[1].board));
}
assert!(samples.iter().all(|s| (-1.0..=1.0).contains(&s.target)));
+ // A draw is the ply cap, or a position coming up a third time (the last time isn't a sample)
+ let repeated = samples.iter().any(|s| samples.iter().filter(|o| o.board == s.board && o.turn == s.turn).count() >= 2);
if outcome == Outcome::Draw
{
- assert_eq!(samples.len(), cfg.ply_cap);
+ assert!(samples.len() == cfg.ply_cap || repeated);
}
}