28 Commits

Author SHA1 Message Date
lieght
ea32598d4c AI Thread fixes 2026-01-25 11:12:57 +01:00
lieght
40e1c3b124 Merge remote-tracking branch 'origin/main' into Development
# Conflicts:
#	app/src/main/java/org/toop/Main.java
#	app/src/main/java/org/toop/app/Server.java
#	app/src/main/java/org/toop/app/canvas/ReversiBitCanvas.java
#	app/src/main/java/org/toop/app/gameControllers/ReversiBitController.java
#	app/src/main/java/org/toop/app/widget/view/LocalMultiplayerView.java
#	app/src/main/java/org/toop/app/widget/view/ServerView.java
#	framework/src/main/java/org/toop/framework/game/BitboardGame.java
#	framework/src/main/java/org/toop/framework/game/gameThreads/OnlineThreadBehaviour.java
#	framework/src/main/java/org/toop/framework/game/gameThreads/ServerThreadBehaviour.java
#	framework/src/main/java/org/toop/framework/game/games/reversi/BitboardReversi.java
#	framework/src/main/java/org/toop/framework/game/players/ArtificialPlayer.java
#	framework/src/main/java/org/toop/framework/game/players/ServerPlayer.java
#	framework/src/main/java/org/toop/framework/gameFramework/model/game/TurnBasedGame.java
#	framework/src/main/java/org/toop/framework/networking/server/GameServer.java
#	framework/src/main/java/org/toop/framework/networking/server/OnlineTurnBasedGame.java
#	framework/src/main/java/org/toop/framework/networking/server/Server.java
#	framework/src/main/java/org/toop/framework/networking/server/client/NettyClient.java
#	framework/src/main/java/org/toop/framework/networking/server/handlers/MessageHandler.java
#	game/src/main/java/org/toop/game/players/ai/MCTSAI.java
2026-01-25 11:11:44 +01:00
lieght
22a73fc50a Moved back in threads 2026-01-23 19:16:29 +01:00
lieght
af9a316639 Merge remote-tracking branch 'origin/Development' into Development 2026-01-23 19:15:55 +01:00
lieght
8508377cb4 Parameters added to tests 2026-01-23 19:15:29 +01:00
ramollia
97276c7e80 readded threads argument 2026-01-23 19:14:46 +01:00
ramollia
11eda3c8b5 Merge remote-tracking branch 'origin/Development' into Development
# Conflicts:
#	game/src/main/java/org/toop/game/players/ai/mcts/MCTSAI3.java
#	game/src/main/java/org/toop/game/players/ai/mcts/MCTSAI4.java
2026-01-23 19:09:51 +01:00
ramollia
039c0393c8 fixed extra wait time for threads 2026-01-23 19:09:28 +01:00
lieght
319eaa33b1 name fixes 2026-01-22 13:48:15 +01:00
lieght
940b26de41 Fixes 2026-01-22 13:47:42 +01:00
lieght
2f161bcc0a Small thread count fix 2026-01-22 13:38:19 +01:00
lieght
ccdca4f0ea Added thread test 2026-01-22 12:30:42 +01:00
lieght
b5bd4adf91 AI wait fixes 2026-01-22 11:10:55 +01:00
lieght
f37c578a35 name fixes 2026-01-22 11:04:08 +01:00
lieght
eb1784550f Data collection fixes 2026-01-22 10:56:55 +01:00
lieght
e8f66a62f0 AI data now correct 2026-01-22 10:44:54 +01:00
lieght
4926bd161e m4 nu 8 threads 2026-01-22 10:29:22 +01:00
lieght
b39659d02d Back to 10ms 2026-01-22 00:00:57 +01:00
lieght
c107e8c0d1 Correct time data visualization effect on timetable lookup for data collection purposes 2026-01-21 23:52:29 +01:00
lieght
8a94aad622 Infinite game collection 2026-01-21 23:46:59 +01:00
lieght
992523b936 Better data collection for overnight run 2026-01-21 23:43:11 +01:00
ramollia
5d2fff7ae7 changed the way multithreading worked 2026-01-21 20:05:19 +01:00
ramollia
f168b974ab Merge remote-tracking branch 'origin/Development' into Development 2026-01-21 15:42:08 +01:00
ramollia
057487e4f9 readded the exploration constant 2026-01-21 15:40:38 +01:00
michiel
fb32bc6f8e saving games data to games.csv 2026-01-20 13:41:10 +01:00
ramollia
4c8bd89a35 fixed things 2026-01-20 13:18:29 +01:00
ramollia
f7b24edf1e implement solved 2026-01-20 13:15:48 +01:00
Bas Antonius de Jong
429e5bc90b 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 commit d24feef73e.

* Revert "Merge remote-tracking branch 'origin/Development' into Development"

This reverts commit 59d46cb73c, reversing
changes made to 38681c5db0.

* Revert "merge widgets with development"

This reverts commit 38681c5db0.

* 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 commit d24feef73e.

* Revert "Merge remote-tracking branch 'origin/Development' into Development"

This reverts commit 59d46cb73c, reversing
changes made to 38681c5db0.

* Revert "merge widgets with development"

This reverts commit 38681c5db0.

* 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 commit 9134d7e343.

* 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 commit d24feef73e.

* Revert "Merge remote-tracking branch 'origin/Development' into Development"

This reverts commit 59d46cb73c, reversing
changes made to 38681c5db0.

* Revert "merge widgets with development"

This reverts commit 38681c5db0.

* 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 commit e2132b549d, reversing
changes made to 9aefcb9b7b.

---------

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>
2026-01-09 19:30:49 +01:00
16 changed files with 796 additions and 179 deletions

View File

@@ -1,23 +1,9 @@
package org.toop; package org.toop;
import org.toop.app.App; import org.toop.app.App;
import org.toop.framework.game.games.reversi.BitboardReversi;
import org.toop.framework.game.players.ArtificialPlayer;
import org.toop.game.players.ai.MCTSAI;
import org.toop.game.players.ai.RandomAI;
import org.toop.game.players.ai.mcts.MCTSAI1;
import org.toop.game.players.ai.mcts.MCTSAI2;
import org.toop.game.players.ai.mcts.MCTSAI3;
import org.toop.game.players.ai.mcts.MCTSAI4;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public final class Main { public final class Main {
static void main(String[] args) { static void main(String[] args) {
App.run(args); App.run(args);
// final ExecutorService executor = Executors.newFixedThreadPool(1);
// executor.execute(() -> testAIs(25));
} }
} }

View File

@@ -210,7 +210,7 @@ public final class Server {
Player[] players = new Player[2]; Player[] players = new Player[2];
players[userStartingTurn] = new ArtificialPlayer(new MCTSAI3(1000, Runtime.getRuntime().availableProcessors()), user); players[userStartingTurn] = new ArtificialPlayer(new MCTSAI3(1000, 8), user);
players[opponentStartingTurn] = new OnlinePlayer(response.opponent()); players[opponentStartingTurn] = new OnlinePlayer(response.opponent());
switch (type) { switch (type) {

View File

@@ -82,13 +82,13 @@ public class LocalMultiplayerView extends ViewWidget {
if (information.players[0].isHuman) { if (information.players[0].isHuman) {
players[0] = new LocalPlayer(information.players[0].name); players[0] = new LocalPlayer(information.players[0].name);
} else { } else {
// players[0] = new ArtificialPlayer(new RandomAI<BitboardReversi>(), "Random AI"); // players[0] = new ArtificialPlayer(new RandomAI(), "Random AI");
players[0] = new ArtificialPlayer(new MCTSAI4(500, 4), "MCTS V4 AI"); players[0] = new ArtificialPlayer(new MCTSAI1(100), "MCTS V1 AI");
} }
if (information.players[1].isHuman) { if (information.players[1].isHuman) {
players[1] = new LocalPlayer(information.players[1].name); players[1] = new LocalPlayer(information.players[1].name);
} else { } else {
players[1] = new ArtificialPlayer(new MCTSAI1(500), "MCTS V1 AI"); players[1] = new ArtificialPlayer(new MCTSAI4(100, 8), "MCTS V4 AI");
} }
if (AppSettings.getSettings().getTutorialFlag() && AppSettings.getSettings().getFirstReversi()) { if (AppSettings.getSettings().getTutorialFlag() && AppSettings.getSettings().getFirstReversi()) {
new ShowEnableTutorialWidget( new ShowEnableTutorialWidget(

View File

@@ -6,7 +6,6 @@ import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.gameFramework.model.player.Player; import org.toop.framework.gameFramework.model.player.Player;
import java.util.Arrays; 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 // There is AI performance to be gained by getting rid of non-primitives and thus speeding up deepCopy
public abstract class BitboardGame implements TurnBasedGame { public abstract class BitboardGame implements TurnBasedGame {
@@ -18,8 +17,8 @@ public abstract class BitboardGame implements TurnBasedGame {
private Player[] players; private Player[] players;
// long is 64 bits. Every game has a limit of 64 cells maximum. // long is 64 bits. Every game has a limit of 64 cells maximum.
private final long[] playerBitboard; protected final long[] playerBitboard;
private int currentTurn = 0; protected int currentTurn = 0;
private final int playerCount; private final int playerCount;
public BitboardGame(int columnSize, int rowSize, int playerCount) { public BitboardGame(int columnSize, int rowSize, int playerCount) {
@@ -75,6 +74,8 @@ public abstract class BitboardGame implements TurnBasedGame {
return playerBitboard.length; return playerBitboard.length;
} }
public int getAmountOfTurns() { return currentTurn; }
public int getCurrentTurn() { public int getCurrentTurn() {
return getCurrentPlayerIndex(); return getCurrentPlayerIndex();
} }

View File

@@ -4,7 +4,6 @@ import org.toop.framework.game.BitboardGame;
import org.toop.framework.gameFramework.GameState; import org.toop.framework.gameFramework.GameState;
import org.toop.framework.gameFramework.model.game.PlayResult; import org.toop.framework.gameFramework.model.game.PlayResult;
import org.toop.framework.gameFramework.model.player.Player; import org.toop.framework.gameFramework.model.player.Player;
import org.toop.framework.game.BitboardGame;
public class BitboardReversi extends BitboardGame { public class BitboardReversi extends BitboardGame {
@@ -368,4 +367,11 @@ public class BitboardReversi extends BitboardGame {
return bestMove; return bestMove;
} }
@Override
public void setFrom(long player1, long player2, int turn) {
this.playerBitboard[0] = player1;
this.playerBitboard[1] = player2;
this.currentTurn = turn;
}
} }

View File

@@ -2,6 +2,7 @@ package org.toop.framework.game.games.tictactoe;
import org.toop.framework.gameFramework.GameState; import org.toop.framework.gameFramework.GameState;
import org.toop.framework.gameFramework.model.game.PlayResult; import org.toop.framework.gameFramework.model.game.PlayResult;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.gameFramework.model.player.Player; import org.toop.framework.gameFramework.model.player.Player;
import org.toop.framework.game.BitboardGame; import org.toop.framework.game.BitboardGame;
@@ -120,4 +121,8 @@ public class BitboardTicTacToe extends BitboardGame {
public long heuristicMove(long legalMoves) { public long heuristicMove(long legalMoves) {
return legalMoves; return legalMoves;
} }
@Override
public void setFrom(long player1, long player2, int turn) {
}
} }

View File

@@ -16,4 +16,6 @@ public interface TurnBasedGame extends DeepCopyable<TurnBasedGame> {
float rateMove(long move); float rateMove(long move);
long heuristicMove(long legalMoves); long heuristicMove(long legalMoves);
void setFrom(long player1, long player2, int turn);
} }

View File

@@ -4,9 +4,12 @@ import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.gameFramework.model.player.AbstractAI; import org.toop.framework.gameFramework.model.player.AbstractAI;
import java.util.Random; import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class MCTSAI extends AbstractAI { public abstract class MCTSAI extends AbstractAI {
protected static class Node { protected static class Node {
public static final int VIRTUAL_LOSS = -1;
public TurnBasedGame state; public TurnBasedGame state;
public long move; public long move;
@@ -15,8 +18,8 @@ public abstract class MCTSAI extends AbstractAI {
public Node parent; public Node parent;
public Node[] children; public Node[] children;
public float value; public AtomicInteger value;
public int visits; public AtomicInteger visits;
public float heuristic; public float heuristic;
@@ -33,8 +36,8 @@ public abstract class MCTSAI extends AbstractAI {
this.parent = parent; this.parent = parent;
this.children = new Node[Long.bitCount(legalMoves)]; this.children = new Node[Long.bitCount(legalMoves)];
this.value = 0.0f; this.value = new AtomicInteger(0);
this.visits = 0; this.visits = new AtomicInteger(0);
this.heuristic = state.rateMove(move); this.heuristic = state.rateMove(move);
@@ -53,14 +56,14 @@ public abstract class MCTSAI extends AbstractAI {
return unexpandedMoves == 0L; return unexpandedMoves == 0L;
} }
public float calculateUCT(int parentVisits) { public float calculateUCT(float explorationFactor) {
if (visits == 0) { if (visits.get() == 0) {
return Float.POSITIVE_INFINITY; return Float.POSITIVE_INFINITY;
} }
final float exploitation = value / visits; final float exploitation = (float) value.get() / visits.get();
final float exploration = (float)(Math.sqrt(Math.log(parentVisits) / visits)); final float exploration = (float)(Math.sqrt(explorationFactor / visits.get()));
final float bias = heuristic * 10.0f / (visits + 1); final float bias = heuristic * 10.0f / (visits.get() + 1);
return exploitation + exploration + bias; return exploitation + exploration + bias;
} }
@@ -72,7 +75,7 @@ public abstract class MCTSAI extends AbstractAI {
float highestUCT = Float.NEGATIVE_INFINITY; float highestUCT = Float.NEGATIVE_INFINITY;
for (int i = 0; i < expanded; i++) { for (int i = 0; i < expanded; i++) {
final float childUCT = children[i].calculateUCT(visits); final float childUCT = children[i].calculateUCT(2.0f * (float)Math.log(visits.get()));
if (childUCT > highestUCT) { if (childUCT > highestUCT) {
highestUCTChild = children[i]; highestUCTChild = children[i];
@@ -107,15 +110,21 @@ public abstract class MCTSAI extends AbstractAI {
} }
protected Node selection(Node root) { protected Node selection(Node root) {
// while (Float.isNaN(root.solved) && root.isFullyExpanded() && !root.state.isTerminal()) { while (Float.isNaN(root.solved) && root.isFullyExpanded() && !root.state.isTerminal()) {
while (root.isFullyExpanded() && !root.state.isTerminal()) { root.value.addAndGet(Node.VIRTUAL_LOSS);
root.visits.incrementAndGet();
root = root.bestUCTChild(); root = root.bestUCTChild();
} }
root.value.addAndGet(Node.VIRTUAL_LOSS);
root.visits.incrementAndGet();
return root; return root;
} }
protected Node expansion(Node leaf) { protected Node expansion(Node leaf) {
synchronized (leaf) {
if (leaf.unexpandedMoves == 0L) { if (leaf.unexpandedMoves == 0L) {
return leaf; return leaf;
} }
@@ -132,8 +141,9 @@ public abstract class MCTSAI extends AbstractAI {
return expandedChild; return expandedChild;
} }
}
protected float simulation(Node leaf) { protected int simulation(Node leaf) {
final TurnBasedGame copiedState = leaf.state.deepCopy(); final TurnBasedGame copiedState = leaf.state.deepCopy();
final int playerIndex = 1 - copiedState.getCurrentTurn(); final int playerIndex = 1 - copiedState.getCurrentTurn();
@@ -145,20 +155,20 @@ public abstract class MCTSAI extends AbstractAI {
} }
if (copiedState.getWinner() == playerIndex) { if (copiedState.getWinner() == playerIndex) {
return 1.0f; return 1;
} }
if (copiedState.getWinner() >= 0) { if (copiedState.getWinner() >= 0) {
return -1.0f; return -1;
} }
return 0.0f; return 0;
} }
protected void backPropagation(Node leaf, float value) { protected void backPropagation(Node leaf, int value) {
while (leaf != null) { while (leaf != null) {
leaf.value += value; value -= Node.VIRTUAL_LOSS;
leaf.visits++; leaf.value.addAndGet(value);
if (Float.isNaN(leaf.solved)) { if (Float.isNaN(leaf.solved)) {
updateSolvedStatus(leaf); updateSolvedStatus(leaf);
@@ -176,9 +186,9 @@ public abstract class MCTSAI extends AbstractAI {
int mostVisited = -1; int mostVisited = -1;
for (int i = 0; i < expanded; i++) { for (int i = 0; i < expanded; i++) {
if (root.children[i].visits > mostVisited) { if (root.children[i].visits.get() > mostVisited) {
mostVisitedChild = root.children[i]; mostVisitedChild = root.children[i];
mostVisited = root.children[i].visits; mostVisited = root.children[i].visits.get();
} }
} }

View 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;
}
}

View 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;
}
}

View File

@@ -23,15 +23,14 @@ public class MCTSAI1 extends MCTSAI {
final long endTime = System.nanoTime() + milliseconds * 1_000_000L; final long endTime = System.nanoTime() + milliseconds * 1_000_000L;
// while (Float.isNaN(root.solved) && System.nanoTime() < endTime) { while (Float.isNaN(root.solved) && System.nanoTime() < endTime) {
while (System.nanoTime() < endTime) {
Node leaf = selection(root); Node leaf = selection(root);
leaf = expansion(leaf); leaf = expansion(leaf);
final float value = simulation(leaf); final int value = simulation(leaf);
backPropagation(leaf, value); backPropagation(leaf, value);
} }
lastIterations = root.visits; lastIterations = root.visits.get();
final Node mostVisitedChild = mostVisitedChild(root); final Node mostVisitedChild = mostVisitedChild(root);
return mostVisitedChild.move; return mostVisitedChild.move;

View File

@@ -29,15 +29,14 @@ public class MCTSAI2 extends MCTSAI {
final long endTime = System.nanoTime() + milliseconds * 1_000_000L; final long endTime = System.nanoTime() + milliseconds * 1_000_000L;
// while (Float.isNaN(root.solved) && System.nanoTime() < endTime) { while (Float.isNaN(root.solved) && System.nanoTime() < endTime) {
while (System.nanoTime() < endTime) {
Node leaf = selection(root); Node leaf = selection(root);
leaf = expansion(leaf); leaf = expansion(leaf);
final float value = simulation(leaf); final int value = simulation(leaf);
backPropagation(leaf, value); backPropagation(leaf, value);
} }
lastIterations = root.visits; lastIterations = root.visits.get();
final Node mostVisitedChild = mostVisitedChild(root); final Node mostVisitedChild = mostVisitedChild(root);
final long move = mostVisitedChild.move; final long move = mostVisitedChild.move;

View File

@@ -3,26 +3,27 @@ package org.toop.game.players.ai.mcts;
import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.game.players.ai.MCTSAI; import org.toop.game.players.ai.MCTSAI;
import java.util.ArrayList; import java.util.concurrent.CountDownLatch;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.TimeUnit;
public class MCTSAI3 extends MCTSAI { public class MCTSAI3 extends MCTSAI {
private final int threads; private final int threads;
private final ExecutorService threadPool;
public MCTSAI3(int milliseconds, int threads) { public MCTSAI3(int milliseconds, int threads) {
super(milliseconds); super(milliseconds);
this.threads = threads; this.threads = threads;
this.threadPool = Executors.newFixedThreadPool(threads);
} }
public MCTSAI3(MCTSAI3 other) { public MCTSAI3(MCTSAI3 other) {
super(other); super(other);
this.threads = other.threads; this.threads = other.threads;
this.threadPool = other.threadPool;
} }
@Override @Override
@@ -32,53 +33,27 @@ public class MCTSAI3 extends MCTSAI {
@Override @Override
public long getMove(TurnBasedGame game) { public long getMove(TurnBasedGame game) {
final ExecutorService pool = Executors.newFixedThreadPool(threads); final Node root = new Node(game.deepCopy(), null, 0L);
final long endTime = System.nanoTime() + milliseconds * 1_000_000L; final long endTime = System.nanoTime() + milliseconds * 1_000_000L;
final List<Callable<Node>> tasks = new ArrayList<>(); final CountDownLatch latch = new CountDownLatch(threads);
for (int i = 0; i < threads; i++) { for (int i = 0; i < threads; i++) {
tasks.add(() -> { threadPool.submit(() -> {
final Node localRoot = new Node(game.deepCopy()); try {
iterate(root, endTime);
// while (Float.isNaN(localRoot.solved) && System.nanoTime() < endTime) { } finally {
while (System.nanoTime() < endTime) { latch.countDown();
Node leaf = selection(localRoot);
leaf = expansion(leaf);
final float value = simulation(leaf);
backPropagation(leaf, value);
} }
return localRoot;
}); });
} }
try { try {
final List<Future<Node>> results = pool.invokeAll(tasks); final long remaining = endTime - System.nanoTime();
latch.await(remaining, TimeUnit.NANOSECONDS);
pool.shutdown(); lastIterations = root.visits.get();
final Node root = new Node(game.deepCopy());
for (int i = 0; i < root.children.length; i++) {
expansion(root);
}
for (final Future<Node> result : results) {
final Node localRoot = result.get();
for (final Node localChild : localRoot.children) {
for (int i = 0; i < root.children.length; i++) {
if (localChild.move == root.children[i].move) {
root.children[i].visits += localChild.visits;
root.visits += localChild.visits;
break;
}
}
}
}
lastIterations = root.visits;
final Node mostVisitedChild = mostVisitedChild(root); final Node mostVisitedChild = mostVisitedChild(root);
return mostVisitedChild.move; return mostVisitedChild.move;
@@ -89,4 +64,13 @@ public class MCTSAI3 extends MCTSAI {
return randomSetBit(legalMoves); return randomSetBit(legalMoves);
} }
} }
private void iterate(Node root, long endTime) {
while (Float.isNaN(root.solved) && System.nanoTime() < endTime) {
Node leaf = selection(root);
leaf = expansion(leaf);
final int value = simulation(leaf);
backPropagation(leaf, value);
}
}
} }

View File

@@ -3,29 +3,29 @@ package org.toop.game.players.ai.mcts;
import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.game.players.ai.MCTSAI; import org.toop.game.players.ai.MCTSAI;
import java.util.ArrayList; import java.util.concurrent.CountDownLatch;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.TimeUnit;
public class MCTSAI4 extends MCTSAI { public class MCTSAI4 extends MCTSAI {
private final int threads; private final int threads;
private final Node[] threadRoots; private final ExecutorService threadPool;
private Node root;
public MCTSAI4(int milliseconds, int threads) { public MCTSAI4(int milliseconds, int threads) {
super(milliseconds); super(milliseconds);
this.threads = threads; this.threads = threads;
this.threadRoots = new Node[threads]; this.threadPool = Executors.newFixedThreadPool(threads);
} }
public MCTSAI4(MCTSAI4 other) { public MCTSAI4(MCTSAI4 other) {
super(other); super(other);
this.threads = other.threads; this.threads = other.threads;
this.threadRoots = other.threadRoots; this.threadPool = other.threadPool;
} }
@Override @Override
@@ -35,66 +35,32 @@ public class MCTSAI4 extends MCTSAI {
@Override @Override
public long getMove(TurnBasedGame game) { public long getMove(TurnBasedGame game) {
for (int i = 0; i < threads; i++) { root = findOrResetRoot(root, game);
threadRoots[i] = findOrResetRoot(threadRoots[i], game);
}
final ExecutorService pool = Executors.newFixedThreadPool(threads);
final long endTime = System.nanoTime() + milliseconds * 1_000_000L; final long endTime = System.nanoTime() + milliseconds * 1_000_000L;
final List<Callable<Node>> tasks = new ArrayList<>(); final CountDownLatch latch = new CountDownLatch(threads);
for (int i = 0; i < threads; i++) { for (int i = 0; i < threads; i++) {
final int threadIndex = i; threadPool.submit(() -> {
try {
tasks.add(() -> { iterate(root, endTime);
final Node localRoot = threadRoots[threadIndex]; } finally {
latch.countDown();
// while (Float.isNaN(localRoot.solved) && System.nanoTime() < endTime) {
while (System.nanoTime() < endTime) {
Node leaf = selection(localRoot);
leaf = expansion(leaf);
final float value = simulation(leaf);
backPropagation(leaf, value);
} }
return localRoot;
}); });
} }
try { try {
final List<Future<Node>> results = pool.invokeAll(tasks); final long remaining = endTime - System.nanoTime();
latch.await(remaining, TimeUnit.NANOSECONDS);
pool.shutdown(); lastIterations = root.visits.get();
final Node root = new Node(game.deepCopy());
for (int i = 0; i < root.children.length; i++) {
expansion(root);
}
for (final Future<Node> result : results) {
final Node localRoot = result.get();
for (final Node localChild : localRoot.children) {
for (int i = 0; i < root.children.length; i++) {
if (localChild.move == root.children[i].move) {
root.children[i].visits += localChild.visits;
root.visits += localChild.visits;
break;
}
}
}
}
lastIterations = root.visits;
final Node mostVisitedChild = mostVisitedChild(root); final Node mostVisitedChild = mostVisitedChild(root);
final long move = mostVisitedChild.move; final long move = mostVisitedChild.move;
for (int i = 0; i < threads; i++) { root = findChildByMove(root, move);
threadRoots[i] = findChildByMove(threadRoots[i], move);
}
return move; return move;
} catch (Exception _) { } catch (Exception _) {
@@ -104,4 +70,13 @@ public class MCTSAI4 extends MCTSAI {
return randomSetBit(legalMoves); return randomSetBit(legalMoves);
} }
} }
private void iterate(Node root, long endTime) {
while (Float.isNaN(root.solved) && System.nanoTime() < endTime) {
Node leaf = selection(root);
leaf = expansion(leaf);
final int value = simulation(leaf);
backPropagation(leaf, value);
}
}
} }

View File

@@ -1,35 +1,50 @@
package research; package research;
import org.apache.maven.surefire.shared.io.FileDeleteStrategy;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import org.toop.framework.game.games.reversi.BitboardReversi; import org.toop.framework.game.games.reversi.BitboardReversi;
import org.toop.framework.game.players.ArtificialPlayer; import org.toop.framework.game.players.ArtificialPlayer;
import org.toop.game.players.ai.MCTSAI; import org.toop.game.players.ai.MCTSAI;
import org.toop.game.players.ai.RandomAI;
import org.toop.game.players.ai.mcts.MCTSAI1; import org.toop.game.players.ai.mcts.MCTSAI1;
import org.toop.game.players.ai.mcts.MCTSAI2; import org.toop.game.players.ai.mcts.MCTSAI2;
import org.toop.game.players.ai.mcts.MCTSAI3; import org.toop.game.players.ai.mcts.MCTSAI3;
import org.toop.game.players.ai.mcts.MCTSAI4; import org.toop.game.players.ai.mcts.MCTSAI4;
import java.io.BufferedReader;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class AITest { public class AITest {
private static String fileName = "gameData.csv";
private static List<Matchup> matchupList = new ArrayList<Matchup>(); private static List<Matchup> matchupList = new ArrayList<Matchup>();
private static List<AIData> dataList = new ArrayList<AIData>(); private static List<AIData> dataList = new ArrayList<AIData>();
private static List<GameData> gameDataList = new ArrayList<GameData>();
@BeforeAll @BeforeAll
public static void init() { public static void init() {
var versions = new ArtificialPlayer[4]; var versions = new ArtificialPlayer[4];
versions[0] = new ArtificialPlayer(new MCTSAI1(100), "MCTS V1"); versions[0] = new ArtificialPlayer(new MCTSAI1(10), "MCTS V1");
versions[1] = new ArtificialPlayer(new MCTSAI2(100), "MCTS V2"); versions[1] = new ArtificialPlayer(new MCTSAI2(10), "MCTS V2");
versions[2] = new ArtificialPlayer(new MCTSAI3(100, 8), "MCTS V3"); versions[2] = new ArtificialPlayer(new MCTSAI3(10, 8), "MCTS V3");
versions[3] = new ArtificialPlayer(new MCTSAI4(100, 8), "MCTS V4"); versions[3] = new ArtificialPlayer(new MCTSAI4(10, 8), "MCTS V4");
for (int i = 0; i < versions.length; i++) { for (int i = 0; i < versions.length; i++) {
for (int j = i + 1; j < versions.length; j++) { for (int j = i + 1; j < versions.length; j++) {
final int playerIndex1 = i % versions.length; final int playerIndex1 = i % versions.length;
@@ -40,22 +55,56 @@ public class AITest {
} }
} }
// @BeforeAll
// public static void init() {
//
// var versions = new ArtificialPlayer[11];
// versions[0] = new ArtificialPlayer(new MCTSAI3(10, 1), "MCTS V3T1");
// versions[1] = new ArtificialPlayer(new MCTSAI3(10, 2), "MCTS V3T2");
// versions[2] = new ArtificialPlayer(new MCTSAI3(10, 4), "MCTS V3T4");
// versions[3] = new ArtificialPlayer(new MCTSAI3(10, 8), "MCTS V3T8");
// versions[4] = new ArtificialPlayer(new MCTSAI3(10, 16), "MCTS V3T16");
// versions[5] = new ArtificialPlayer(new MCTSAI3(10, 128), "MCTS V3T32");
// versions[6] = new ArtificialPlayer(new MCTSAI3(10, 256), "MCTS V3T64");
// versions[7] = new ArtificialPlayer(new MCTSAI3(10, 128), "MCTS V3T128");
// versions[8] = new ArtificialPlayer(new MCTSAI3(10, 256), "MCTS V3T256");
// versions[9] = new ArtificialPlayer(new MCTSAI3(10, 512), "MCTS V3T512");
// versions[10] = new ArtificialPlayer(new MCTSAI3(10, 1024), "MCTS V3T1024");
//
// for (int i = 0; i < versions.length; i++) {
// for (int j = i + 1; j < versions.length; j++) {
// final int playerIndex1 = i % versions.length;
// final int playerIndex2 = j % versions.length;
// addMatch(versions[playerIndex1], versions[playerIndex2]);
// addMatch(versions[playerIndex2], versions[playerIndex1]); // home vs away system
// }
// }
// }
public static void addMatch(ArtificialPlayer v1, ArtificialPlayer v2) { public static void addMatch(ArtificialPlayer v1, ArtificialPlayer v2) {
matchupList.add(new Matchup(v1, v2)); matchupList.add(new Matchup(v1, v2));
} }
public void addData(AIData data) { public void addAIData(AIData data) {
dataList.add(data); dataList.add(data);
} }
public void addGameData(GameData data) {
gameDataList.add(data);
}
@Test @Test
public void testAIvsAI() { public void testAIvsAI() {
while (true) {
for (Matchup m : matchupList) { for (Matchup m : matchupList) {
playGame(m); playGame(m);
} }
} }
}
public void playGame(Matchup m) { public void playGame(Matchup m) {
long nanocounterAI1 = 0L;
long nanocounterAI2 = 0L;
List<Integer> iterationsAI1 = new ArrayList<>(); List<Integer> iterationsAI1 = new ArrayList<>();
List<Integer> iterationsAI2 = new ArrayList<>(); List<Integer> iterationsAI2 = new ArrayList<>();
final BitboardReversi match = new BitboardReversi(); final BitboardReversi match = new BitboardReversi();
@@ -65,18 +114,91 @@ public class AITest {
match.init(players); match.init(players);
while (!match.isTerminal()) { while (!match.isTerminal()) {
final int currentAI = match.getCurrentTurn(); final int currentAI = match.getCurrentTurn();
final long startTime = System.nanoTime();
final long move = players[currentAI].getMove(match); final long move = players[currentAI].getMove(match);
final long endTime = System.nanoTime();
if (players[currentAI].getAi() instanceof MCTSAI) { if (players[currentAI].getAi() instanceof MCTSAI) {
final int lastIterations = ((MCTSAI) players[currentAI].getAi()).getLastIterations(); final int lastIterations = ((MCTSAI) players[currentAI].getAi()).getLastIterations();
if (currentAI == 0) { if (currentAI == 0) {
iterationsAI1.add(lastIterations); iterationsAI1.add(lastIterations);
nanocounterAI1 += (endTime - startTime);
} else { } else {
iterationsAI2.add(lastIterations); iterationsAI2.add(lastIterations);
nanocounterAI2 += (endTime - startTime);
} }
} }
match.play(move); match.play(move);
} }
generateData(m, match, iterationsAI1, iterationsAI2); generateMatchData(m.getPlayer1().getName(), m.getPlayer2().getName(), match, iterationsAI1, iterationsAI2, nanocounterAI1, nanocounterAI2);
}
public void generateMatchData(
String AI1,
String AI2,
BitboardReversi match,
List<Integer> iterationsAI1,
List<Integer> iterationsAI2,
long nanocounterAI1,
long nanocounterAI2
) {
try {
var ai110 = iterationsAI1.subList(0, 10);
var ai120 = iterationsAI1.subList(10, 20);
var ai130 = iterationsAI1.subList(20, iterationsAI1.size());
var ai210 = iterationsAI2.subList(0, 10);
var ai220 = iterationsAI2.subList(10, 20);
var ai230 = iterationsAI2.subList(20, iterationsAI2.size());
writeGamesToCSV(fileName, new GameData(
AI1,
AI2,
getWinnerForMatch(AI1, AI2, match),
match.getAmountOfTurns(),
iterationsAI1.stream().mapToLong(Integer::longValue).sum(),
ai110.stream().mapToLong(Integer::longValue).sum(),
ai120.stream().mapToLong(Integer::longValue).sum(),
ai130.stream().mapToLong(Integer::longValue).sum(),
iterationsAI1.stream().mapToDouble(Integer::doubleValue).sum() / iterationsAI1.size(),
ai110.stream().mapToDouble(Integer::doubleValue).sum() / ai110.size(),
ai120.stream().mapToDouble(Integer::doubleValue).sum() / ai120.size(),
ai130.stream().mapToDouble(Integer::doubleValue).sum() / ai130.size(),
iterationsAI2.stream().mapToInt(Integer::intValue).sum(),
ai210.stream().mapToLong(Integer::longValue).sum(),
ai220.stream().mapToLong(Integer::longValue).sum(),
ai230.stream().mapToLong(Integer::longValue).sum(),
iterationsAI2.stream().mapToDouble(Integer::doubleValue).sum() / iterationsAI2.size(),
ai210.stream().mapToDouble(Integer::doubleValue).sum() / ai210.size(),
ai220.stream().mapToDouble(Integer::doubleValue).sum() / ai220.size(),
ai230.stream().mapToDouble(Integer::doubleValue).sum() / ai230.size(),
nanocounterAI1,
nanocounterAI2,
LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))
));
} catch (IOException e) {
throw new RuntimeException(e);
} catch (IndexOutOfBoundsException e) {
return;
}
}
public String getWinnerForMatch(String AI1, String AI2, BitboardReversi match) {
if (match.getWinner() == 0) {
return AI1;
}
if (match.getWinner() == 1) {
return AI2;
} else {
return "TIE";
}
} }
public void generateData(Matchup matchup, BitboardReversi match, List<Integer> iterationsAI1, List<Integer> iterationsAI2) { public void generateData(Matchup matchup, BitboardReversi match, List<Integer> iterationsAI1, List<Integer> iterationsAI2) {
@@ -90,10 +212,10 @@ public class AITest {
} }
} }
if (!(matchup1Found)) { if (!(matchup1Found)) {
addData(new AIData(matchup.getPlayer1().getName(), 0, 0, 0, 0, 0, 0)); addAIData(new AIData(matchup.getPlayer1().getName(), 0, 0, 0, 0, 0, 0));
} }
if (!(matchup2Found)) { if (!(matchup2Found)) {
addData(new AIData(matchup.getPlayer2().getName(), 0, 0, 0, 0, 0, 0)); addAIData(new AIData(matchup.getPlayer2().getName(), 0, 0, 0, 0, 0, 0));
} }
for (AIData aiData : dataList) { // set data for player 1 for (AIData aiData : dataList) { // set data for player 1
@@ -150,13 +272,56 @@ public class AITest {
@AfterAll @AfterAll
public static void writeAfterTests() { public static void writeAfterTests() {
try { try {
writeToCsv("Data.csv", dataList); writeAIToCsv("Data.csv", dataList);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
public static void writeToCsv(String filepath, List<AIData> dataList) throws IOException { public static void writeGamesToCSV(String filepath, GameData gameData) throws IOException {
try (
final BufferedWriter writer = Files.newBufferedWriter(
Paths.get(filepath),
StandardCharsets.UTF_8,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND
);
final BufferedReader reader = new BufferedReader(new FileReader(filepath))
) {
if (reader.readLine() == null || reader.readLine().isBlank()) {
writer.write("Black,White,Winner,Turns Played,Black total iterations,Black total iterations 0-10,Black total iterations 11-20,Black total iterations 21-30,Black average iterations,Black average iterations 0-10,Black average iterations 11-20,Black average iterations 21-30,White total iterations,White total iterations 0-10,White total iterations 11-20,White total iterations 21-30,White average iterations,White average iterations 0-10,White average iterations 11-20,White average iterations 21-30,Total Time AI1,Total Time AI2,Time");
writer.newLine();
}
writer.write(
gameData.AI1() + "," +
gameData.AI2() + "," +
gameData.winner() + "," +
gameData.turns() + "," +
gameData.AI1totalIterations() + "," +
gameData.AI1totalIterations10() + "," +
gameData.AI1totalIterations20() + "," +
gameData.AI1totalIterations30() + "," +
BigDecimal.valueOf(gameData.AI1averageIterations()).setScale(2, RoundingMode.HALF_EVEN) + "," +
BigDecimal.valueOf(gameData.AI1averageIterations10()).setScale(2, RoundingMode.HALF_EVEN) + "," +
BigDecimal.valueOf(gameData.AI1averageIterations20()).setScale(2, RoundingMode.HALF_EVEN) + "," +
BigDecimal.valueOf(gameData.AI1averageIterations30()).setScale(2, RoundingMode.HALF_EVEN) + "," +
gameData.AI2totalIterations() + "," +
gameData.AI2totalIterations10() + "," +
gameData.AI2totalIterations20() + "," +
gameData.AI2totalIterations30() + "," +
BigDecimal.valueOf(gameData.AI2averageIterations()).setScale(2, RoundingMode.HALF_EVEN) + "," +
BigDecimal.valueOf(gameData.AI2averageIterations10()).setScale(2, RoundingMode.HALF_EVEN) + "," +
BigDecimal.valueOf(gameData.AI2averageIterations20()).setScale(2, RoundingMode.HALF_EVEN) + "," +
BigDecimal.valueOf(gameData.AI2averageIterations30()).setScale(2, RoundingMode.HALF_EVEN) + "," +
(gameData.nanoAI1() / 1_000_000L) + "," +
(gameData.nanoAI2() / 1_000_000L) + "," +
gameData.time());
writer.newLine();
}
}
public static void writeAIToCsv(String filepath, List<AIData> dataList) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filepath))) { try (BufferedWriter writer = new BufferedWriter(new FileWriter(filepath))) {
writer.write("AI Name,Games Played,Winrate,Average Iterations,Average Iterations 0-10, Average Iterations 11-20, Average Iterations 20-30"); writer.write("AI Name,Games Played,Winrate,Average Iterations,Average Iterations 0-10, Average Iterations 11-20, Average Iterations 20-30");
writer.newLine(); writer.newLine();

View File

@@ -0,0 +1,32 @@
package research;
public record GameData(
String AI1,
String AI2,
String winner,
int turns,
long AI1totalIterations,
long AI1totalIterations10,
long AI1totalIterations20,
long AI1totalIterations30,
double AI1averageIterations,
double AI1averageIterations10,
double AI1averageIterations20,
double AI1averageIterations30,
long AI2totalIterations,
long AI2totalIterations10,
long AI2totalIterations20,
long AI2totalIterations30,
double AI2averageIterations,
double AI2averageIterations10,
double AI2averageIterations20,
double AI2averageIterations30,
long nanoAI1,
long nanoAI2,
String time
) {}