mirror of
https://github.com/2OOP/pism.git
synced 2026-02-04 02:44:50 +00:00
Demo 6
* Server update with new dev changes (#305) * merge widgets with development * readd previous game thread code * Revert "readd previous game thread code" This reverts commitd24feef73e. * Revert "Merge remote-tracking branch 'origin/Development' into Development" This reverts commit59d46cb73c, reversing changes made to38681c5db0. * Revert "merge widgets with development" This reverts commit38681c5db0. * Merge 292 into development (#293) Applied template method pattern to abstract player * Added documentation to player classes and improved method names (#295) * mcts v1 * bitboard optimization * bitboard fix & mcts v2 & mcts v3. v3 still in progress and v4 coming soon * main * Hotfix for stuff * Logging and fixed user input getting stuck * Fixed merge mistakes (#307) * fast server connection * Fixed bugs and oversights * Renamed asset folder to resource, made resourceLoader more robust. Completed some TODO's, formatting * AppSettings now also get loaded into the assetmanager * start to reversi logic * legal moves now get highlighted in red * Moves flip dots. all tests pass. can play reversi local. * Working state. Split AudioManager into 3 different branches for easier testing and srp * Reworked to now use better defined generics and easier to use API. Added AudioResource to be used in changing volume * Made all of the updated classes more generic for better flexibility in unittesting * Added more flexible dependency injection to MusicManager for unittesting. Moved to event driven design for less complex code and lower runtime complexity. * Split SoundEffectManager from AudioManager. (#171) Clips no longer create a new clip instance each time they are played. A singular clip is made for each resource and is opened/closed when loaded/unloaded. When a clip is played that is already playing it'll stop playback and start again. Clip volume handling isn't done very well. * Unit tests for MusicManager.java * Hotfix for loading clip volume issue (#174) * Fixed AudioVolumemanager, all volumes calculations are now made in VolumeTypes enum * Added ability to remove a manager from VolumeTypes * Removed file no longer in use * Fixed grammer and spelling mistakes * Renamed VOLUME to MASTERVOLUME for better naming * Minor changes in API design * Renamed VolumeTypes to VolumeControl. Made it thread safe. Added docs to VolumeControl and co. removed .updateAllVolumes() in favor of auto updating inside enum instead * Added ErrorProne for potential bugs. Fixed potential bugs. * Small fixes. * Removed no more needed code. * Finished todo's * Moved restrictedAPI to future release * Finished todo's * Moved restrictedAPI to future release * Changed pom to be correct. Fixed SnowflakeGenerator not making unique ids. Changed naming for event implementation. Automated id getter for events. Added Error-Prone to all modules. Added parents to all modules. Added processors module. * SoundEffectManager now generic * Removed ResourceManager from AudioManagers * Added linelistener to SoundEffectAsset * commit * commit ofzo * Tests for SoundEffectManager * getLegalMoves logic seems fixed //todo write better tests * Tests toegevoegd * punk toegevoegd * Added shuffling on user request * Reworked NetworkingClientManager into SRP model. * Forgot to remove * Improved API for dependency injection * Some better docs. * gui refactor * fixed merge conflicts * Added exceptions. Added reconnect attempts and changeable address * Fixed event bug * add: reversi game * add: server chat box * visual update * Refactor to make Events easier to work with. * Quick fix for closing connection. * Documentation * Correct client creation and user polling * begin van audio display * Polling music event, fires every 1 second * Updated test. * Updated timings * Nuke everything on close. * Basis Audio Display toegevoegd + standaard CSS toegevoegd Kan nu zien hoe lang de song duurt, hoe lang ie al bezig is met draaien en de titel (-.mp3) * Clips now also return positional information * Skip Button * Skip Button * Fixes for garbage code by Omar * Tiny fix when natural skip * Small event fix * Faster event schedule for PlayingMusic event * test fix * added method for sorting the flipped pieces by distance to Move.position * new reversi test (both players no legal moves) * connect4 with minimax AI * Toegevoegd: -Play Button + CSS + Events -Previous Button + CSS + Events -Changed interface for AudioResource to include a pause button which works really well with mediaplayer, however now SoundEffectAsset has an unnessescary pause * Made it so that it indicates with the play/pause button if its paused or played * add simple flip animations and fixed(?) server somewhat * fixed tests * can start game from playerlist screen * tourney ready * spam minder * fixed setgamelabels * spam minder v2 * canvas changes * moved score out of game * can now go to last using previous and being at the first song * mainview false for sendchallengeview * moved score out of game * kleine ui fix * updated music ma,es * started working on the widget system * iets met timing verkeerd temporary fix * Fixes for garbage code by Omar * Added replace to reduce boiler plate code * Manually fallback to the fallback locale when a ResourceBundle is missing a resource key. Fallsback to "MISSING RESOUREC" if it's not present in the fallback. * Removed unused import and unused parameter * cool onhover effect for reversi * Made the GameState enum it's own file and fixed imports * Removed unused import * Turned abstract methods into an interface * Moved the Move record into it's own file, seperated from Game * Removed unused imports * Renamed Interface Playtable to IPlayable * Turned Abstract Method for AI into interface * Refactored Game to follow encapsulation principle * Removed unused imports * Applied encapsulation principle to TurnBasedBame.java * Privated methods that didn't have to be public * Reversi: made method private * Changed checkForEarlyDraw so it doesn't need a game as input. * Fixed warning "Warning:(27, 12) Copy constructor does not copy field 'mostRecentlyFlippedPieces'", removed unused field * Made connect4 public method private * half done with the widget system * added some comments and made some methods a bit more readable * widget system almost complete * Functional code, is now object orientated * Removed no more needed comments * started a basis for the tutorials, tic tac toe is almost done with some general stuff still to do. * rest van de tutorials toegevoegd * resizable true * fixed turn skip bug fixed end score bug now only shows legal and highlight moves when human * Squashed commit of the following: commit a517f2f302baa89f8ef59946a31c7bb59c56770f Author: Stef <stbuwalda@gmail.com> Date: Thu Nov 27 15:43:43 2025 +0100 Make it so the game shows "Waiting on ... to make their move". Styling isn't done but it is easier to see who's turn it is. There is a lot of structuring to do in the previous code... * merge widgets with development * readd previous game thread code * Revert "readd previous game thread code" This reverts commitd24feef73e. * Revert "Merge remote-tracking branch 'origin/Development' into Development" This reverts commit59d46cb73c, reversing changes made to38681c5db0. * Revert "merge widgets with development" This reverts commit38681c5db0. * Fixed compilation errors * Changed the way turns are being stored in TurnBasedGame. * Removed views * Added function input for enabling/disabling localization p/text * Fix eventbus problems (#265) * Added unsubscribe to EventFlow. ListenerHandler now functional. GlobalEventbus now user listenerHandler * getAllListeners * Removed nulls * Fixed stress tests * Added docs, no more list creation when adding events to the bus. * Fixed unsubscribe not working. * Moved away from deprecated functions * moved from wildcard to typed * Moved away from deprecated function * Debugs for EventBus and fixed unsubscribe all (#266) * Added unsubscribe to EventFlow. ListenerHandler now functional. GlobalEventbus now user listenerHandler * getAllListeners * Removed nulls * Fixed stress tests * Added docs, no more list creation when adding events to the bus. * Fixed unsubscribe not working. * Moved away from deprecated functions * moved from wildcard to typed * Moved away from deprecated function * Added debugging to GlobalEventBus * Fixed cleaning flow * Fixed unsubscribe all * Fixed unsubscribe all * Removed unused import * Fix music display not working (#267) * Added unsubscribe to EventFlow. ListenerHandler now functional. GlobalEventbus now user listenerHandler * getAllListeners * Removed nulls * Fixed stress tests * Added docs, no more list creation when adding events to the bus. * Fixed unsubscribe not working. * Moved away from deprecated functions * moved from wildcard to typed * Moved away from deprecated function * Added debugging to GlobalEventBus * Fixed cleaning flow * Fixed unsubscribe all * Fixed unsubscribe all * Removed unused import * Added LoadingWidget.java for server feedback * Replace deprecated with correct function * Removed loading widget from Server.java * Fixed old new EventFlow().listen() missing false as third param * Tutorials to Dev (#264) * Fixed garbage code * added a pop button * Tutorial images now use ImageAsset.java * Added button to continue and start game. Refactors * Refactored nextScreen runnable * Removed unused imports * Refactored switch statement * Added documentation * Removed space * Added translations * Added function input for enabling/disabling localization p/text --------- Co-authored-by: ramollia <> * Merge new framework into development (#269) * Created a somewhat generic TurnBasedGame thread. Temporary UI that only works for TicTacToe rn. Added a LocalPlayer with the intent to add more players * (RANDOM COMMIT) Hope it works * Changes by bas * Fixed dependency issues * Fixed major issue in game deepcopy * Merge conflict fix * Removed unused import * Update GTBGT branch from dev branch (#263) * started a basis for the tutorials, tic tac toe is almost done with some general stuff still to do. * rest van de tutorials toegevoegd * Removed views * Merge conflict fix * Removed unused import --------- Co-authored-by: michiel301b <m.brands.3@st.hanze.nl> Co-authored-by: ramollia <> Co-authored-by: Bas Antonius de Jong <49651652+BAFGdeJong@users.noreply.github.com> * Revert "Update GTBGT branch from dev branch (#263)" This reverts commit9134d7e343. * Fixed frontend not using GameController because of spaghetti code. * Removed unused imports * GameCanvas not implements a DrawPlayerMove that can be overridden for specific implementations * Created an event that will request the controller to refresh the UI. * ADDED DEPENDENCY. Renamed GameControllers to GameManagers, gameThread is not game controller. * Attempt at adding an online player. I think it doesn't work because of unsubscriben after success not working * Multiplayer is functional through OnlineThreadBehaviour. Empty slots are currently represented by -1 in the GUI. * Removed sout spam, added logger than I can't get to work. * Idek what these changes are * Te lang geen commit, sorry * Multiplayer seems to work pretty well now, hopefully I can add the other games soon. * Added unsubscribe to EventFlow. ListenerHandler now functional. GlobalEventbus now user listenerHandler * getAllListeners * Removed nulls * Inbetween commit of adding Reversi. This is a lot of spaghetti. * Fixed stress tests * Fixed typo in NetworkingGameClientHandler that prevented losses from being received * Missed 2nd typo. Fixed * Added docs, no more list creation when adding events to the bus. * Fixed unsubscribe not working. * Moved away from deprecated functions * moved from wildcard to typed * Moved away from deprecated function * Added debugging to GlobalEventBus * Fixed cleaning flow * Fixed unsubscribe all * Fixed unsubscribe all * Removed unused import * Works now with updated EventFlow(). Unsubscribing works. ReversiAIR has an issue where a forced move returns -1 and local play back button doesn't work properly. To be fixed * Fixed ReversiR issue that caused skip turn desync * Fixed color mismatch with server and online main player is now correct. * Added a bunch of java doc and small changes * Small changes * Added a new Thread Behaviour to test framework. * Fixed human error I made in TicTacToeR logic... * Fixed broken event and wrong player being presented as winner. * Idk changes * Fixed PR conflicts --------- Co-authored-by: michiel301b <m.brands.3@st.hanze.nl> Co-authored-by: Bas Antonius de Jong <49651652+BAFGdeJong@users.noreply.github.com> * added back button sounds because SOMEONE fucked it up..... * 231 connecting to server feedback (#275) * Added unsubscribe to EventFlow. ListenerHandler now functional. GlobalEventbus now user listenerHandler * getAllListeners * Removed nulls * Fixed stress tests * Added docs, no more list creation when adding events to the bus. * Fixed unsubscribe not working. * Moved away from deprecated functions * moved from wildcard to typed * Moved away from deprecated function * Added debugging to GlobalEventBus * Fixed cleaning flow * Fixed unsubscribe all * Fixed unsubscribe all * Removed unused import * Added LoadingWidget.java for server feedback * Imports * fixed loadingwidget * Workable LoadingWidget and trying to connect to server * Removed output * Small bug temp fix --------- Co-authored-by: ramollia <> * Double loading call fix, LoadingWidget docs * Main menu loader (#277) * LoadingWidget main menu * fixed garbage code * Fixed garbage code 2 * LoadWidget fix, added loading to starting the game. Removed unnecessary console output --------- Co-authored-by: ramollia <> * Fixed systems starting, before assets being loaded (I am retarded) * Added infinite boolean, fixed loading behaviour at startup * 272 remake game framework interfaces to properly represent vmc (#278) * Cleaned up a lot of old files and renamed/remade interfaces to better suit the framework * Broken commit * Fixed online play * Better file structure and closer to MVC * Best fix for white screen at start * Making threads verbose regarding exceptions * Loading circle, better loading colors. * Event bus now testable, improved UI (#284) * turn updates * smalle fixes aan turn updates * better human/ai selector with bot selection and depth on TicTacToeAIR * depth + thinktime back to AIs, along with a a specific TicTacToeAIRSleep * fixed overlapping back and disconnect buttons * Changed to debug instead of info * changed the transitionNextCustom to be easier to use * added getAllWidgets to WidgetContainer * Correct back view * added replacePrevious in ViewWidget * added removeIndexFromPreviousChain * fixed incorrect index counting * Fixt wrong view order * Removed todo * Challenge popups "Fixed" * Popups now remove themselves * localize the ChallengePopup text * made the game text a header instead * fixed getAllWidgets * Escape popup * fixed redundant container * Escape remove popup * Working escape menu * Added find functionality * Tutorials moved to escape menu * Escape can't be opened in mainview now * Can now test the event bus, created testable interfaces * Logging errors * Made events and handlers more generic * Suppress * Managers now have changeable eventbus * Tutorials fixed * Removed import * Single threaded eventbus * Fixed wrong eventbus * Removed get * Removed old code * Renaming * Optimization * Removed useless comment * Removed unnecessary imports * Rename * Renaming, refactor and type safety * Rename * Removed import --------- Co-authored-by: michiel301b <m.brands.3@st.hanze.nl> Co-authored-by: ramollia <> * initSystems now uses latch instead of timer. Moved single threads to Executor * Safety * Deleted unnecessary imports * Code cleanup * changed "fullscreen exit key combination" from esc to F11 * shitty fix for player selector spacing issue * shitty fix for player selector spacing issue v2 * fixed reversi colors being switched, causing multiple issues * Merge bitboards into development (#285) * added new classes for the games that use bitboards instead. also combined game with turnbasedgame * (DOES NOT COMPILE) In-between commit * turn updates * smalle fixes aan turn updates * Bitboard implemented with scuffed TicTacToe translation done by game. This should be done by the view. * Almost done with implementing bitboards. Reversi is broken and artifical players don't work yet. * better human/ai selector with bot selection and depth on TicTacToeAIR * fixed getLegalMoves * depth + thinktime back to AIs, along with a a specific TicTacToeAIRSleep * fixed overlapping back and disconnect buttons * Changed to debug instead of info * changed the transitionNextCustom to be easier to use * added getAllWidgets to WidgetContainer * Correct back view * added replacePrevious in ViewWidget * added removeIndexFromPreviousChain * fixed incorrect index counting * Fixt wrong view order * fixed? getLegalMoves * Everything is broken * Removed todo * fixed getLegalMoves & getFlips * Challenge popups "Fixed" * Fixed local and online play for both games * Popups now remove themselves * Removed souts for debugging * localize the ChallengePopup text * made the game text a header instead * made more classes deepClonable. * fixed getAllWidgets * Added comment * Escape popup * fixed redundant container * Made all network events async again * Escape remove popup * Working escape menu * Removed old AI and old files. Added a new generic random AI. game no longer deals with translation. * Drawing of board on canvas is now done from bitboards rather than translating. * Added a method getWinner() to game interface.Controller now tells gameThreads how to deal with drawing UI and sending a move to server. * Added find functionality * Added a ChatGPT generated MiniMaxAI based on the old MiniMaxAI but with alpha-beta pruning and heuristics for Reversi * Removed System-Outs to clean up console * Update BitGameCanvas.java * Merge fixes * Removed unused imports --------- Co-authored-by: ramollia <> Co-authored-by: michiel301b <m.brands.3@st.hanze.nl> Co-authored-by: lieght <49651652+BAFGdeJong@users.noreply.github.com> * Better limits to generic acceptance * Will fix tests etc later * Merge 292 into development (#293) Applied template method pattern to abstract player * Added documentation to player classes and improved method names (#295) * Init server code * Moves * Testing code * Removed Generics, pray nothing breaks. * Code cleanup * Tests and better instantiation * Init challenges * Working challenges * Fixed bugs, easy to use host button * Fixed tic tac toe naming * Fixes * Removed Generics, pray nothing breaks. * Added ability to take ServerPlayer from user * Added pairs * Using pairs now in server.java * Removed space in naming * Werkt nog niet * Fixed hasArgs * Closable server * Making moves works. Game notifies when game has ended. * Partial server refactor * Refactor done, added ability to subscribe * Working subscription, button only subs to reversi right now * Small improvements to usability, auto disconnect when server closes connection * Missed a boolean * Quick fix so more than one game can be played in succession * Removed user from subscription if in a game * UI fixes after game end * TableWidget * mcts v1 * Moved subscriptions to store * bitboard optimization * Collapsed interfaces from model portion * Fixed runtime error I forgot to fix. * Collapsed interfaces in Controller section * Collapsed interfaces in View section * Code readability * bitboard fix & mcts v2 & mcts v3. v3 still in progress and v4 coming soon * main * 289 server demo ready (#306) * Server update with new dev changes (#305) * merge widgets with development * readd previous game thread code * Revert "readd previous game thread code" This reverts commitd24feef73e. * Revert "Merge remote-tracking branch 'origin/Development' into Development" This reverts commit59d46cb73c, reversing changes made to38681c5db0. * Revert "merge widgets with development" This reverts commit38681c5db0. * Merge 292 into development (#293) Applied template method pattern to abstract player * Added documentation to player classes and improved method names (#295) * mcts v1 * bitboard optimization * bitboard fix & mcts v2 & mcts v3. v3 still in progress and v4 coming soon * main * Hotfix for stuff * Logging and fixed user input getting stuck * Fixed merge mistakes * Revert "Merge remote-tracking branch 'refs/remotes/origin/main' into Development" This reverts commite2132b549d, reversing changes made to9aefcb9b7b. --------- Co-authored-by: ramollia <@> Co-authored-by: Ticho Hidding <tichohidding@gmail.com> Co-authored-by: Stef <48526421+StefBuwalda@users.noreply.github.com> Co-authored-by: Stef <stbuwalda@gmail.com> Co-authored-by: michiel <m.brands.3@st.hanze.nl> Co-authored-by: ramollia <> Co-authored-by: tichohidding <58555714+tichohidding@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
8146be16ed
commit
429e5bc90b
@@ -1,86 +0,0 @@
|
||||
package org.toop.game;
|
||||
|
||||
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
||||
import org.toop.framework.gameFramework.model.player.Player;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
// There is AI performance to be gained by getting rid of non-primitives and thus speeding up deepCopy
|
||||
public abstract class BitboardGame<T extends BitboardGame<T>> implements TurnBasedGame<T> {
|
||||
private final int columnSize;
|
||||
private final int rowSize;
|
||||
|
||||
private Player<T>[] players;
|
||||
|
||||
// long is 64 bits. Every game has a limit of 64 cells maximum.
|
||||
private final long[] playerBitboard;
|
||||
private int currentTurn = 0;
|
||||
|
||||
public BitboardGame(int columnSize, int rowSize, int playerCount, Player<T>[] players) {
|
||||
this.columnSize = columnSize;
|
||||
this.rowSize = rowSize;
|
||||
this.players = players;
|
||||
this.playerBitboard = new long[playerCount];
|
||||
|
||||
Arrays.fill(playerBitboard, 0L);
|
||||
}
|
||||
|
||||
public BitboardGame(BitboardGame<T> other) {
|
||||
this.columnSize = other.columnSize;
|
||||
this.rowSize = other.rowSize;
|
||||
|
||||
this.playerBitboard = other.playerBitboard.clone();
|
||||
this.currentTurn = other.currentTurn;
|
||||
this.players = Arrays.stream(other.players)
|
||||
.map(Player<T>::deepCopy)
|
||||
.toArray(Player[]::new);
|
||||
}
|
||||
|
||||
public int getColumnSize() {
|
||||
return this.columnSize;
|
||||
}
|
||||
|
||||
public int getRowSize() {
|
||||
return this.rowSize;
|
||||
}
|
||||
|
||||
public long getPlayerBitboard(int player) {
|
||||
return this.playerBitboard[player];
|
||||
}
|
||||
|
||||
public void setPlayerBitboard(int player, long bitboard) {
|
||||
this.playerBitboard[player] = bitboard;
|
||||
}
|
||||
|
||||
public int getPlayerCount() {
|
||||
return playerBitboard.length;
|
||||
}
|
||||
|
||||
public int getCurrentTurn() {
|
||||
return getCurrentPlayerIndex();
|
||||
}
|
||||
|
||||
public Player<T> getPlayer(int index) {return players[index];}
|
||||
|
||||
public int getCurrentPlayerIndex() {
|
||||
return currentTurn % playerBitboard.length;
|
||||
}
|
||||
|
||||
public int getNextPlayer() {
|
||||
return (currentTurn + 1) % playerBitboard.length;
|
||||
}
|
||||
|
||||
public Player<T> getCurrentPlayer(){
|
||||
return players[getCurrentPlayerIndex()];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public long[] getBoard() {return this.playerBitboard;}
|
||||
|
||||
public void nextTurn() {
|
||||
currentTurn++;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package org.toop.game;
|
||||
// TODO: Remove this, only used in ReversiCanvas. Needs to not
|
||||
public record Move(int position, char value) {}
|
||||
@@ -1,88 +0,0 @@
|
||||
package org.toop.game.gameThreads;
|
||||
|
||||
import org.toop.framework.eventbus.EventFlow;
|
||||
import org.toop.framework.gameFramework.GameState;
|
||||
import org.toop.framework.gameFramework.model.game.PlayResult;
|
||||
import org.toop.framework.gameFramework.model.game.threadBehaviour.AbstractThreadBehaviour;
|
||||
import org.toop.framework.gameFramework.view.GUIEvents;
|
||||
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
||||
import org.toop.framework.gameFramework.model.player.Player;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Handles local turn-based game logic at a fixed update rate.
|
||||
* <p>
|
||||
* Runs a separate thread that executes game turns at a fixed frequency (default 60 updates/sec),
|
||||
* applying player moves, updating the game state, and dispatching UI events.
|
||||
*/
|
||||
public class LocalFixedRateThreadBehaviour<T extends TurnBasedGame<T>> extends AbstractThreadBehaviour<T> implements Runnable {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a fixed-rate behaviour for a local turn-based game.
|
||||
*
|
||||
* @param game the game instance
|
||||
*/
|
||||
public LocalFixedRateThreadBehaviour(T game) {
|
||||
super(game);
|
||||
}
|
||||
|
||||
/** Starts the game loop thread if not already running. */
|
||||
@Override
|
||||
public void start() {
|
||||
if (isRunning.compareAndSet(false, true)) {
|
||||
new Thread(this).start();
|
||||
}
|
||||
}
|
||||
|
||||
/** Stops the game loop after the current iteration. */
|
||||
@Override
|
||||
public void stop() {
|
||||
isRunning.set(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main loop running at a fixed rate.
|
||||
* <p>
|
||||
* Fetches the current player's move, applies it to the game,
|
||||
* updates the UI, and handles game-ending states.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
final int UPS = 1;
|
||||
final long UPDATE_INTERVAL = 1_000_000_000L / UPS;
|
||||
long nextUpdate = System.nanoTime();
|
||||
|
||||
while (isRunning.get()) {
|
||||
long now = System.nanoTime();
|
||||
if (now >= nextUpdate) {
|
||||
nextUpdate += UPDATE_INTERVAL;
|
||||
|
||||
Player<T> currentPlayer = game.getPlayer(game.getCurrentTurn());
|
||||
long move = currentPlayer.getMove(game.deepCopy());
|
||||
PlayResult result = game.play(move);
|
||||
|
||||
updateUI();
|
||||
|
||||
GameState state = result.state();
|
||||
switch (state) {
|
||||
case WIN, DRAW -> {
|
||||
isRunning.set(false);
|
||||
new EventFlow().addPostEvent(GUIEvents.GameEnded.class, state == GameState.WIN, result.player()).postEvent();
|
||||
}
|
||||
case NORMAL, TURN_SKIPPED -> { /* continue */ }
|
||||
default -> {
|
||||
logger.error("Unexpected state {}", state);
|
||||
isRunning.set(false);
|
||||
throw new RuntimeException("Unknown state: " + state);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package org.toop.game.gameThreads;
|
||||
|
||||
import org.toop.framework.eventbus.EventFlow;
|
||||
import org.toop.framework.gameFramework.model.game.threadBehaviour.AbstractThreadBehaviour;
|
||||
import org.toop.framework.gameFramework.view.GUIEvents;
|
||||
import org.toop.framework.gameFramework.model.game.PlayResult;
|
||||
import org.toop.framework.gameFramework.GameState;
|
||||
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
||||
import org.toop.framework.gameFramework.model.player.Player;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Handles local turn-based game logic in its own thread.
|
||||
* <p>
|
||||
* Repeatedly gets the current player's move, applies it to the game,
|
||||
* updates the UI, and stops when the game ends or {@link #stop()} is called.
|
||||
*/
|
||||
public class LocalThreadBehaviour<T extends TurnBasedGame<T>> extends AbstractThreadBehaviour<T> implements Runnable {
|
||||
|
||||
/**
|
||||
* Creates a new behaviour for a local turn-based game.
|
||||
*
|
||||
* @param game the game instance
|
||||
*/
|
||||
public LocalThreadBehaviour(T game) {
|
||||
super(game);
|
||||
}
|
||||
|
||||
/** Starts the game loop in a new thread. */
|
||||
@Override
|
||||
public void start() {
|
||||
if (isRunning.compareAndSet(false, true)) {
|
||||
new Thread(this).start();
|
||||
}
|
||||
}
|
||||
|
||||
/** Stops the game loop after the current iteration. */
|
||||
@Override
|
||||
public void stop() {
|
||||
isRunning.set(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main game loop: gets the current player's move, applies it,
|
||||
* updates the UI, and handles end-of-game states.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
while (isRunning.get()) {
|
||||
Player<T> currentPlayer = game.getPlayer(game.getCurrentTurn());
|
||||
long move = currentPlayer.getMove(game.deepCopy());
|
||||
PlayResult result = game.play(move);
|
||||
|
||||
updateUI();
|
||||
|
||||
GameState state = result.state();
|
||||
switch (state) {
|
||||
case WIN, DRAW -> {
|
||||
isRunning.set(false);
|
||||
new EventFlow().addPostEvent(
|
||||
GUIEvents.GameEnded.class,
|
||||
state == GameState.WIN,
|
||||
result.player()
|
||||
).postEvent();
|
||||
}
|
||||
case NORMAL, TURN_SKIPPED -> { /* continue normally */ }
|
||||
default -> {
|
||||
logger.error("Unexpected state {}", state);
|
||||
isRunning.set(false);
|
||||
throw new RuntimeException("Unknown state: " + state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package org.toop.game.gameThreads;
|
||||
|
||||
import org.toop.framework.eventbus.EventFlow;
|
||||
import org.toop.framework.gameFramework.model.game.threadBehaviour.AbstractThreadBehaviour;
|
||||
import org.toop.framework.gameFramework.view.GUIEvents;
|
||||
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
||||
import org.toop.framework.gameFramework.model.game.SupportsOnlinePlay;
|
||||
import org.toop.framework.gameFramework.model.player.Player;
|
||||
import org.toop.game.players.OnlinePlayer;
|
||||
|
||||
/**
|
||||
* Handles online multiplayer game logic.
|
||||
* <p>
|
||||
* Reacts to server events, sending moves and updating the game state
|
||||
* for the local player while receiving moves from other players.
|
||||
*/
|
||||
public class OnlineThreadBehaviour<T extends TurnBasedGame<T>> extends AbstractThreadBehaviour<T> implements SupportsOnlinePlay {
|
||||
/**
|
||||
* Creates behaviour and sets the first local player
|
||||
* (non-online player) from the given array.
|
||||
*/
|
||||
public OnlineThreadBehaviour(T game) {
|
||||
super(game);
|
||||
}
|
||||
|
||||
/** Finds the first non-online player in the array. */
|
||||
private int getFirstNotOnlinePlayer(Player<T>[] players) {
|
||||
for (int i = 0; i < players.length; i++) {
|
||||
if (!(players[i] instanceof OnlinePlayer)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("All players are online players");
|
||||
}
|
||||
|
||||
/** Starts processing network events for the local player. */
|
||||
@Override
|
||||
public void start() {
|
||||
isRunning.set(true);
|
||||
}
|
||||
|
||||
/** Stops processing network events. */
|
||||
@Override
|
||||
public void stop() {
|
||||
isRunning.set(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the server notifies that it is the local player's turn.
|
||||
* Sends the generated move back to the server.
|
||||
*/
|
||||
@Override
|
||||
public void onYourTurn(long clientId) {
|
||||
if (!isRunning.get()) return;
|
||||
long move = game.getPlayer(game.getCurrentTurn()).getMove(game.deepCopy());
|
||||
sendMove(clientId, move);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a move received from the server for any player.
|
||||
* Updates the game state and triggers a UI refresh.
|
||||
*/
|
||||
public void onMoveReceived(long move) {
|
||||
if (!isRunning.get()) return;
|
||||
game.play(move);
|
||||
|
||||
updateUI();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the end of the game as notified by the server.
|
||||
* Updates the UI to show a win or draw result for the local player.
|
||||
*/
|
||||
public void gameFinished(String condition) {
|
||||
switch(condition.toUpperCase()){
|
||||
case "WIN", "LOSS" -> new EventFlow().addPostEvent(GUIEvents.GameEnded.class, true, game.getWinner()).postEvent();
|
||||
case "DRAW" -> new EventFlow().addPostEvent(GUIEvents.GameEnded.class, false, -1).postEvent();
|
||||
default -> {
|
||||
logger.error("Invalid condition");
|
||||
throw new RuntimeException("Unknown condition");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package org.toop.game.gameThreads;
|
||||
|
||||
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
||||
import org.toop.framework.networking.events.NetworkEvents;
|
||||
|
||||
/**
|
||||
* Online thread behaviour that adds a fixed delay before processing
|
||||
* the local player's turn.
|
||||
* <p>
|
||||
* This is identical to {@link OnlineThreadBehaviour}, but inserts a
|
||||
* short sleep before delegating to the base implementation.
|
||||
*/
|
||||
public class OnlineWithSleepThreadBehaviour<T extends TurnBasedGame<T>> extends OnlineThreadBehaviour<T> {
|
||||
|
||||
/**
|
||||
* Creates the behaviour and forwards the players to the base class.
|
||||
*
|
||||
* @param game the online-capable turn-based game
|
||||
*/
|
||||
public OnlineWithSleepThreadBehaviour(T game) {
|
||||
super(game);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits briefly before handling the "your turn" event.
|
||||
*
|
||||
* @param event the network event indicating it's this client's turn
|
||||
*/
|
||||
@Override
|
||||
public void onYourTurn(long clientId) {
|
||||
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
super.onYourTurn(clientId);
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
package org.toop.game.games.reversi;
|
||||
|
||||
import org.toop.framework.gameFramework.GameState;
|
||||
import org.toop.framework.gameFramework.model.game.PlayResult;
|
||||
import org.toop.framework.gameFramework.model.player.Player;
|
||||
import org.toop.game.BitboardGame;
|
||||
|
||||
public class BitboardReversi extends BitboardGame<BitboardReversi> {
|
||||
|
||||
public record Score(int black, int white) {}
|
||||
|
||||
private final long notAFile = 0xfefefefefefefefeL;
|
||||
private final long notHFile = 0x7f7f7f7f7f7f7f7fL;
|
||||
|
||||
public BitboardReversi(Player<BitboardReversi>[] players) {
|
||||
super(8, 8, 2, players);
|
||||
|
||||
// Black (player 0)
|
||||
setPlayerBitboard(0, (1L << (3 + 4 * 8)) | (1L << (4 + 3 * 8)));
|
||||
|
||||
// White (player 1)
|
||||
setPlayerBitboard(1, (1L << (3 + 3 * 8)) | (1L << (4 + 4 * 8)));
|
||||
}
|
||||
|
||||
public BitboardReversi(BitboardReversi other) {
|
||||
super(other);
|
||||
}
|
||||
|
||||
public long getLegalMoves() {
|
||||
final long player = getPlayerBitboard(getCurrentPlayerIndex());
|
||||
final long opponent = getPlayerBitboard(getNextPlayer());
|
||||
|
||||
long legalMoves = 0L;
|
||||
|
||||
// north & south
|
||||
legalMoves |= computeMoves(player, opponent, 8, -1L);
|
||||
legalMoves |= computeMoves(player, opponent, -8, -1L);
|
||||
|
||||
// east & west
|
||||
legalMoves |= computeMoves(player, opponent, 1, notAFile);
|
||||
legalMoves |= computeMoves(player, opponent, -1, notHFile);
|
||||
|
||||
// north-east & north-west & south-east & south-west
|
||||
legalMoves |= computeMoves(player, opponent, 9, notAFile);
|
||||
legalMoves |= computeMoves(player, opponent, 7, notHFile);
|
||||
legalMoves |= computeMoves(player, opponent, -7, notAFile);
|
||||
legalMoves |= computeMoves(player, opponent, -9, notHFile);
|
||||
|
||||
return legalMoves;
|
||||
}
|
||||
|
||||
public long getFlips(long move) {
|
||||
final long player = getPlayerBitboard(getCurrentPlayerIndex());
|
||||
final long opponent = getPlayerBitboard(getNextPlayer());
|
||||
|
||||
long flips = 0L;
|
||||
|
||||
// north & south
|
||||
flips |= computeFlips(move, player, opponent, 8, -1L);
|
||||
flips |= computeFlips(move, player, opponent, -8, -1L);
|
||||
|
||||
// east & west
|
||||
flips |= computeFlips(move, player, opponent, 1, notAFile);
|
||||
flips |= computeFlips(move, player, opponent, -1, notHFile);
|
||||
|
||||
// north-east & north-west & south-east & south-west
|
||||
flips |= computeFlips(move, player, opponent, 9, notAFile);
|
||||
flips |= computeFlips(move, player, opponent, 7, notHFile);
|
||||
flips |= computeFlips(move, player, opponent, -7, notAFile);
|
||||
flips |= computeFlips(move, player, opponent, -9, notHFile);
|
||||
|
||||
return flips;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitboardReversi deepCopy() {return new BitboardReversi(this);}
|
||||
|
||||
public PlayResult play(long move) {
|
||||
final long flips = getFlips(move);
|
||||
|
||||
long player = getPlayerBitboard(getCurrentPlayerIndex());
|
||||
long opponent = getPlayerBitboard(getNextPlayer());
|
||||
|
||||
player |= move | flips;
|
||||
opponent &= ~flips;
|
||||
|
||||
setPlayerBitboard(getCurrentPlayerIndex(), player);
|
||||
setPlayerBitboard(getNextPlayer(), opponent);
|
||||
|
||||
nextTurn();
|
||||
|
||||
final long nextLegalMoves = getLegalMoves();
|
||||
|
||||
if (nextLegalMoves == 0) {
|
||||
nextTurn();
|
||||
|
||||
final long skippedLegalMoves = getLegalMoves();
|
||||
|
||||
if (skippedLegalMoves == 0) {
|
||||
int winner = getWinner();
|
||||
|
||||
if (winner == -1) {
|
||||
return new PlayResult(GameState.DRAW, -1);
|
||||
}
|
||||
|
||||
return new PlayResult(GameState.WIN, winner);
|
||||
}
|
||||
|
||||
return new PlayResult(GameState.TURN_SKIPPED, getCurrentPlayerIndex());
|
||||
}
|
||||
|
||||
return new PlayResult(GameState.NORMAL, getCurrentPlayerIndex());
|
||||
}
|
||||
|
||||
public Score getScore() {
|
||||
return new Score(
|
||||
Long.bitCount(getPlayerBitboard(0)),
|
||||
Long.bitCount(getPlayerBitboard(1))
|
||||
);
|
||||
}
|
||||
|
||||
public int getWinner(){
|
||||
final long black = getPlayerBitboard(0);
|
||||
final long white = getPlayerBitboard(1);
|
||||
|
||||
final int blackCount = Long.bitCount(black);
|
||||
final int whiteCount = Long.bitCount(white);
|
||||
|
||||
if (blackCount == whiteCount){
|
||||
return -1;
|
||||
}
|
||||
else if (blackCount > whiteCount){
|
||||
return 0;
|
||||
}
|
||||
else{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private long computeMoves(long player, long opponent, int shift, long mask) {
|
||||
long moves = shift(player, shift, mask) & opponent;
|
||||
long captured = moves;
|
||||
|
||||
while (moves != 0) {
|
||||
moves = shift(moves, shift, mask) & opponent;
|
||||
captured |= moves;
|
||||
}
|
||||
|
||||
long landing = shift(captured, shift, mask);
|
||||
return landing & ~(player | opponent);
|
||||
}
|
||||
|
||||
private long computeFlips(long move, long player, long opponent, int shift, long mask) {
|
||||
long flips = 0L;
|
||||
long pos = move;
|
||||
|
||||
while (true) {
|
||||
pos = shift(pos, shift, mask);
|
||||
if (pos == 0) return 0L;
|
||||
|
||||
if ((pos & opponent) != 0) flips |= pos;
|
||||
else if ((pos & player) != 0) return flips;
|
||||
else return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
private long shift(long bit, int shift, long mask) {
|
||||
return shift > 0 ? (bit << shift) & mask : (bit >>> -shift) & mask;
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package org.toop.game.games.tictactoe;
|
||||
|
||||
import org.toop.framework.gameFramework.GameState;
|
||||
import org.toop.framework.gameFramework.model.game.PlayResult;
|
||||
import org.toop.framework.gameFramework.model.player.Player;
|
||||
import org.toop.game.BitboardGame;
|
||||
|
||||
public class BitboardTicTacToe extends BitboardGame<BitboardTicTacToe> {
|
||||
private final long[] winningLines = {
|
||||
0b111000000L, // top row
|
||||
0b000111000L, // middle row
|
||||
0b000000111L, // bottom row
|
||||
0b100100100L, // left column
|
||||
0b010010010L, // middle column
|
||||
0b001001001L, // right column
|
||||
0b100010001L, // diagonal
|
||||
0b001010100L // anti-diagonal
|
||||
};
|
||||
|
||||
public BitboardTicTacToe(Player<BitboardTicTacToe>[] players) {
|
||||
super(3, 3, 2, players);
|
||||
}
|
||||
public BitboardTicTacToe(BitboardTicTacToe other) {
|
||||
super(other);
|
||||
}
|
||||
|
||||
public long getLegalMoves() {
|
||||
final long xBitboard = getPlayerBitboard(0);
|
||||
final long oBitboard = getPlayerBitboard(1);
|
||||
|
||||
final long taken = (xBitboard | oBitboard);
|
||||
return (~taken) & 0x1ffL;
|
||||
}
|
||||
|
||||
public int getWinner(){
|
||||
return getCurrentPlayerIndex();
|
||||
}
|
||||
|
||||
public PlayResult play(long move) {
|
||||
// Player loses if move is invalid
|
||||
if ((move & getLegalMoves()) == 0 || Long.bitCount(move) != 1){
|
||||
return new PlayResult(GameState.WIN, getNextPlayer());
|
||||
}
|
||||
|
||||
// Move is legal, make move
|
||||
long playerBitboard = getPlayerBitboard(getCurrentPlayerIndex());
|
||||
playerBitboard |= move;
|
||||
|
||||
setPlayerBitboard(getCurrentPlayerIndex(), playerBitboard);
|
||||
|
||||
// Check if current player won
|
||||
if (checkWin(playerBitboard)) {
|
||||
return new PlayResult(GameState.WIN, getCurrentPlayerIndex());
|
||||
}
|
||||
|
||||
// Proceed to next turn
|
||||
nextTurn();
|
||||
|
||||
|
||||
// Check for early draw
|
||||
if (getLegalMoves() == 0L || checkEarlyDraw()) {
|
||||
return new PlayResult(GameState.DRAW, -1);
|
||||
}
|
||||
|
||||
// Nothing weird happened, continue on as normal
|
||||
return new PlayResult(GameState.NORMAL, -1);
|
||||
}
|
||||
|
||||
private boolean checkWin(long board) {
|
||||
for (final long line : winningLines) {
|
||||
if ((board & line) == line) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean checkEarlyDraw() {
|
||||
final long xBitboard = getPlayerBitboard(0);
|
||||
final long oBitboard = getPlayerBitboard(1);
|
||||
|
||||
final long taken = (xBitboard | oBitboard);
|
||||
final long empty = (~taken) & 0x1FFL;
|
||||
|
||||
for (final long line : winningLines) {
|
||||
if (((line & xBitboard) != 0 && (line & oBitboard) != 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((line & empty) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitboardTicTacToe deepCopy() {
|
||||
return new BitboardTicTacToe(this);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package org.toop.game.players;
|
||||
|
||||
import org.toop.framework.gameFramework.model.player.*;
|
||||
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
||||
|
||||
/**
|
||||
* Represents a player controlled by an AI in a game.
|
||||
* <p>
|
||||
* This player uses an {@link AbstractAI} instance to determine its moves. The generic
|
||||
* parameter {@code T} specifies the type of {@link GameR} the AI can handle.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the specific type of game this AI player can play
|
||||
*/
|
||||
public class ArtificialPlayer<T extends TurnBasedGame<T>> extends AbstractPlayer<T> {
|
||||
|
||||
/** The AI instance used to calculate moves. */
|
||||
private final AI<T> ai;
|
||||
|
||||
/**
|
||||
* Constructs a new ArtificialPlayer using the specified AI.
|
||||
*
|
||||
* @param ai the AI instance that determines moves for this player
|
||||
*/
|
||||
public ArtificialPlayer(AI<T> ai, String name) {
|
||||
super(name);
|
||||
this.ai = ai;
|
||||
}
|
||||
|
||||
public ArtificialPlayer(ArtificialPlayer<T> other) {
|
||||
super(other);
|
||||
this.ai = other.ai.deepCopy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the next move for this player using its AI.
|
||||
* <p>
|
||||
* This method overrides {@link AbstractPlayer#getMove(GameR)}. Because the AI is
|
||||
* typed to {@code T}, a runtime cast is required. It is the caller's
|
||||
* responsibility to ensure that {@code gameCopy} is of type {@code T}.
|
||||
* </p>
|
||||
*
|
||||
* @param gameCopy a copy of the current game state
|
||||
* @return the integer representing the chosen move
|
||||
* @throws ClassCastException if {@code gameCopy} is not of type {@code T}
|
||||
*/
|
||||
public long getMove(T gameCopy) {
|
||||
return ai.getMove(gameCopy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArtificialPlayer<T> deepCopy() {
|
||||
return new ArtificialPlayer<T>(this);
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package org.toop.game.players;
|
||||
|
||||
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
||||
import org.toop.framework.gameFramework.model.player.AbstractPlayer;
|
||||
import org.toop.framework.gameFramework.model.player.Player;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class LocalPlayer<T extends TurnBasedGame<T>> extends AbstractPlayer<T> {
|
||||
// Future can be used with event system, IF unsubscribeAfterSuccess works...
|
||||
// private CompletableFuture<Integer> LastMove = new CompletableFuture<>();
|
||||
|
||||
private CompletableFuture<Long> LastMove;
|
||||
|
||||
public LocalPlayer(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public LocalPlayer(LocalPlayer<T> other) {
|
||||
super(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMove(T gameCopy) {
|
||||
return getValidMove(gameCopy);
|
||||
}
|
||||
|
||||
public void setMove(long move) {
|
||||
LastMove.complete(move);
|
||||
}
|
||||
|
||||
// TODO: helper function, would like to replace to get rid of this method
|
||||
public static boolean contains(int[] array, int value){
|
||||
for (int i : array) if (i == value) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private long getMove2(T gameCopy) {
|
||||
LastMove = new CompletableFuture<>();
|
||||
long move = 0;
|
||||
try {
|
||||
move = LastMove.get();
|
||||
System.out.println(Long.toBinaryString(move));
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
// TODO: Add proper logging.
|
||||
e.printStackTrace();
|
||||
}
|
||||
return move;
|
||||
}
|
||||
|
||||
protected long getValidMove(T gameCopy){
|
||||
// Get this player's valid moves
|
||||
long validMoves = gameCopy.getLegalMoves();
|
||||
// Make sure provided move is valid
|
||||
// TODO: Limit amount of retries?
|
||||
// TODO: Stop copying game so many times
|
||||
long move = getMove2(gameCopy.deepCopy());
|
||||
while ((validMoves & move) == 0) {
|
||||
System.out.println("Not a valid move, try again");
|
||||
move = getMove2(gameCopy.deepCopy());
|
||||
}
|
||||
return move;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalPlayer<T> deepCopy() {
|
||||
return new LocalPlayer<T>(this.getName());
|
||||
}
|
||||
|
||||
/*public void register() {
|
||||
// Listening to PlayerAttemptedMove
|
||||
new EventFlow().listen(GUIEvents.PlayerAttemptedMove.class, event -> {
|
||||
if (!LastMove.isDone()) {
|
||||
LastMove.complete(event.move()); // complete the future
|
||||
}
|
||||
}, true); // auto-unsubscribe
|
||||
}
|
||||
|
||||
// This blocks until the next move arrives
|
||||
public int take() throws ExecutionException, InterruptedException {
|
||||
int move = LastMove.get(); // blocking
|
||||
LastMove = new CompletableFuture<>(); // reset for next move
|
||||
return move;
|
||||
}*/
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package org.toop.game.players;
|
||||
|
||||
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
||||
import org.toop.framework.gameFramework.model.player.AbstractPlayer;
|
||||
import org.toop.framework.gameFramework.model.player.Player;
|
||||
|
||||
/**
|
||||
* Represents a player controlled remotely or over a network.
|
||||
* <p>
|
||||
* This class extends {@link AbstractPlayer} and can be used to implement game logic
|
||||
* where moves are provided by an external source (e.g., another user or a server).
|
||||
* Currently, this class is a placeholder and does not implement move logic.
|
||||
* </p>
|
||||
*/
|
||||
public class OnlinePlayer<T extends TurnBasedGame<T>> extends AbstractPlayer<T> {
|
||||
|
||||
/**
|
||||
* Constructs a new OnlinePlayer.
|
||||
* <p>
|
||||
* Currently, no additional initialization is performed. Subclasses or
|
||||
* future implementations should provide mechanisms to receive moves from
|
||||
* an external source.
|
||||
*/
|
||||
public OnlinePlayer(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public OnlinePlayer(OnlinePlayer<T> other) {
|
||||
super(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player<T> deepCopy() {
|
||||
return new OnlinePlayer<>(this);
|
||||
}
|
||||
}
|
||||
193
game/src/main/java/org/toop/game/players/ai/MCTSAI.java
Normal file
193
game/src/main/java/org/toop/game/players/ai/MCTSAI.java
Normal file
@@ -0,0 +1,193 @@
|
||||
package org.toop.game.players.ai;
|
||||
|
||||
import org.toop.framework.gameFramework.GameState;
|
||||
import org.toop.framework.gameFramework.model.game.PlayResult;
|
||||
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
||||
import org.toop.framework.gameFramework.model.player.AbstractAI;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class MCTSAI extends AbstractAI {
|
||||
private static class Node {
|
||||
public TurnBasedGame state;
|
||||
public long move;
|
||||
|
||||
public Node parent;
|
||||
|
||||
public int expanded;
|
||||
public Node[] children;
|
||||
|
||||
public int visits;
|
||||
public float value;
|
||||
|
||||
public Node(TurnBasedGame state, long move, Node parent) {
|
||||
this.state = state;
|
||||
this.move = move;
|
||||
|
||||
this.parent = parent;
|
||||
|
||||
this.expanded = 0;
|
||||
this.children = new Node[Long.bitCount(state.getLegalMoves())];
|
||||
|
||||
this.visits = 0;
|
||||
this.value = 0.0f;
|
||||
}
|
||||
|
||||
public Node(TurnBasedGame state) {
|
||||
this(state, 0L, null);
|
||||
}
|
||||
|
||||
public boolean isFullyExpanded() {
|
||||
return expanded >= children.length;
|
||||
}
|
||||
|
||||
float calculateUCT() {
|
||||
float exploitation = visits <= 0? 0 : value / visits;
|
||||
float exploration = 1.41f * (float)(Math.sqrt(Math.log(visits) / visits));
|
||||
|
||||
return exploitation + exploration;
|
||||
}
|
||||
|
||||
public Node bestUCTChild() {
|
||||
int bestChildIndex = -1;
|
||||
float bestScore = Float.NEGATIVE_INFINITY;
|
||||
|
||||
for (int i = 0; i < expanded; i++) {
|
||||
final float score = calculateUCT();
|
||||
|
||||
if (score > bestScore) {
|
||||
bestChildIndex = i;
|
||||
bestScore = score;
|
||||
}
|
||||
}
|
||||
|
||||
return bestChildIndex >= 0? children[bestChildIndex] : this;
|
||||
}
|
||||
}
|
||||
|
||||
private final int milliseconds;
|
||||
|
||||
public MCTSAI(int milliseconds) {
|
||||
this.milliseconds = milliseconds;
|
||||
}
|
||||
|
||||
public MCTSAI(MCTSAI other) {
|
||||
this.milliseconds = other.milliseconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MCTSAI deepCopy() {
|
||||
return new MCTSAI(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMove(TurnBasedGame game) {
|
||||
Node root = new Node(game.deepCopy());
|
||||
|
||||
long endTime = System.currentTimeMillis() + milliseconds;
|
||||
|
||||
while (System.currentTimeMillis() <= endTime) {
|
||||
Node node = selection(root);
|
||||
long legalMoves = node.state.getLegalMoves();
|
||||
|
||||
if (legalMoves != 0) {
|
||||
node = expansion(node, legalMoves);
|
||||
}
|
||||
|
||||
float result = 0.0f;
|
||||
|
||||
if (node.state.getLegalMoves() != 0) {
|
||||
result = simulation(node.state, game.getCurrentTurn());
|
||||
}
|
||||
|
||||
backPropagation(node, result);
|
||||
}
|
||||
|
||||
int mostVisitedIndex = -1;
|
||||
int mostVisits = -1;
|
||||
|
||||
for (int i = 0; i < root.expanded; i++) {
|
||||
if (root.children[i].visits > mostVisits) {
|
||||
mostVisitedIndex = i;
|
||||
mostVisits = root.children[i].visits;
|
||||
}
|
||||
}
|
||||
|
||||
return mostVisitedIndex != -1? root.children[mostVisitedIndex].move : randomSetBit(game.getLegalMoves());
|
||||
}
|
||||
|
||||
private Node selection(Node node) {
|
||||
while (node.state.getLegalMoves() != 0L && node.isFullyExpanded()) {
|
||||
node = node.bestUCTChild();
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private Node expansion(Node node, long legalMoves) {
|
||||
for (int i = 0; i < node.expanded; i++) {
|
||||
legalMoves &= ~node.children[i].move;
|
||||
}
|
||||
|
||||
if (legalMoves == 0L) {
|
||||
return node;
|
||||
}
|
||||
|
||||
long move = randomSetBit(legalMoves);
|
||||
|
||||
TurnBasedGame copy = node.state.deepCopy();
|
||||
copy.play(move);
|
||||
|
||||
Node newlyExpanded = new Node(copy, move, node);
|
||||
|
||||
node.children[node.expanded] = newlyExpanded;
|
||||
node.expanded++;
|
||||
|
||||
return newlyExpanded;
|
||||
}
|
||||
|
||||
private float simulation(TurnBasedGame state, int playerIndex) {
|
||||
TurnBasedGame copy = state.deepCopy();
|
||||
long legalMoves = copy.getLegalMoves();
|
||||
PlayResult result = null;
|
||||
|
||||
while (legalMoves != 0) {
|
||||
result = copy.play(randomSetBit(legalMoves));
|
||||
legalMoves = copy.getLegalMoves();
|
||||
}
|
||||
|
||||
if (result.state() == GameState.WIN) {
|
||||
if (result.player() == playerIndex) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
return -0.2f;
|
||||
}
|
||||
|
||||
private void backPropagation(Node node, float value) {
|
||||
while (node != null) {
|
||||
node.visits++;
|
||||
node.value += value;
|
||||
node = node.parent;
|
||||
}
|
||||
}
|
||||
|
||||
public static long randomSetBit(long value) {
|
||||
Random random = new Random();
|
||||
|
||||
int count = Long.bitCount(value);
|
||||
int target = random.nextInt(count);
|
||||
|
||||
while (true) {
|
||||
int bit = Long.numberOfTrailingZeros(value);
|
||||
if (target == 0) {
|
||||
return 1L << bit;
|
||||
}
|
||||
value &= value - 1;
|
||||
target--;
|
||||
}
|
||||
}
|
||||
}
|
||||
195
game/src/main/java/org/toop/game/players/ai/MCTSAI2.java
Normal file
195
game/src/main/java/org/toop/game/players/ai/MCTSAI2.java
Normal file
@@ -0,0 +1,195 @@
|
||||
package org.toop.game.players.ai;
|
||||
|
||||
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
||||
import org.toop.framework.gameFramework.model.player.AbstractAI;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class MCTSAI2 extends AbstractAI {
|
||||
private static class Node {
|
||||
public TurnBasedGame state;
|
||||
|
||||
public long move;
|
||||
public long unexpandedMoves;
|
||||
|
||||
public Node parent;
|
||||
|
||||
public Node[] children;
|
||||
public int expanded;
|
||||
|
||||
public float value;
|
||||
public int visits;
|
||||
|
||||
public Node(TurnBasedGame state, Node parent, long move) {
|
||||
final long legalMoves = state.getLegalMoves();
|
||||
|
||||
this.state = state;
|
||||
|
||||
this.move = move;
|
||||
this.unexpandedMoves = legalMoves;
|
||||
|
||||
this.parent = parent;
|
||||
|
||||
this.children = new Node[Long.bitCount(legalMoves)];
|
||||
this.expanded = 0;
|
||||
|
||||
this.value = 0.0f;
|
||||
this.visits = 0;
|
||||
}
|
||||
|
||||
public Node(TurnBasedGame state) {
|
||||
this(state, null, 0L);
|
||||
}
|
||||
|
||||
public boolean isFullyExpanded() {
|
||||
return expanded == children.length;
|
||||
}
|
||||
|
||||
public float calculateUCT(int parentVisits) {
|
||||
final float exploitation = value / visits;
|
||||
final float exploration = 1.41f * (float)(Math.sqrt(Math.log(parentVisits) / visits));
|
||||
|
||||
return exploitation + exploration;
|
||||
}
|
||||
|
||||
public Node bestUCTChild() {
|
||||
Node highestUCTChild = null;
|
||||
float highestUCT = Float.NEGATIVE_INFINITY;
|
||||
|
||||
for (int i = 0; i < expanded; i++) {
|
||||
final float childUCT = children[i].calculateUCT(visits);
|
||||
|
||||
if (childUCT > highestUCT) {
|
||||
highestUCTChild = children[i];
|
||||
highestUCT = childUCT;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return highestUCTChild;
|
||||
}
|
||||
}
|
||||
|
||||
private final Random random;
|
||||
private final int milliseconds;
|
||||
|
||||
public MCTSAI2(int milliseconds) {
|
||||
this.random = new Random();
|
||||
this.milliseconds = milliseconds;
|
||||
}
|
||||
|
||||
public MCTSAI2(MCTSAI2 other) {
|
||||
this.random = other.random;
|
||||
this.milliseconds = other.milliseconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MCTSAI2 deepCopy() {
|
||||
return new MCTSAI2(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMove(TurnBasedGame game) {
|
||||
final Node root = new Node(game, null, 0L);
|
||||
|
||||
final long endTime = System.nanoTime() + milliseconds * 1_000_000L;
|
||||
|
||||
while (System.nanoTime() < endTime) {
|
||||
Node leaf = selection(root);
|
||||
leaf = expansion(leaf);
|
||||
final float value = simulation(leaf);
|
||||
backPropagation(leaf, value);
|
||||
}
|
||||
|
||||
final Node mostVisitedChild = mostVisitedChild(root);
|
||||
|
||||
return mostVisitedChild != null? mostVisitedChild.move : 0L;
|
||||
}
|
||||
|
||||
private Node mostVisitedChild(Node root) {
|
||||
Node mostVisitedChild = null;
|
||||
int mostVisited = -1;
|
||||
|
||||
for (int i = 0; i < root.expanded; i++) {
|
||||
if (root.children[i].visits > mostVisited) {
|
||||
mostVisitedChild = root.children[i];
|
||||
mostVisited = root.children[i].visits;
|
||||
}
|
||||
}
|
||||
|
||||
return mostVisitedChild;
|
||||
}
|
||||
|
||||
private Node selection(Node root) {
|
||||
while (root.isFullyExpanded() && !root.state.isTerminal()) {
|
||||
root = root.bestUCTChild();
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private Node expansion(Node leaf) {
|
||||
if (leaf.unexpandedMoves == 0L) {
|
||||
return leaf;
|
||||
}
|
||||
|
||||
final long unexpandedMove = leaf.unexpandedMoves & -leaf.unexpandedMoves;
|
||||
|
||||
final TurnBasedGame copiedState = leaf.state.deepCopy();
|
||||
copiedState.play(unexpandedMove);
|
||||
|
||||
final Node expandedChild = new Node(copiedState, leaf, unexpandedMove);
|
||||
|
||||
leaf.children[leaf.expanded] = expandedChild;
|
||||
leaf.expanded++;
|
||||
|
||||
leaf.unexpandedMoves &= ~unexpandedMove;
|
||||
|
||||
return expandedChild;
|
||||
}
|
||||
|
||||
private float simulation(Node leaf) {
|
||||
final TurnBasedGame copiedState = leaf.state.deepCopy();
|
||||
final int playerIndex = 1 - copiedState.getCurrentTurn();
|
||||
|
||||
while (!copiedState.isTerminal()) {
|
||||
final long legalMoves = copiedState.getLegalMoves();
|
||||
final long randomMove = randomSetBit(legalMoves);
|
||||
|
||||
copiedState.play(randomMove);
|
||||
}
|
||||
|
||||
if (copiedState.getWinner() == playerIndex) {
|
||||
return 1.0f;
|
||||
} else if (copiedState.getWinner() >= 0) {
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
private void backPropagation(Node leaf, float value) {
|
||||
while (leaf != null) {
|
||||
leaf.value += value;
|
||||
leaf.visits++;
|
||||
|
||||
value = -value;
|
||||
leaf = leaf.parent;
|
||||
}
|
||||
}
|
||||
|
||||
private long randomSetBit(long value) {
|
||||
if (0L == value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final int bitCount = Long.bitCount(value);
|
||||
final int randomBitCount = random.nextInt(bitCount);
|
||||
|
||||
for (int i = 0; i < randomBitCount; i++) {
|
||||
value &= value - 1;
|
||||
}
|
||||
|
||||
return value & -value;
|
||||
}
|
||||
}
|
||||
258
game/src/main/java/org/toop/game/players/ai/MCTSAI3.java
Normal file
258
game/src/main/java/org/toop/game/players/ai/MCTSAI3.java
Normal file
@@ -0,0 +1,258 @@
|
||||
package org.toop.game.players.ai;
|
||||
|
||||
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
||||
import org.toop.framework.gameFramework.model.player.AbstractAI;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class MCTSAI3 extends AbstractAI {
|
||||
private static class Node {
|
||||
public TurnBasedGame state;
|
||||
|
||||
public long move;
|
||||
public long unexpandedMoves;
|
||||
|
||||
public Node parent;
|
||||
|
||||
public Node[] children;
|
||||
public int expanded;
|
||||
|
||||
public float value;
|
||||
public int visits;
|
||||
|
||||
public Node(TurnBasedGame state, Node parent, long move) {
|
||||
final long legalMoves = state.getLegalMoves();
|
||||
|
||||
this.state = state;
|
||||
|
||||
this.move = move;
|
||||
this.unexpandedMoves = legalMoves;
|
||||
|
||||
this.parent = parent;
|
||||
|
||||
this.children = new Node[Long.bitCount(legalMoves)];
|
||||
this.expanded = 0;
|
||||
|
||||
this.value = 0.0f;
|
||||
this.visits = 0;
|
||||
}
|
||||
|
||||
public Node(TurnBasedGame state) {
|
||||
this(state, null, 0L);
|
||||
}
|
||||
|
||||
public boolean isFullyExpanded() {
|
||||
return expanded == children.length;
|
||||
}
|
||||
|
||||
public float calculateUCT(int parentVisits) {
|
||||
final float exploitation = value / visits;
|
||||
final float exploration = 1.41f * (float)(Math.sqrt(Math.log(parentVisits) / visits));
|
||||
|
||||
return exploitation + exploration;
|
||||
}
|
||||
|
||||
public Node bestUCTChild() {
|
||||
Node highestUCTChild = null;
|
||||
float highestUCT = Float.NEGATIVE_INFINITY;
|
||||
|
||||
for (int i = 0; i < expanded; i++) {
|
||||
final float childUCT = children[i].calculateUCT(visits);
|
||||
|
||||
if (childUCT > highestUCT) {
|
||||
highestUCTChild = children[i];
|
||||
highestUCT = childUCT;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return highestUCTChild;
|
||||
}
|
||||
}
|
||||
|
||||
private final Random random;
|
||||
|
||||
private Node root;
|
||||
private final int milliseconds;
|
||||
|
||||
public MCTSAI3(int milliseconds) {
|
||||
this.random = new Random();
|
||||
|
||||
this.root = null;
|
||||
this.milliseconds = milliseconds;
|
||||
}
|
||||
|
||||
public MCTSAI3(MCTSAI3 other) {
|
||||
this.random = other.random;
|
||||
|
||||
this.root = other.root;
|
||||
this.milliseconds = other.milliseconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MCTSAI3 deepCopy() {
|
||||
return new MCTSAI3(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMove(TurnBasedGame game) {
|
||||
detectRoot(game);
|
||||
|
||||
final long endTime = System.nanoTime() + milliseconds * 1_000_000L;
|
||||
|
||||
while (System.nanoTime() < endTime) {
|
||||
Node leaf = selection(root);
|
||||
leaf = expansion(leaf);
|
||||
final float value = simulation(leaf);
|
||||
backPropagation(leaf, value);
|
||||
}
|
||||
|
||||
final Node mostVisitedChild = mostVisitedChild(root);
|
||||
final long move = mostVisitedChild != null? mostVisitedChild.move : 0L;
|
||||
|
||||
newRoot(move);
|
||||
|
||||
return move;
|
||||
}
|
||||
|
||||
private Node mostVisitedChild(Node root) {
|
||||
Node mostVisitedChild = null;
|
||||
int mostVisited = -1;
|
||||
|
||||
for (int i = 0; i < root.expanded; i++) {
|
||||
if (root.children[i].visits > mostVisited) {
|
||||
mostVisitedChild = root.children[i];
|
||||
mostVisited = root.children[i].visits;
|
||||
}
|
||||
}
|
||||
|
||||
return mostVisitedChild;
|
||||
}
|
||||
|
||||
private void detectRoot(TurnBasedGame game) {
|
||||
if (root == null) {
|
||||
root = new Node(game.deepCopy());
|
||||
return;
|
||||
}
|
||||
|
||||
final long[] currentBoards = game.getBoard();
|
||||
final long[] rootBoards = root.state.getBoard();
|
||||
|
||||
boolean detected = true;
|
||||
|
||||
for (int i = 0; i < rootBoards.length; i++) {
|
||||
if (rootBoards[i] != currentBoards[i]) {
|
||||
detected = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (detected) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < root.expanded; i++) {
|
||||
final Node child = root.children[i];
|
||||
|
||||
final long[] childBoards = child.state.getBoard();
|
||||
|
||||
detected = true;
|
||||
|
||||
for (int j = 0; j < childBoards.length; j++) {
|
||||
if (childBoards[j] != currentBoards[j]) {
|
||||
detected = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (detected) {
|
||||
root = child;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
root = new Node(game.deepCopy());
|
||||
}
|
||||
|
||||
private void newRoot(long move) {
|
||||
for (final Node child : root.children) {
|
||||
if (child.move == move) {
|
||||
root = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Node selection(Node root) {
|
||||
while (root.isFullyExpanded() && !root.state.isTerminal()) {
|
||||
root = root.bestUCTChild();
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private Node expansion(Node leaf) {
|
||||
if (leaf.unexpandedMoves == 0L) {
|
||||
return leaf;
|
||||
}
|
||||
|
||||
final long unexpandedMove = leaf.unexpandedMoves & -leaf.unexpandedMoves;
|
||||
|
||||
final TurnBasedGame copiedState = leaf.state.deepCopy();
|
||||
copiedState.play(unexpandedMove);
|
||||
|
||||
final Node expandedChild = new Node(copiedState, leaf, unexpandedMove);
|
||||
|
||||
leaf.children[leaf.expanded] = expandedChild;
|
||||
leaf.expanded++;
|
||||
|
||||
leaf.unexpandedMoves &= ~unexpandedMove;
|
||||
|
||||
return expandedChild;
|
||||
}
|
||||
|
||||
private float simulation(Node leaf) {
|
||||
final TurnBasedGame copiedState = leaf.state.deepCopy();
|
||||
final int playerIndex = 1 - copiedState.getCurrentTurn();
|
||||
|
||||
while (!copiedState.isTerminal()) {
|
||||
final long legalMoves = copiedState.getLegalMoves();
|
||||
final long randomMove = randomSetBit(legalMoves);
|
||||
|
||||
copiedState.play(randomMove);
|
||||
}
|
||||
|
||||
if (copiedState.getWinner() == playerIndex) {
|
||||
return 1.0f;
|
||||
} else if (copiedState.getWinner() >= 0) {
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
private void backPropagation(Node leaf, float value) {
|
||||
while (leaf != null) {
|
||||
leaf.value += value;
|
||||
leaf.visits++;
|
||||
|
||||
value = -value;
|
||||
leaf = leaf.parent;
|
||||
}
|
||||
}
|
||||
|
||||
private long randomSetBit(long value) {
|
||||
if (0L == value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final int bitCount = Long.bitCount(value);
|
||||
final int randomBitCount = random.nextInt(bitCount);
|
||||
|
||||
for (int i = 0; i < randomBitCount; i++) {
|
||||
value &= value - 1;
|
||||
}
|
||||
|
||||
return value & -value;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.toop.game.players;
|
||||
package org.toop.game.players.ai;
|
||||
|
||||
import org.toop.framework.gameFramework.GameState;
|
||||
import org.toop.framework.gameFramework.model.game.PlayResult;
|
||||
@@ -9,7 +9,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class MiniMaxAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
|
||||
public class MiniMaxAI extends AbstractAI {
|
||||
|
||||
private final int maxDepth;
|
||||
private final Random random = new Random();
|
||||
@@ -18,17 +18,17 @@ public class MiniMaxAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
|
||||
this.maxDepth = depth;
|
||||
}
|
||||
|
||||
public MiniMaxAI(MiniMaxAI<T> other) {
|
||||
public MiniMaxAI(MiniMaxAI other) {
|
||||
this.maxDepth = other.maxDepth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MiniMaxAI<T> deepCopy() {
|
||||
return new MiniMaxAI<>(this);
|
||||
public MiniMaxAI deepCopy() {
|
||||
return new MiniMaxAI(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMove(T game) {
|
||||
public long getMove(TurnBasedGame game) {
|
||||
long legalMoves = game.getLegalMoves();
|
||||
if (legalMoves == 0) return 0;
|
||||
|
||||
@@ -39,7 +39,7 @@ public class MiniMaxAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
|
||||
long movesLoop = legalMoves;
|
||||
while (movesLoop != 0) {
|
||||
long move = 1L << Long.numberOfTrailingZeros(movesLoop);
|
||||
T copy = game.deepCopy();
|
||||
TurnBasedGame copy = game.deepCopy();
|
||||
PlayResult result = copy.play(move);
|
||||
|
||||
int score;
|
||||
@@ -75,7 +75,7 @@ public class MiniMaxAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
|
||||
* @param beta Beta value
|
||||
* @return score of the position
|
||||
*/
|
||||
private int getMoveScore(T game, int depth, boolean maximizing, int aiPlayer, int alpha, int beta) {
|
||||
private int getMoveScore(TurnBasedGame game, int depth, boolean maximizing, int aiPlayer, int alpha, int beta) {
|
||||
long legalMoves = game.getLegalMoves();
|
||||
|
||||
// Terminal state
|
||||
@@ -95,7 +95,7 @@ public class MiniMaxAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
|
||||
|
||||
while (movesLoop != 0) {
|
||||
long move = 1L << Long.numberOfTrailingZeros(movesLoop);
|
||||
T copy = game.deepCopy();
|
||||
TurnBasedGame copy = game.deepCopy();
|
||||
PlayResult result = copy.play(move);
|
||||
|
||||
int score;
|
||||
@@ -126,11 +126,11 @@ public class MiniMaxAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
|
||||
* Simple heuristic evaluation for Reversi-like games.
|
||||
* Positive = good for AI, Negative = good for opponent.
|
||||
*
|
||||
* @param game Game state
|
||||
* @param game OnlineTurnBasedGame state
|
||||
* @param aiPlayer AI's player index
|
||||
* @return heuristic score
|
||||
*/
|
||||
private int evaluateBoard(T game, int aiPlayer) {
|
||||
private int evaluateBoard(TurnBasedGame game, int aiPlayer) {
|
||||
long[] board = game.getBoard();
|
||||
int aiCount = 0;
|
||||
int opponentCount = 0;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.toop.game.players;
|
||||
package org.toop.game.players.ai;
|
||||
|
||||
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
|
||||
import org.toop.framework.gameFramework.model.player.AbstractAI;
|
||||
@@ -6,19 +6,19 @@ import org.toop.framework.gameFramework.model.player.AbstractAI;
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
public class RandomAI<T extends TurnBasedGame<T>> extends AbstractAI<T> {
|
||||
public class RandomAI extends AbstractAI {
|
||||
|
||||
public RandomAI() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomAI<T> deepCopy() {
|
||||
return new RandomAI<T>();
|
||||
public RandomAI deepCopy() {
|
||||
return new RandomAI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMove(T game) {
|
||||
public long getMove(TurnBasedGame game) {
|
||||
long legalMoves = game.getLegalMoves();
|
||||
int move = new Random().nextInt(Long.bitCount(legalMoves));
|
||||
return nthBitIndex(legalMoves, move);
|
||||
Reference in New Issue
Block a user