···1010- [x] Implement a system to test the performance of the generated moves
1111- [x] Maybe have some way to perform a search using iterative deepening?
1212- [x] Order moves before searching via heuristics to improve alpha-beta pruning
1313-- [ ] Continue searching past regular depth when captures are available (https://www.chessprogramming.org/Quiescence_Search)
1313+- [x] Continue searching past regular depth when captures are available (https://www.chessprogramming.org/Quiescence_Search)
1414- [ ] Improve static evaluation using pawn structure
1515- [ ] Improve endgame play by encouraging the king to the centre of the board and encouraging pawns to promote
1616- [ ] Incrementally update game information such as zobrist hash, material count, and other evaluation information
+52-2
src/starfish/internal/search.gleam
···211211 }
212212 moves ->
213213 case depth {
214214- // Once we reach the limit of our depth, we statically evaluate the position.
214214+ // Once we reach the limit of our depth, we continue searching until
215215+ // we reach only quiet positions.
215216 0 -> {
216216- let eval = evaluate.evaluate(game, moves)
217217+ let eval =
218218+ quiescent_search(game, moves, best_eval, best_opponent_move)
217219 let cached_positions =
218220 hash.cache(
219221 cached_positions,
···430432431433 position_improvement + move_specific_score
432434}
435435+436436+/// Search until we find a "quiet" position, to avoid thinking a position is good
437437+/// while really on the next move a valuable piece could be captured.
438438+/// https://www.chessprogramming.org/Quiescence_Search
439439+fn quiescent_search(
440440+ game: Game,
441441+ moves: List(#(Move, Int)),
442442+ best_eval: Int,
443443+ best_opponent_move: Int,
444444+) -> Int {
445445+ let evaluation = evaluate.evaluate(game, moves)
446446+447447+ use <- bool.guard(evaluation >= best_opponent_move, evaluation)
448448+449449+ let best_eval = case evaluation > best_eval {
450450+ True -> evaluation
451451+ False -> best_eval
452452+ }
453453+454454+ quiescent_search_loop(game, moves, best_eval, best_opponent_move)
455455+}
456456+457457+fn quiescent_search_loop(
458458+ game: Game,
459459+ moves: List(#(Move, Int)),
460460+ best_eval: Int,
461461+ best_opponent_move: Int,
462462+) -> Int {
463463+ case moves {
464464+ [] -> best_eval
465465+ // We don't need to search quiet moves
466466+ [#(move.Move(..), _), ..moves] | [#(move.Castle(..), _), ..moves] ->
467467+ quiescent_search_loop(game, moves, best_eval, best_opponent_move)
468468+ [#(move, _), ..moves] -> {
469469+ let new_game = move.apply(game, move)
470470+ let new_moves = order_moves(new_game)
471471+ let evaluation =
472472+ -quiescent_search(new_game, new_moves, -best_opponent_move, -best_eval)
473473+474474+ use <- bool.guard(evaluation >= best_opponent_move, evaluation)
475475+ let best_eval = case evaluation > best_eval {
476476+ True -> evaluation
477477+ False -> best_eval
478478+ }
479479+ quiescent_search_loop(game, moves, best_eval, best_opponent_move)
480480+ }
481481+ }
482482+}