Skip to content

Commit 6cb66c1

Browse files
committed
chore: basic first move mover
1 parent 05830d6 commit 6cb66c1

File tree

11 files changed

+362
-1
lines changed

11 files changed

+362
-1
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ tetka-games = { path = "./games" }
1616

1717
[workspace]
1818
resolver = "2"
19-
members = [ "uxi", "games" ]
19+
members = [ "uxi", "games", "friendhaver" ]

friendhaver/Cargo.toml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "friendhaver"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
tetka = { path = ".." }
8+
rand = "0.8.5"
9+
derive-new = "0.5"
10+
derive_more = "0.99.18"
11+
12+
[profile.release]
13+
opt-level = 3
14+
codegen-units = 1
15+
lto = true

friendhaver/src/commands/context.rs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use std::str::FromStr;
2+
3+
use tetka::games::isolation::Position;
4+
5+
use crate::search::{self, Searcher};
6+
7+
pub struct Context {
8+
pub position: Position,
9+
pub searcher: search::Searcher,
10+
}
11+
12+
impl Default for Context {
13+
fn default() -> Self {
14+
let position =
15+
Position::from_str("--------/--------/p-------/-------P/--------/-------- w 1")
16+
.unwrap();
17+
Context {
18+
position,
19+
searcher: Searcher::new(position),
20+
}
21+
}
22+
}

friendhaver/src/commands/go.rs

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
use tetka::games::isolation::{self, Position};
2+
use tetka::uxi::{error, Bundle, Command, Flag, RunError};
3+
4+
use crate::search;
5+
6+
use super::Context;
7+
8+
// TODO: Move these macros into UXI
9+
10+
macro_rules! lock {
11+
($bundle:ident > $ctx:ident => $($stmt:stmt;)*) => {
12+
let $ctx = $bundle.lock();
13+
$(
14+
$stmt
15+
)*
16+
drop($ctx);
17+
};
18+
19+
($bundle:ident > mut $ctx:ident => $($stmt:stmt;)*) => {
20+
let mut $ctx = $bundle.lock();
21+
$(
22+
$stmt
23+
)*
24+
drop($ctx);
25+
};
26+
}
27+
28+
pub fn go() -> Command<Context> {
29+
Command::new(|bundle: Bundle<Context>| {
30+
lock! {
31+
bundle > ctx =>
32+
let position = ctx.position; // Get the position to search
33+
let mut searcher = ctx.searcher.clone(); // Get the previous search state
34+
}
35+
36+
let mut nodes = 0;
37+
38+
// Update the searcher with the new position and start searching.
39+
searcher.update_position(position);
40+
let limits = parse_limits(&bundle, &position)?;
41+
let bestmove = searcher.search(limits, &mut nodes);
42+
43+
println!("bestmove {}", bestmove);
44+
45+
lock! {
46+
bundle > mut ctx =>
47+
// Push the new search state to the context.
48+
ctx.searcher = searcher;
49+
}
50+
51+
Ok(())
52+
})
53+
// Flags for reporting the current time situation.
54+
.flag("p2inc", Flag::Single)
55+
.flag("p1inc", Flag::Single)
56+
.flag("p2time", Flag::Single)
57+
.flag("p1time", Flag::Single)
58+
.flag("movestogo", Flag::Single)
59+
// Flags for imposing other search limits.
60+
.flag("depth", Flag::Single)
61+
.flag("nodes", Flag::Single)
62+
.flag("movetime", Flag::Single)
63+
// Flags for setting the search type.
64+
// .flag("ponder", Flag::Single)
65+
.flag("infinite", Flag::Single)
66+
// This command should be run in a separate thread so that the Client
67+
// can still respond to and run other Commands while this one is running.
68+
.parallelize(true)
69+
}
70+
71+
fn parse_limits(bundle: &Bundle<Context>, position: &Position) -> Result<search::Limits, RunError> {
72+
////////////////////////////////////////////
73+
// Check which of the limit flags are set //
74+
////////////////////////////////////////////
75+
76+
// Standard Time Control flags
77+
let p1inc = bundle.is_flag_set("p1inc");
78+
let p2inc = bundle.is_flag_set("p2inc");
79+
let p1time = bundle.is_flag_set("p1time");
80+
let p2time = bundle.is_flag_set("p2time");
81+
82+
// Other limit flags
83+
let depth = bundle.is_flag_set("depth");
84+
let nodes = bundle.is_flag_set("nodes");
85+
let movetime = bundle.is_flag_set("movetime");
86+
87+
// Infinite flag
88+
let infinite = bundle.is_flag_set("infinite");
89+
90+
///////////////////////////////////////////////////////
91+
// Ensure that the given flag configuration is valid //
92+
///////////////////////////////////////////////////////
93+
94+
let std_tc = p2time || p1time || p2inc || p1inc;
95+
let oth_tc = depth || nodes || movetime;
96+
97+
// No other time control flags may be set alongside 'infinite'.
98+
if infinite && (std_tc || oth_tc) {
99+
return error!("bad flag set: time control flags set alongside infinite");
100+
}
101+
102+
// A little utility macro to parse the given flag into the required type.
103+
macro_rules! get_flag {
104+
($name:expr) => {
105+
match bundle.get_single_flag($name) {
106+
Some(value) => Some(value.parse()?),
107+
None => None,
108+
}
109+
};
110+
}
111+
112+
////////////////////////////////////////////
113+
// Parse the provided search/perft limits //
114+
////////////////////////////////////////////
115+
116+
Ok(search::Limits {
117+
maxnodes: get_flag!("nodes"),
118+
maxdepth: get_flag!("depth"),
119+
movetime: if std_tc {
120+
let (time, incr) = match position.side_to_move {
121+
isolation::Color::White => ("p1time", "p1inc"),
122+
isolation::Color::Black => ("p2time", "p2inc"),
123+
};
124+
125+
let time: u128 = get_flag!(time).unwrap_or(0);
126+
let incr: u128 = get_flag!(incr).unwrap_or(0);
127+
128+
Some((time / 20 + incr / 2).max(1))
129+
} else {
130+
get_flag!("movetime")
131+
},
132+
movestogo: get_flag!("movestogo"),
133+
})
134+
}

friendhaver/src/commands/mod.rs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// pub use bench::*;
2+
pub use context::*;
3+
pub use go::*;
4+
pub use position::*;
5+
pub use simple::*;
6+
7+
// mod bench;
8+
mod context;
9+
mod go;
10+
mod position;
11+
mod simple;

friendhaver/src/commands/position.rs

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use std::str::FromStr;
2+
3+
use tetka::games::{interface::PositionType, isolation};
4+
use tetka::uxi::{error, Bundle, Command, Flag, RunError};
5+
6+
use super::Context;
7+
8+
pub fn position() -> Command<Context> {
9+
Command::new(|bundle: Bundle<Context>| {
10+
let mut ctx = bundle.lock();
11+
12+
let has_fen = bundle.is_flag_set("fen");
13+
let has_startpos = bundle.is_flag_set("startpos");
14+
15+
if has_fen && has_startpos {
16+
return error!("only one of 'fen <fen>' or 'startpos' flags expected");
17+
}
18+
19+
let fen = if has_startpos {
20+
"--------/--------/p-------/-------P/--------/-------- w 1".to_string()
21+
} else if has_fen {
22+
bundle.get_array_flag("fen").unwrap().join(" ")
23+
} else {
24+
return error!("one of 'startpos' or 'fen <fen>' flags expected");
25+
};
26+
27+
ctx.position = isolation::Position::from_str(&fen)?;
28+
29+
if bundle.is_flag_set("moves") {
30+
for mov in bundle.get_array_flag("moves").unwrap() {
31+
ctx.position = ctx
32+
.position
33+
.after_move::<true>(isolation::Move::from_str(&mov)?);
34+
}
35+
}
36+
37+
Ok(())
38+
})
39+
.flag("fen", Flag::Array(4))
40+
.flag("startpos", Flag::Boolean)
41+
.flag("moves", Flag::Variadic)
42+
}

friendhaver/src/commands/simple.rs

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use std::str::FromStr;
2+
3+
use tetka::games::isolation;
4+
use tetka::uxi::Command;
5+
6+
use super::Context;
7+
8+
pub fn d() -> Command<Context> {
9+
Command::new(|bundle| {
10+
let ctx = bundle.lock();
11+
println!("{}", ctx.position);
12+
13+
Ok(())
14+
})
15+
}
16+
17+
pub fn uginewgame() -> Command<Context> {
18+
Command::new(|bundle| {
19+
let mut ctx = bundle.lock();
20+
ctx.position = isolation::Position::from_str(
21+
"--------/--------/p-------/-------P/--------/-------- w 1",
22+
)?;
23+
24+
Ok(())
25+
})
26+
}
27+
28+
pub fn protocol() -> Command<Context> {
29+
Command::new(|bundle| {
30+
let ctx = bundle.lock();
31+
println!("current protocol: {}", ctx.protocol());
32+
Ok(())
33+
})
34+
}

friendhaver/src/main.rs

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use std::env;
2+
3+
use tetka::uxi::Client;
4+
5+
mod commands;
6+
mod options;
7+
mod search;
8+
9+
fn main() {
10+
let client = Client::new()
11+
.protocol("uii")
12+
.engine("FriendHaver v0.0.0")
13+
.author("Rak Laptudirm")
14+
// Register engine options.
15+
.option("Hash", options::hash())
16+
.option("Threads", options::threads())
17+
// Register the custom commands.
18+
.command("d", commands::d())
19+
.command("go", commands::go())
20+
.command("protocol", commands::protocol())
21+
.command("position", commands::position())
22+
.command("uginewgame", commands::uginewgame());
23+
24+
let args = env::args()
25+
.skip(1)
26+
.reduce(|acc, e| format!("{} {}", acc, e))
27+
.unwrap_or("".to_string());
28+
let args = args.trim().to_string();
29+
if args.is_empty() {
30+
client.start(Default::default());
31+
} else {
32+
println!("args found {}", args);
33+
if let Err(err) = client.run_cmd_string(args, Default::default()) {
34+
println!("{}", err);
35+
};
36+
}
37+
}

friendhaver/src/options.rs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use tetka::uxi::Parameter;
2+
3+
pub fn hash() -> Parameter {
4+
Parameter::Spin(16, 1, 33554432)
5+
}
6+
7+
pub fn threads() -> Parameter {
8+
Parameter::Spin(1, 1, 1024)
9+
}

friendhaver/src/search/mod.rs

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use tetka::games::{
2+
interface::{MoveType, PositionType},
3+
isolation,
4+
};
5+
6+
#[derive(Clone)]
7+
pub struct Searcher {
8+
position: isolation::Position,
9+
}
10+
11+
impl Searcher {
12+
pub fn new(position: isolation::Position) -> Searcher {
13+
Searcher { position }
14+
}
15+
16+
pub fn update_position(&mut self, position: isolation::Position) {
17+
self.position = position;
18+
}
19+
20+
pub fn search(&mut self, limits: Limits, nodes: &mut usize) -> isolation::Move {
21+
let moves = self.position.generate_moves::<false, true, true>();
22+
moves.into_iter().next().unwrap_or(isolation::Move::NULL)
23+
}
24+
}
25+
26+
#[derive(Debug)]
27+
pub struct Limits {
28+
pub maxdepth: Option<usize>,
29+
pub maxnodes: Option<usize>,
30+
pub movetime: Option<u128>,
31+
32+
#[allow(unused)]
33+
pub movestogo: Option<usize>,
34+
}

src/main.rs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use std::env;
2+
use std::{str::FromStr, time};
3+
use tetka::games::{isolation, perft};
4+
5+
fn main() {
6+
let depth = env::args().nth(1).unwrap_or("5".to_string());
7+
let fen = env::args()
8+
.nth(2)
9+
.unwrap_or("--------/--------/p-------/-------P/--------/-------- w 1".to_string());
10+
11+
let position = isolation::Position::from_str(&fen).unwrap();
12+
println!("{}", position);
13+
14+
let start = time::Instant::now();
15+
let nodes = perft::<true, true, isolation::Position>(position, depth.parse().unwrap());
16+
let elapsed = start.elapsed();
17+
18+
println!(
19+
"nodes {} nps {}",
20+
nodes,
21+
nodes as u128 * 1000 / elapsed.as_millis().max(1)
22+
)
23+
}

0 commit comments

Comments
 (0)