Skip to content

Commit 2fb788c

Browse files
committed
Many minor but breaking changes + javadoc
- Adds MoveLibrary.getMoves - AbstractMoveLibrary getRecord changed to getRecords, and no more return optional. - Removes BasicEntry - OrderedUtils renamed to SortedUtils - PhysicalCores.UnreachableException renamed to UnavailableException
1 parent b0821cd commit 2fb788c

18 files changed

+127
-110
lines changed

.project

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,15 @@
2020
<nature>org.eclipse.jdt.core.javanature</nature>
2121
<nature>org.eclipse.m2e.core.maven2Nature</nature>
2222
</natures>
23+
<filteredResources>
24+
<filter>
25+
<id>1739988148319</id>
26+
<name></name>
27+
<type>30</type>
28+
<matcher>
29+
<id>org.eclipse.core.resources.regexFilterMatcher</id>
30+
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
31+
</matcher>
32+
</filter>
33+
</filteredResources>
2334
</projectDescription>

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ It also provides you with other useful building blocks like a clock, PerfT tests
1616
# Known limitations
1717
- The AI always supposes a player that can't move loosed or make a draw ... which is not always the case (typically in [Reversi](https://en.wikipedia.org/wiki/Reversi)).
1818
This doesn't means that the AI can't be used to play Reversi. Simply, the reversi move generator should have a special '*can't play*' move returned when a player can't move but the game is not finished.
19-
- The Negamax is quite basic, it implements a highly configurable transposition and quiesce move search, but none other advanced algorithm (no PV serach, futility prunig, killer or null move, etc ...).
19+
- The Negamax is quite basic, it implements a highly configurable transposition and quiesce move search, but none other advanced algorithm (no PV search, futility pruning, killer or null move, etc ...).
20+
21+
## TODO (probable breaking changes)
22+
- com.fathzer.games.ai.AbstractAI requires an ExecutionContext in its constructor. I think this is not a good approach (even it was mine ;-)) to have this non standard implementation detail exposed outside this public class. Moreover, this disallow building different multi-threading models in AI implementation. Typically it is currently impossible to use a fork/join pool to perform the search.
23+
As it seems, in real life, a new context is created at each AI invocation, a better approach would be to let the AI manage its own threading scheme, and simply pass a SearchContext to the constructor.
2024

2125
## TODO (maybe)
2226
- Make MoveLibrary implement AI?

src/main/java/com/fathzer/games/ai/SearchResult.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import com.fathzer.games.ai.evaluation.EvaluatedMove;
77
import com.fathzer.games.ai.evaluation.Evaluation;
8-
import com.fathzer.games.util.OrderedUtils;
8+
import com.fathzer.games.util.SortedUtils;
99

1010
/** The result of a best move search.
1111
*/
@@ -38,7 +38,7 @@ synchronized int getLow() {
3838
* @param value The evaluation of the move
3939
*/
4040
public synchronized void add(M move, Evaluation value) {
41-
OrderedUtils.insert(this.result, new EvaluatedMove<M>(move, value));
41+
SortedUtils.insert(this.result, new EvaluatedMove<M>(move, value));
4242
}
4343

4444
/** Updates the evaluation of a move.

src/main/java/com/fathzer/games/ai/SearchStatistics.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public void movesGenerated(int moveCount) {
103103
}
104104

105105
/** Gets the duration of the search in milliseconds.
106-
* <br>This method returns the time elapsed since the call to {@link #start()} or since this instance was created if {@link #start()} has not been called yet.
106+
* <br>This method returns the time elapsed since the call to {@link #clear()} or since this instance was created if {@link #clear()} has not been called yet.
107107
* @return a positive long
108108
*/
109109
public long getDurationMs() {

src/main/java/com/fathzer/games/ai/iterativedeepening/IterativeDeepeningSearch.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import com.fathzer.games.ai.DepthFirstAI;
1111
import com.fathzer.games.ai.iterativedeepening.IterativeDeepeningEngine.Mute;
1212
import com.fathzer.games.ai.iterativedeepening.IterativeDeepeningEngine.SearchEventLogger;
13-
import com.fathzer.games.util.OrderedUtils;
13+
import com.fathzer.games.util.SortedUtils;
1414
import com.fathzer.games.ai.DepthFirstSearchParameters;
1515
import com.fathzer.games.ai.SearchResult;
1616
import com.fathzer.games.ai.evaluation.EvaluatedMove;
@@ -97,7 +97,7 @@ private static <M> List<EvaluatedMove<M>> complete(SearchResult<M> result, List<
9797
return result.getList();
9898
}
9999
final var moves = new ArrayList<>(result.getList());
100-
ended.stream().forEach(em -> OrderedUtils.insert(moves, em));
100+
ended.stream().forEach(em -> SortedUtils.insert(moves, em));
101101
return moves;
102102
}
103103

src/main/java/com/fathzer/games/ai/transposition/BasicEntry.java

Lines changed: 0 additions & 66 deletions
This file was deleted.

src/main/java/com/fathzer/games/ai/transposition/BasicPolicy.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ public AlphaBetaState<M> accept(TranspositionTableEntry<M> entry, int depth, int
3535
return state;
3636
}
3737

38+
/** Processes an non exact transposition table entry.
39+
* <br>This method is called by {@link #accept(TranspositionTableEntry, int, int, int, IntUnaryOperator)} when entry is not null and it is not invalid and its type is not EXACT.
40+
* @param entry The entry
41+
* @param alpha The current alpha value
42+
* @param beta The current beta value
43+
* @param value The value stored in the entry, converted by the <code>toTTScoreConverter</code> function passed to the {@link #accept(TranspositionTableEntry, int, int, int, IntUnaryOperator)} method.
44+
* @param state The state to update (this state will be returned by @link #accept(TranspositionTableEntry, int, int, int, IntUnaryOperator)).
45+
*/
3846
protected void acceptNonExactRecord(TranspositionTableEntry<M> entry, int alpha, int beta, final int value, final AlphaBetaState<M> state) {
3947
boolean updated = false;
4048
if (LOWER_BOUND==entry.getEntryType()) {
@@ -71,6 +79,17 @@ public boolean store(TranspositionTable<M, B> table, long key, AlphaBetaState<M>
7179
return table.store(key, type, state.getDepth(), toTTScoreConverter.applyAsInt(state.getValue()), state.getBestMove(), p-> shouldReplace(p, key, state.getDepth(), type));
7280
}
7381

82+
/** Checks whether an entry should be replaced by new data.
83+
* @param entry The entry that is currently in the transposition table
84+
* @param newKey The new entry key
85+
* @param newDepth The new entry depth
86+
* @param newType The new entry type
87+
* @return true if the entry should be replaced, false otherwise. The default implementation returns true if:<ul>
88+
* <li>of course, the current entry is invalid</li>
89+
* <li>the new entry is exact and the current entry is not exact</li>
90+
* <li>the current entry and the new depth is &gt; the current depth</li>
91+
* </ul>
92+
*/
7493
protected boolean shouldReplace(TranspositionTableEntry<M> entry, long newKey, int newDepth, EntryType newType) {
7594
if (!entry.isValid()) {
7695
// Always write if no entry is in the table

src/main/java/com/fathzer/games/ai/transposition/EntryType.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,21 @@
44
import java.util.Collections;
55
import java.util.List;
66

7+
/** The type of a <a href="https://en.wikipedia.org/wiki/Transposition_table">transposition table</a> entry.
8+
*/
79
public enum EntryType {
8-
INVALID, EXACT, LOWER_BOUND, UPPER_BOUND;
10+
/** Entry is invalid (typically, not yet assigned) */
11+
INVALID,
12+
/** Entry has an exact value */
13+
EXACT,
14+
/** Entry has a lower bound value */
15+
LOWER_BOUND,
16+
/** Entry has an upper bound value */
17+
UPPER_BOUND;
918

10-
public static final List<EntryType> ALL = Collections.unmodifiableList(Arrays.asList(EntryType.values()));
19+
/** A list of all entry types
20+
* <br>This list is immutable and can be used as a constant.
21+
* <br>It can also be used as a replacement for the standard {@link #values()} method that allocates a new array every time it is called.
22+
*/
23+
public static final List<EntryType> ALL = Collections.unmodifiableList(Arrays.asList(EntryType.values()));
1124
}

src/main/java/com/fathzer/games/ai/transposition/SizeUnit.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
package com.fathzer.games.ai.transposition;
22

3+
/** A unit of size for memory size. */
34
public enum SizeUnit {
4-
MB(1024*1024), KB(1024), B(1);
5+
/** Megabytes (1024KB) */
6+
MB(1024*1024),
7+
/** Kilobytes (1024 Bytes) */
8+
KB(1024),
9+
/** Bytes (1B) */
10+
B(1);
511

612
private final int size;
713

src/main/java/com/fathzer/games/ai/transposition/TranspositionTablePolicy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
/** A class that decide if a transposition table entry should be replaced or not.
99
*/
1010
public interface TranspositionTablePolicy<M, B extends MoveGenerator<M>> {
11-
/** Process a transposition table entry.
11+
/** Processes a transposition table entry.
1212
* <br>This method is called before iterating on possible moves to use entry data in order to speed up the Negamax algorithm.
1313
* <br>One can override it to customize how transposition table entries are used.
1414
* <br>The default behaviour is to return the value of entries with {@link EntryType#EXACT} type and depth &gt;= the current negamax depth and ignore others.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/** <a href="https://en.wikipedia.org/wiki/Transposition_table">Transposition tables</a> related classes.
2+
*/
3+
package com.fathzer.games.ai.transposition;

src/main/java/com/fathzer/games/movelibrary/AbstractMoveLibrary.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.fathzer.games.movelibrary;
22

3+
import java.util.Collections;
34
import java.util.List;
45
import java.util.Optional;
56
import java.util.Random;
@@ -59,9 +60,9 @@ public Function<List<R>, R> weightedMoveSelector() {
5960

6061
/** Gets the records related to a position.
6162
* @param board The position
62-
* @return A list of records, each of them describes a move. Or an empty optional if the position is not in the base
63+
* @return A list of records, each of them describes a move, an empty list if the position is not in the base
6364
*/
64-
protected abstract Optional<List<R>> getRecord(B board);
65+
protected abstract List<R> getRecords(B board);
6566

6667
/** Converts a database record that describes an evaluated move to a move instance.
6768
* @param board The position
@@ -70,7 +71,7 @@ public Function<List<R>, R> weightedMoveSelector() {
7071
*/
7172
protected abstract EvaluatedMove<M> toEvaluatedMove(B board, R move);
7273

73-
/** Sets the move selector (the function that selects a move in the list of move records returned by {@link #getRecord(MoveGenerator)}
74+
/** Sets the move selector (the function that selects a move in the list of move records returned by {@link #getRecords(MoveGenerator)}
7475
* <br>By default, the move is randomly chosen in the list.
7576
* @param moveSelector A move selector
7677
*/
@@ -85,17 +86,25 @@ public void setNext(MoveLibrary<M, MoveGenerator<M>> next) {
8586
this.other = next;
8687
}
8788

89+
@Override
90+
public List<EvaluatedMove<M>> getMoves(B board) {
91+
final List<R> moves = getRecords(board);
92+
if (moves.isEmpty()) {
93+
return other==null ? Collections.emptyList() : other.getMoves(board);
94+
}
95+
return moves.stream().map(r -> toEvaluatedMove(board, r)).toList();
96+
}
97+
8898
/** {@inheritDoc}
8999
* <br>If this library can't find a move for this position, and a 'next' library was linked with this using {@link #setNext(MoveLibrary)},
90100
* its apply method's result is returned.
91101
*/
92102
@Override
93103
public Optional<EvaluatedMove<M>> apply(B board) {
94-
final Optional<List<R>> theRecordO = getRecord(board);
95-
if (theRecordO.isEmpty()) {
104+
final List<R> moves = getRecords(board);
105+
if (moves.isEmpty()) {
96106
return other==null ? Optional.empty() : other.apply(board);
97107
}
98-
final List<R> moves = theRecordO.get();
99108
final R selectedRecord = moveSelector.apply(moves);
100109
return Optional.of(toEvaluatedMove(board, selectedRecord));
101110
}

src/main/java/com/fathzer/games/movelibrary/MoveLibrary.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.fathzer.games.movelibrary;
22

3+
import java.util.List;
34
import java.util.Optional;
45
import java.util.function.Function;
56

@@ -12,6 +13,11 @@
1213
* @param <B> The type of game board
1314
*/
1415
public interface MoveLibrary<M, B extends MoveGenerator<M>> extends Function<B, Optional<EvaluatedMove<M>>> {
16+
/** Gets the moves for a position.
17+
* @param board a position
18+
* @return The moves available for the position in the library and their evaluations, or an empty list if it can't find any move.
19+
*/
20+
List<EvaluatedMove<M>> getMoves(B board);
1521

1622
/**
1723
* Gets the move to play for a position.

src/main/java/com/fathzer/games/util/MoveList.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
import java.util.ListIterator;
88

99
/** A specialized list optimized to sort moves.
10-
* Some ai algorithm, like alpha-beta pruning can be greatly optimized when moves are sorted with the (a priori) best moves first.
11-
* Usually, the <i>a priori</i> considers a lot of possible moves as equivalent;
10+
* <br>Some ai algorithm, like alpha-beta pruning can be greatly optimized when moves are sorted with the (a priori) best moves first.
11+
* Usually, the <i>a priori</i> comparator considers a lot of possible moves as equivalent;
1212
* Typically, in chess, all non promotion, non capture moves are considered equivalent.<br>
1313
* As <a href="https://en.wikipedia.org/wiki/Sorting_algorithm#Classification">sort has a O(<i>n</i>&#160;log&#160;<i>n</i>) computational complexity</a>, an optimization is to sort only the promotion or capture moves.
1414
* This class does this optimization by excluding from the sort all moves with an evaluation of Integer.MIN_VALUE.

0 commit comments

Comments
 (0)