Skip to content

Commit

Permalink
MVP.
Browse files Browse the repository at this point in the history
  • Loading branch information
hpmv committed Aug 6, 2023
1 parent d8f8ee3 commit e8c13b4
Show file tree
Hide file tree
Showing 18 changed files with 3,392 additions and 67 deletions.
30 changes: 15 additions & 15 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "wynn"
name = "penguin"
version = "0.1.0"
authors = ["Your Name"]
authors = ["hpmv"]
edition = "2018"

[lib]
Expand Down
36 changes: 31 additions & 5 deletions src/board2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ impl Board2 {
}

pub fn ended(self) -> bool {
(self.data >> 40) & 0x11111 == 12 || (self.data >> 45) & 0x11111 == 12
(self.data >> 40) & 0b11111 == 12 || (self.data >> 45) & 0b11111 == 12
}

pub fn do_move(self, m: Move) -> Self {
Expand Down Expand Up @@ -142,6 +142,9 @@ impl Board2 {
}

pub fn all_moves(self) -> Vec<(Move, Board2)> {
if self.ended() {
return Vec::new();
}
let mut moves = Vec::new();
let flat = self.flatten_to_bitarray();
let offsets = if self.maximizing() {
Expand Down Expand Up @@ -179,19 +182,30 @@ impl Board2 {
from: from as u8,
to,
};
moves.push((m, self.do_move(m)));
let board = self.do_move(m);
if board.ended() {
return vec![(m, board)];
}
moves.push((m, board));
}
}
moves
}

const CELL_WEIGHTS_PAWN: [i32; 25] = [
0, 3, 0, 3, 0, 3, 25, 25, 25, 3, 0, 25, 0, 25, 0, 3, 25, 25, 25, 3, 0, 3, 0, 3, 0,
0, 3, 0, 3, 0, //
3, 25, 25, 25, 3, //
0, 25, 0, 25, 0, //
3, 25, 25, 25, 3, //
0, 3, 0, 3, 0,
];

const CELL_WEIGHTS_KING: [i32; 25] = [
10, 0, 10, 0, 10, 0, 50, 50, 50, 0, 10, 50, 100000, 50, 10, 0, 50, 50, 50, 0, 10, 0, 10, 0,
10,
10, 0, 10, 0, 10, //
0, 200, 200, 200, 0, //
10, 200, 100000, 200, 10, //
0, 200, 200, 200, 0, //
10, 0, 10, 0, 10,
];

pub fn score(&self) -> i32 {
Expand Down Expand Up @@ -547,3 +561,15 @@ const MOVEMENT_TABLE: [[[u8; 4]; 8]; 32] = [
nil2,
nil2,
];

#[test]
fn test_all_moves_when_almost_win() {
let board = Board2::from_positions(&vec![3, 4, 7, 16, 13, 17, 18, 24, 8, 0, 1]);
println!("{:?}", board.all_moves());
}

#[test]
fn test_ended() {
let board = Board2::from_positions(&vec![3, 4, 7, 16, 13, 17, 18, 24, 12, 0, 0]);
assert!(board.ended());
}
57 changes: 44 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ pub mod cell;
pub mod player;

use board2::{Board2, Move};
use itertools::Itertools;
use serde::Serialize;
use std::{
collections::HashMap,
collections::{HashMap, HashSet},
fmt::Debug,
rc::Rc,
sync::{
Expand Down Expand Up @@ -37,23 +38,31 @@ impl Debug for SearchResult {
struct SearchState {
transposition_table: HashMap<Board2, (i32, Move)>,
next_transposition_table: HashMap<Board2, (i32, Move)>,
being_searched: HashSet<Board2>,
nodes_searched: usize,
max_depth: usize,
max_transposition_table_depth: usize,
stop: Box<dyn Fn() -> bool>,
collect_first_move_scores: bool,
}

struct Interrupted;

impl SearchState {
pub fn new(stop: Box<dyn Fn() -> bool>) -> SearchState {
pub fn new(
stop: Box<dyn Fn() -> bool>,
collect_first_move_scores: bool,
history_states: Vec<Board2>,
) -> SearchState {
SearchState {
transposition_table: HashMap::new(),
next_transposition_table: HashMap::new(),
being_searched: history_states.into_iter().collect(),
nodes_searched: 0,
max_depth: 0,
max_transposition_table_depth: 8,
max_transposition_table_depth: 20,
stop,
collect_first_move_scores,
}
}

Expand Down Expand Up @@ -82,6 +91,7 @@ impl SearchState {
let mut best_score = if maximizing { i32::MIN } else { i32::MAX };

let mut moves = state.all_moves();
moves.retain(|(_, board)| !self.being_searched.contains(&board));
if moves.is_empty() {
return Ok(SearchResult {
score: if maximizing { -100000 } else { 100000 },
Expand All @@ -103,6 +113,7 @@ impl SearchState {
}
});

self.being_searched.insert(state);
let mut first_move_scores = Vec::new();
for (one_move, next_state) in moves.into_iter() {
let SearchResult {
Expand All @@ -116,7 +127,7 @@ impl SearchState {
best_path = best_subpath;
best_path.push(one_move);
}
if score > alpha {
if score > alpha && (!self.collect_first_move_scores || depth > 0) {
alpha = score;
}
} else {
Expand All @@ -125,23 +136,26 @@ impl SearchState {
best_path = best_subpath;
best_path.push(one_move);
}
if score < beta {
if score < beta && (!self.collect_first_move_scores || depth > 0) {
beta = score;
}
}
if depth == 0 {
if depth == 0 && self.collect_first_move_scores {
first_move_scores.push((one_move, score));
}
if alpha >= beta {
break;
}
}
if depth < self.max_transposition_table_depth {
self.next_transposition_table.insert(
state.clone(),
(best_score, best_path.last().copied().unwrap()),
);
if self.next_transposition_table.len() < 30000000 {
self.next_transposition_table.insert(
state.clone(),
(best_score, best_path.last().copied().unwrap()),
);
}
}
self.being_searched.remove(&state);

Ok(SearchResult {
score: best_score,
Expand All @@ -166,15 +180,19 @@ impl SearchState {
pub struct PartialSearchResult {
pub depth: usize,
pub nodes_searched: usize,
pub transposition_table_size: usize,
pub result: SearchResult,
}

pub fn find_best_move(
state: Board2,
stop: impl Fn() -> bool + 'static,
partial: impl Fn(PartialSearchResult),
collect_first_move_scores: bool,
history_states: Vec<Board2>,
) -> Option<Move> {
let mut search_state = SearchState::new(Box::new(stop));
let mut search_state =
SearchState::new(Box::new(stop), collect_first_move_scores, history_states);
let mut result: Option<SearchResult> = None;
loop {
search_state.next_depth();
Expand All @@ -187,6 +205,7 @@ pub fn find_best_move(
partial(PartialSearchResult {
depth,
nodes_searched: search_state.nodes_searched,
transposition_table_size: search_state.next_transposition_table.len(),
result: one_result.clone(),
});
let win_found = one_result.score.abs() > 10000;
Expand Down Expand Up @@ -216,7 +235,12 @@ impl Engine {
}
}

pub fn find_best_move(&self, state: Vec<u8>) -> Option<String> {
pub fn find_best_move(
&self,
state: Vec<u8>,
collect_first_move_scores: bool,
history_states: Vec<u8>,
) -> Option<String> {
let state = Board2::from_positions(&state);
let stop = self.stop.clone();
let m = find_best_move(
Expand All @@ -227,6 +251,13 @@ impl Engine {
let m = JsValue::from_str(&m);
self.partial.call1(&JsValue::NULL, &m).unwrap();
},
collect_first_move_scores,
history_states
.into_iter()
.chunks(11)
.into_iter()
.map(|s| Board2::from_positions(&s.collect()))
.collect(),
);
m.map(|m| serde_json::to_string(&m).unwrap())
}
Expand All @@ -241,7 +272,7 @@ fn test_basic_engine() {
// does not terminate.
println!(
"{:?}",
engine.find_best_move(vec![0, 1, 3, 4, 20, 21, 23, 24, 22, 2, 1])
engine.find_best_move(vec![0, 1, 3, 4, 20, 21, 23, 24, 22, 2, 1], false, vec![])
);
}

Expand Down
6 changes: 6 additions & 0 deletions www/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
21 changes: 21 additions & 0 deletions www/HistoryView.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React, { useContext } from 'react';
import { HistoryContext } from './history';
import { Board } from './board';

export const HistoryView = ({ }) => {
const { history, dispatch } = useContext(HistoryContext);
const reversed = [...history.boards];
reversed.reverse();

return <div className="history-list">
{reversed.map(({ board, move }, ri) => {
const i = history.boards.length - 1 - ri;
return <div
key={i}
className={`history-entry ${i === history.currentIndex ? 'selected' : ''}`}
onClick={() => dispatch({ select: i })}>
<Board className="history-board" board={board} lastMove={move} canMove={false} turnNumber={i}></Board>
</div>
})}
</div>
};
Loading

0 comments on commit e8c13b4

Please sign in to comment.