aboutsummaryrefslogtreecommitdiff
path: root/src/claude-player.rs
blob: 4590c12440b0b2533114991314eab1dbef2187db (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
use std::{collections::HashMap, time::Duration};

use crate::{board::{GameBoard, Move, PieceType, Turn}, player::{CacheEntry, MinimaxPlayer}};

/// Sheep that start the game: on the board plus in hand.
const TOTAL_SHEEP: u32 = 20;

/// A computer player that searches with minimax for `time_limit` per move and scores positions with `evaluate`.
pub struct ClaudePlayer
{
	pub time_limit: Duration,
	cache: HashMap<(GameBoard, Turn, usize), CacheEntry>
}

impl Default for ClaudePlayer
{
	fn default() -> Self
	{
		Self { time_limit: Duration::from_secs(1), cache: HashMap::new() }
	}
}

impl ClaudePlayer
{
	pub fn new(time_limit: Duration) -> Self
	{
		Self { time_limit, cache: HashMap::new() }
	}
}

impl MinimaxPlayer for ClaudePlayer
{
	/// Scored from the tigers' side. In order of weight:
	/// - dead sheep, since five of them wins the game for the tigers;
	/// - jumps the tigers could make right now (sheep left hanging);
	/// - tigers boxed in with no move at all, since trapping every tiger wins for the sheep;
	/// - tiger mobility in general, since the sheep win by squeezing it to zero.
	fn evaluate(&mut self, board: &GameBoard) -> f32
	{
		let dead = TOTAL_SHEEP - board.sheep_count() - board.sheep_left();

		let tiger_moves = board.get_moves(Turn::Tiger);
		let jumps = tiger_moves.iter().filter(|m| is_jump(**m)).count();

		let mut trapped = 0;
		for y in 0..5
		{
			for x in 0..5
			{
				if matches!(board.get((x, y)), PieceType::Tiger)
					&& !tiger_moves.iter().any(|m| matches!(m, Move::MoveTiger { from, .. } if *from == (x, y)))
				{
					trapped += 1;
				}
			}
		}

		100.0 * dead as f32
			+ 20.0 * jumps as f32
			- 15.0 * trapped as f32
			+ 2.0 * tiger_moves.len() as f32
	}

	fn time_limit(&self) -> Duration { self.time_limit }

	fn cache(&mut self) -> Option<&mut dyn crate::player::ScoreCache> {
		Some(&mut self.cache)
	}
}

/// True for a tiger move of two points, which always jumps (and kills) a sheep.
fn is_jump(mv: Move) -> bool
{
	match mv
	{
		Move::MoveTiger { from: (fx, fy), to: (tx, ty) } => fx.abs_diff(tx) == 2 || fy.abs_diff(ty) == 2,
		_ => false,
	}
}