225 Commits

Author SHA1 Message Date
65220d9649 Hotfix for stuff 2026-01-07 17:16:45 +01:00
Bas Antonius de Jong
c64a2e2c65 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

---------

Co-authored-by: ramollia <>
Co-authored-by: Stef <stbuwalda@gmail.com>
Co-authored-by: Stef <48526421+StefBuwalda@users.noreply.github.com>
2026-01-07 16:15:49 +01:00
230f7480e4 Merge remote-tracking branch 'origin/289-server' into 289-server 2026-01-07 15:41:28 +01:00
Bas de Jong
67f39c3f3b Code readability 2026-01-07 14:38:19 +01:00
6e6a383708 Collapsed interfaces in View section 2026-01-07 13:26:43 +01:00
b7dec7798b Collapsed interfaces in Controller section 2026-01-07 13:13:32 +01:00
2caa4fc79f Fixed runtime error I forgot to fix. 2026-01-07 12:42:54 +01:00
e72d888d84 Collapsed interfaces from model portion 2026-01-07 12:41:25 +01:00
Bas de Jong
a7d1a964c2 Moved subscriptions to store 2025-12-15 10:01:23 +01:00
lieght
dccf428bb8 TableWidget 2025-12-14 17:18:57 +01:00
lieght
6e2ea82a32 UI fixes after game end 2025-12-14 13:30:21 +01:00
lieght
34c85ec472 Removed user from subscription if in a game 2025-12-14 13:11:55 +01:00
2d9b34b7f6 Quick fix so more than one game can be played in succession 2025-12-14 11:36:51 +01:00
lieght
8867d5a1ea Missed a boolean 2025-12-14 01:19:16 +01:00
lieght
b94d1b6c9d Small improvements to usability, auto disconnect when server closes connection 2025-12-14 01:13:42 +01:00
lieght
8cb0a86d4e Working subscription, button only subs to reversi right now 2025-12-13 23:20:28 +01:00
lieght
c2f1df7143 Refactor done, added ability to subscribe 2025-12-13 22:44:13 +01:00
lieght
0956286616 Partial server refactor 2025-12-13 21:11:26 +01:00
55de6b5b18 Merge remote-tracking branch 'origin/289-server' into 289-server 2025-12-13 18:53:18 +01:00
73d71f2a2d Making moves works. Game notifies when game has ended. 2025-12-13 18:53:10 +01:00
lieght
cbcce29780 Closable server 2025-12-13 18:38:31 +01:00
lieght
afcd9be71e Fixed hasArgs 2025-12-13 17:53:31 +01:00
a9145d44cf Merge remote-tracking branch 'origin/289-server' into 289-server 2025-12-13 17:50:03 +01:00
c015100ebf Werkt nog niet 2025-12-13 17:49:54 +01:00
lieght
cd5736afc8 Removed space in naming 2025-12-13 17:38:36 +01:00
lieght
89a9cb1e55 Using pairs now in server.java 2025-12-13 17:37:34 +01:00
lieght
22270e58dc Added pairs 2025-12-13 17:33:14 +01:00
lieght
edd2c24b65 Added ability to take ServerPlayer from user 2025-12-13 17:22:56 +01:00
c929abc4b8 Removed Generics, pray nothing breaks. 2025-12-13 17:08:34 +01:00
lieght
8b85915c74 Fixes 2025-12-13 17:08:10 +01:00
lieght
150fb2986f Fixed tic tac toe naming 2025-12-13 15:17:16 +01:00
lieght
9c20fcbc39 Fixed bugs, easy to use host button 2025-12-13 15:01:28 +01:00
lieght
4d31a8ed44 Working challenges 2025-12-12 21:48:57 +01:00
lieght
fc47d81b8e Init challenges 2025-12-12 19:47:51 +01:00
lieght
a60c702306 Tests and better instantiation 2025-12-12 16:47:17 +01:00
lieght
c30c118c04 Code cleanup 2025-12-12 16:04:12 +01:00
4b8edf1585 Merge remote-tracking branch 'origin/289-server' into 289-server
# Conflicts:
#	app/src/main/java/org/toop/app/canvas/ReversiBitCanvas.java
#	app/src/main/java/org/toop/app/canvas/TicTacToeBitCanvas.java
#	framework/src/main/java/org/toop/framework/game/games/reversi/BitboardReversi.java
#	framework/src/main/java/org/toop/framework/game/games/tictactoe/BitboardTicTacToe.java
2025-12-12 15:53:45 +01:00
fa9c2ce32b Removed Generics, pray nothing breaks. 2025-12-12 15:53:24 +01:00
Bas de Jong
2599f9fa40 Testing code 2025-12-12 15:17:29 +01:00
Bas de Jong
84e411fa38 Moves 2025-12-12 15:17:12 +01:00
Bas de Jong
66cb000fad Init server code 2025-12-12 15:15:55 +01:00
Bas Antonius de Jong
0132981d94 Merge branch 'main' into Development 2025-12-09 21:20:08 +01:00
Bas de Jong
322197494c Will fix tests etc later 2025-12-09 21:19:30 +01:00
Bas de Jong
a9c99df5d2 Better limits to generic acceptance 2025-12-09 21:07:30 +01:00
Stef
912d25c01f 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>
2025-12-08 18:23:06 +01:00
Ticho Hidding
adc7b1a8f3 fixed reversi colors being switched, causing multiple issues 2025-12-08 17:14:31 +01:00
Ticho Hidding
3a8b1c2454 shitty fix for player selector spacing issue v2 2025-12-08 16:12:42 +01:00
Ticho Hidding
846898988f shitty fix for player selector spacing issue 2025-12-08 15:59:11 +01:00
Ticho Hidding
ecd0fd26be changed "fullscreen exit key combination" from esc to F11 2025-12-08 15:11:17 +01:00
lieght
a838973e67 Code cleanup 2025-12-07 20:43:56 +01:00
lieght
a4f2a67d9c Deleted unnecessary imports 2025-12-07 20:39:29 +01:00
lieght
12bccb8854 Safety 2025-12-07 20:33:27 +01:00
lieght
80d3e47ad9 initSystems now uses latch instead of timer. Moved single threads to Executor 2025-12-07 18:59:19 +01:00
Bas Antonius de Jong
38f50cc16d 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 <>
2025-12-07 17:38:34 +01:00
lieght
f60df73b66 Loading circle, better loading colors. 2025-12-03 23:55:12 +01:00
8ca2399e6a Merge branch 'Development' of https://github.com/2OOP/pism into Development 2025-12-03 21:51:14 +01:00
8c75ac1471 Making threads verbose regarding exceptions 2025-12-03 21:51:01 +01:00
lieght
f866eab8ba Best fix for white screen at start 2025-12-03 21:50:32 +01:00
Stef
8d77f51355 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
2025-12-03 20:35:37 +01:00
lieght
040287ad70 Added infinite boolean, fixed loading behaviour at startup 2025-12-03 18:56:08 +01:00
lieght
740d2cf3db Fixed systems starting, before assets being loaded (I am retarded) 2025-12-03 18:13:57 +01:00
Bas Antonius de Jong
628e4f30b5 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 <>
2025-12-03 14:49:59 +01:00
Bas de Jong
bc84171029 Double loading call fix, LoadingWidget docs 2025-12-03 11:39:25 +01:00
Bas Antonius de Jong
3c9b010dd3 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 <>
2025-12-02 20:59:46 +01:00
michiel
4dbc4997a0 added back button sounds because SOMEONE fucked it up..... 2025-12-02 11:51:00 +01:00
Stef
9f55f8e1c7 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>
2025-12-02 11:25:22 +01:00
Bas Antonius de Jong
d9437c1b8a 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 <>
2025-12-02 10:57:46 +01:00
c332033a06 Fixed old new EventFlow().listen() missing false as third param 2025-11-30 18:03:16 +01:00
lieght
3953762178 Removed loading widget from Server.java 2025-11-30 17:55:52 +01:00
Bas Antonius de Jong
12a20a224e 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
2025-11-30 17:51:52 +01:00
Bas Antonius de Jong
81740acd04 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
2025-11-30 17:15:14 +01:00
Bas Antonius de Jong
25c02c7ad0 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
2025-11-30 14:22:05 +01:00
lieght
ec0ce4ea37 Added function input for enabling/disabling localization p/text 2025-11-29 12:23:17 +01:00
lieght
0ab071693f Removed views 2025-11-28 12:09:40 +01:00
Bas Antonius de Jong
1a11827ba3 Merge pull request #261 from 2OOP/Tutorials
merge tutorials to dev branch
2025-11-28 11:56:42 +01:00
ramollia
a2d651cd7d Merge remote-tracking branch 'refs/remotes/origin/Development' into Tutorials
# Conflicts:
#	app/src/main/java/org/toop/app/game/Connect4Game.java
2025-11-28 11:51:46 +01:00
0cb025edb9 Changed the way turns are being stored in TurnBasedGame. 2025-11-27 18:37:34 +01:00
6ea94fe658 Fixed compilation errors 2025-11-27 17:42:39 +01:00
tichohidding
ef5c1ce6e3 Merge pull request #241 from 2OOP/Widgets
Widgets into development
2025-11-27 17:19:30 +01:00
tichohidding
8c01c7fa7a Merge branch 'Development' into Widgets 2025-11-27 17:19:17 +01:00
Ticho Hidding
72dd78137b Merge remote-tracking branch 'origin/Development' into Development 2025-11-27 15:50:36 +01:00
71c918e9ee 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...
2025-11-27 15:45:31 +01:00
Ticho Hidding
81f4d307a2 fixed turn skip bug
fixed end score bug
now only shows legal and highlight moves when human
2025-11-27 15:41:09 +01:00
Ticho Hidding
710438ec1b resizable true 2025-11-27 14:07:39 +01:00
michiel
c14b66e892 rest van de tutorials toegevoegd 2025-11-27 11:42:38 +01:00
michiel301b
8c69453506 started a basis for the tutorials, tic tac toe is almost done with some general stuff still to do. 2025-11-26 22:03:06 +01:00
Bas de Jong
a6b835bddf Removed no more needed comments 2025-11-20 11:41:44 +01:00
Bas de Jong
ca11151381 Functional code, is now object orientated 2025-11-06 15:32:15 +01:00
ramollia
fa4e1ad5e3 widget system almost complete 2025-11-03 12:47:56 +01:00
Ticho Hidding
295c61c7eb added some comments and made some methods a bit more readable 2025-11-01 15:12:09 +01:00
ramollia
1c9af58264 half done with the widget system 2025-10-31 17:33:19 +01:00
6dc05e7123 Made connect4 public method private 2025-10-29 20:09:39 +01:00
d1a9f94ee0 Fixed warning "Warning:(27, 12) Copy constructor does not copy field 'mostRecentlyFlippedPieces'", removed unused field 2025-10-29 20:04:50 +01:00
edfb4ffe51 Changed checkForEarlyDraw so it doesn't need a game as input. 2025-10-29 20:03:07 +01:00
be31de4660 Reversi: made method private 2025-10-29 19:51:09 +01:00
ea30e20585 Privated methods that didn't have to be public 2025-10-29 18:05:01 +01:00
d7e370536e Applied encapsulation principle to TurnBasedBame.java 2025-10-29 17:37:21 +01:00
6811890531 Removed unused imports 2025-10-29 17:29:28 +01:00
5da0a02cc8 Refactored Game to follow encapsulation principle 2025-10-29 17:28:43 +01:00
50713c5021 Turned Abstract Method for AI into interface 2025-10-29 15:01:58 +01:00
d17edf7c4a Renamed Interface Playtable to IPlayable 2025-10-29 14:52:19 +01:00
84e257c17c Removed unused imports 2025-10-29 14:50:40 +01:00
925c848fda Moved the Move record into it's own file, seperated from Game 2025-10-29 14:49:43 +01:00
13bac113b7 Turned abstract methods into an interface 2025-10-29 14:37:31 +01:00
068337e01b Removed unused import 2025-10-29 14:27:51 +01:00
44d8ffbef2 Made the GameState enum it's own file and fixed imports 2025-10-29 14:26:41 +01:00
Ticho Hidding
ff611724e7 Merge remote-tracking branch 'origin/Development' into Development 2025-10-29 14:06:23 +01:00
Ticho Hidding
7f36d7f244 cool onhover effect for reversi 2025-10-29 14:06:05 +01:00
eda85ea888 Removed unused import and unused parameter 2025-10-29 13:51:56 +01:00
f026a4fec6 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. 2025-10-29 13:49:50 +01:00
Bas de Jong
b84255e00e Added replace to reduce boiler plate code 2025-10-28 15:23:36 +01:00
Bas de Jong
84c17d185b Fixes for garbage code by Omar 2025-10-28 14:37:15 +01:00
michiel
3776167299 iets met timing verkeerd temporary fix 2025-10-28 14:23:53 +01:00
ramollia
48ed6df6b4 started working on the widget system 2025-10-28 12:59:15 +01:00
michiel
2428048bd5 updated music ma,es 2025-10-28 12:53:10 +01:00
michiel
1440190ac3 kleine ui fix 2025-10-28 12:30:48 +01:00
ramollia
3a51434f91 Merge remote-tracking branch 'origin/Development' into Development 2025-10-28 11:14:22 +01:00
ramollia
5b20909f80 moved score out of game 2025-10-28 11:13:56 +01:00
michiel
00b5f9dced mainview false for sendchallengeview 2025-10-28 11:10:17 +01:00
michiel
188a5e8a45 can now go to last using previous and being at the first song 2025-10-28 10:53:02 +01:00
ramollia
0638a38fc1 moved score out of game 2025-10-28 09:53:33 +01:00
ramollia
75fb865dad Merge branch 'Demo3' into Development 2025-10-28 09:16:22 +01:00
ramollia
2cda94e4db canvas changes 2025-10-28 09:13:09 +01:00
ramollia
d6f3bb2185 Merge remote-tracking branch 'origin/Demo3' into Demo3 2025-10-27 17:22:35 +01:00
ramollia
95d7ea5e1d Merge branch 'OtherGames' into Demo3 2025-10-27 17:22:07 +01:00
Ticho Hidding
6fb248bec4 spam minder v2 2025-10-27 17:21:50 +01:00
ramollia
7b5c188280 Merge remote-tracking branch 'origin/OtherGames' into OtherGames 2025-10-27 17:21:32 +01:00
ramollia
1c2736ac75 fixed setgamelabels 2025-10-27 17:21:04 +01:00
Ticho Hidding
caa812217f spam minder 2025-10-27 17:20:22 +01:00
Ticho Hidding
c115fb91af tourney ready 2025-10-27 17:14:36 +01:00
Ticho Hidding
b506afdade can start game from playerlist screen 2025-10-27 14:57:15 +01:00
michiel
59cc64ada7 fixed tests 2025-10-27 14:20:20 +01:00
Ticho Hidding
bd096df2c2 Merge remote-tracking branch 'origin/AudioDisplay' into OtherGames 2025-10-27 13:56:07 +01:00
Ticho Hidding
dfd3934b96 Merge branch 'Development' into OtherGames 2025-10-27 13:53:06 +01:00
michiel
2f7f2955c1 Merge remote-tracking branch 'origin/AudioDisplay' into Development 2025-10-27 10:00:49 +01:00
ramollia
fb8acbe228 add simple flip animations and fixed(?) server somewhat 2025-10-25 17:37:50 +02:00
michiel301b
be40ee0187 Made it so that it indicates with the play/pause button if its paused or played 2025-10-24 13:28:59 +02:00
michiel301b
0c536b0f9b 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
2025-10-24 13:22:42 +02:00
lieght
c85ab38142 Merge remote-tracking branch 'origin/185-networkingeventlistener' into Development 2025-10-21 20:43:21 +02:00
Ticho Hidding
c3f764f33d connect4 with minimax AI 2025-10-19 22:01:33 +02:00
Ticho Hidding
df493a5eba new reversi test (both players no legal moves) 2025-10-19 13:06:46 +02:00
Ticho Hidding
0447f7b0fe added method for sorting the flipped pieces by distance to Move.position 2025-10-19 02:18:11 +02:00
lieght
b1589032be test fix 2025-10-17 20:22:27 +02:00
lieght
55989ab8ce Merge remote-tracking branch 'origin/UI' into AudioDisplay 2025-10-17 20:13:05 +02:00
lieght
443f2da97d Merge remote-tracking branch 'origin/UI' into 185-networkingeventlistener 2025-10-17 20:12:41 +02:00
lieght
8f047cd7b5 Faster event schedule for PlayingMusic event 2025-10-17 19:51:49 +02:00
lieght
bb2630f3d5 Small event fix 2025-10-17 19:46:03 +02:00
michiel
f44bf6bd02 Tiny fix when natural skip 2025-10-16 14:00:40 +02:00
Bas de Jong
3a120803e3 Fixes for garbage code by Omar 2025-10-16 13:59:24 +02:00
michiel
6e35067c18 Skip Button 2025-10-16 13:37:01 +02:00
michiel
30f797022c Skip Button 2025-10-16 13:35:41 +02:00
lieght
abc173a1ee Merge remote-tracking branch 'origin/AudioDisplay' into AudioDisplay 2025-10-16 00:57:01 +02:00
lieght
3784cd790e Clips now also return positional information 2025-10-16 00:56:46 +02:00
michiel301b
f44c3932dc 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)
2025-10-16 00:45:29 +02:00
lieght
975207a6de Nuke everything on close. 2025-10-16 00:37:20 +02:00
lieght
545e830b3c Updated timings 2025-10-15 23:54:17 +02:00
lieght
977e9bb102 Updated test. 2025-10-15 23:24:21 +02:00
lieght
69bc55dc26 Polling music event, fires every 1 second 2025-10-15 23:21:00 +02:00
michiel301b
62e2b71ece begin van audio display 2025-10-15 22:58:25 +02:00
lieght
cf9dbef882 Correct client creation and user polling 2025-10-15 21:36:46 +02:00
Bas Antonius de Jong
09f3dc4e3e Merge pull request #186 from 2OOP/UI
Update UI for networking
2025-10-15 21:06:31 +02:00
lieght
6ae5f1c2d2 Merge remote-tracking branch 'origin/UI' into UI
# Conflicts:
#	app/src/main/java/org/toop/app/App.java
#	app/src/main/java/org/toop/app/layer/layers/MainLayer.java
#	app/src/main/java/org/toop/app/layer/layers/OptionsPopup.java
#	app/src/main/java/org/toop/app/layer/layers/game/TicTacToeLayer.java
#	app/src/main/java/org/toop/local/AppSettings.java
2025-10-15 21:05:33 +02:00
lieght
b30420616a Merge remote-tracking branch 'origin/185-networkingeventlistener' into UI
# Conflicts:
#	app/src/main/java/org/toop/app/App.java
#	app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java
#	app/src/main/java/org/toop/app/layer/layers/MainLayer.java
#	app/src/main/java/org/toop/app/layer/layers/OptionsPopup.java
#	app/src/main/java/org/toop/app/layer/layers/game/TicTacToeLayer.java
#	app/src/main/java/org/toop/local/AppSettings.java
2025-10-15 21:05:01 +02:00
lieght
1134649197 Documentation 2025-10-15 20:11:43 +02:00
lieght
5beebf9663 Quick fix for closing connection. 2025-10-15 19:08:18 +02:00
lieght
798005de2c Refactor to make Events easier to work with. 2025-10-15 19:05:09 +02:00
ramollia
e71369f7ff visual update 2025-10-15 16:46:19 +02:00
ramollia
56ebe1347c add: server chat box 2025-10-15 16:13:42 +02:00
ramollia
7f8ed029b9 add: reversi game 2025-10-15 15:22:19 +02:00
lieght
bc6182e29a Fixed event bug 2025-10-15 03:26:19 +02:00
lieght
dc81d9c3ca Added exceptions. Added reconnect attempts and changeable address 2025-10-15 02:58:14 +02:00
ramollia
2c8db95ba7 Merge remote-tracking branch 'origin/Reversi' into UI
# Conflicts:
#	app/src/main/java/org/toop/app/App.java
#	app/src/main/java/org/toop/app/canvas/GameCanvas.java
#	app/src/main/java/org/toop/app/layer/layers/MainLayer.java
#	app/src/main/java/org/toop/app/layer/layers/OptionsPopup.java
#	app/src/main/java/org/toop/app/layer/layers/game/TicTacToeLayer.java
#	app/src/main/java/org/toop/local/AppSettings.java
#	app/src/main/resources/assets/localization/localization_ar.properties
#	app/src/main/resources/assets/localization/localization_de.properties
#	app/src/main/resources/assets/localization/localization_en.properties
#	app/src/main/resources/assets/localization/localization_es.properties
#	app/src/main/resources/assets/localization/localization_fr.properties
#	app/src/main/resources/assets/localization/localization_hi.properties
#	app/src/main/resources/assets/localization/localization_it.properties
#	app/src/main/resources/assets/localization/localization_ja.properties
#	app/src/main/resources/assets/localization/localization_ko.properties
#	app/src/main/resources/assets/localization/localization_nl.properties
#	app/src/main/resources/assets/localization/localization_ru.properties
#	app/src/main/resources/assets/localization/localization_zh.properties
2025-10-15 00:39:14 +02:00
ramollia
1f64a2dfd0 fixed merge conflicts 2025-10-15 00:26:27 +02:00
ramollia
0a6c2487bd Merge remote-tracking branch 'origin/UI' into UI
# Conflicts:
#	app/src/main/java/org/toop/app/App.java
#	app/src/main/java/org/toop/app/GameInformation.java
#	app/src/main/java/org/toop/app/canvas/GameCanvas.java
#	app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java
#	app/src/main/java/org/toop/app/layer/Container.java
#	app/src/main/java/org/toop/app/layer/Layer.java
#	app/src/main/java/org/toop/app/layer/NodeBuilder.java
#	app/src/main/java/org/toop/app/layer/Popup.java
#	app/src/main/java/org/toop/app/layer/containers/HorizontalContainer.java
#	app/src/main/java/org/toop/app/layer/containers/VerticalContainer.java
#	app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java
#	app/src/main/java/org/toop/app/layer/layers/CreditsPopup.java
#	app/src/main/java/org/toop/app/layer/layers/MainLayer.java
#	app/src/main/java/org/toop/app/layer/layers/MultiplayerLayer.java
#	app/src/main/java/org/toop/app/layer/layers/OptionsPopup.java
#	app/src/main/java/org/toop/app/layer/layers/QuitPopup.java
#	app/src/main/java/org/toop/app/layer/layers/game/GameFinishedPopup.java
#	app/src/main/java/org/toop/app/layer/layers/game/TicTacToeLayer.java
#	app/src/main/java/org/toop/local/AppSettings.java
2025-10-15 00:17:17 +02:00
ramollia
e382cf95f2 gui refactor 2025-10-15 00:14:39 +02:00
lieght
14e6785c6f Some better docs. 2025-10-15 00:01:41 +02:00
lieght
444a81abc3 Improved API for dependency injection 2025-10-14 23:44:45 +02:00
lieght
d0676d9363 Forgot to remove 2025-10-14 23:27:52 +02:00
lieght
8f7e78efb5 Reworked NetworkingClientManager into SRP model. 2025-10-14 23:27:12 +02:00
lieght
cb7e3298fd Added shuffling on user request 2025-10-14 19:57:07 +02:00
michiel
c09f0eb1f9 punk toegevoegd 2025-10-14 14:18:52 +02:00
michiel
a3203dd78e Merge remote-tracking branch 'origin/Reversi' into Reversi 2025-10-14 13:40:00 +02:00
michiel
efbab12b10 Tests toegevoegd 2025-10-14 13:39:22 +02:00
Ticho Hidding
8e823b4108 getLegalMoves logic seems fixed //todo write better tests 2025-10-14 13:34:47 +02:00
3165ff33b3 Tests for SoundEffectManager 2025-10-14 13:21:17 +02:00
Ticho Hidding
81abeef843 commit ofzo 2025-10-14 13:12:45 +02:00
Ticho Hidding
be26db953e Merge remote-tracking branch 'origin/Development' into Reversi
# Conflicts:
#	app/src/main/java/org/toop/app/canvas/GameCanvas.java
#	app/src/main/java/org/toop/app/layer/layers/MainLayer.java
#	game/src/main/java/org/toop/game/Game.java
#	game/src/main/java/org/toop/game/othello/Othello.java
#	game/src/main/java/org/toop/game/othello/OthelloAI.java
2025-10-14 11:25:46 +02:00
Ticho Hidding
76c7e44447 Merge remote-tracking branch 'origin/Development' into Reversi 2025-10-14 11:07:10 +02:00
Ticho Hidding
d9f6c7ee74 commit 2025-10-14 11:06:41 +02:00
Bas de Jong
0e3165ed98 Added linelistener to SoundEffectAsset 2025-10-13 13:45:16 +02:00
lieght
4bcf70d4dc Removed ResourceManager from AudioManagers 2025-10-13 02:47:34 +02:00
lieght
fcc0b9d0dd SoundEffectManager now generic 2025-10-13 02:27:04 +02:00
lieght
cdc34b432e 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.
2025-10-13 02:06:12 +02:00
Bas Antonius de Jong
00daf37244 Merge pull request #175 from 2OOP/166-audiomanager-opslitsen-om-single-responsibility-principle-te-volgen
166 audiomanager opslitsen om single responsibility principle te volgen Done
2025-10-12 20:44:57 +02:00
lieght
bab11b64a5 Merge remote-tracking branch 'origin/166-audiomanager-opslitsen-om-single-responsibility-principle-te-volgen' into 166-audiomanager-opslitsen-om-single-responsibility-principle-te-volgen 2025-10-12 20:42:09 +02:00
lieght
4b60fa88ee Moved restrictedAPI to future release 2025-10-12 20:41:56 +02:00
lieght
f3316145e7 Finished todo's 2025-10-12 20:41:54 +02:00
lieght
53a72cc340 Moved restrictedAPI to future release 2025-10-12 20:41:10 +02:00
lieght
081fbda7b1 Merge remote-tracking branch 'origin/166-audiomanager-opslitsen-om-single-responsibility-principle-te-volgen' into 166-audiomanager-opslitsen-om-single-responsibility-principle-te-volgen
# Conflicts:
#	framework/src/main/java/org/toop/framework/audio/MusicManager.java
2025-10-12 20:37:23 +02:00
lieght
d051e3e603 Finished todo's 2025-10-12 20:36:28 +02:00
Bas de Jong
5317c42c81 Removed no more needed code. 2025-10-12 07:20:28 +02:00
Bas de Jong
6085f6ebe2 Small fixes. 2025-10-12 07:19:27 +02:00
Bas de Jong
ea6264e519 Added ErrorProne for potential bugs. Fixed potential bugs. 2025-10-12 07:14:26 +02:00
lieght
e3bce3889e Renamed VolumeTypes to VolumeControl. Made it thread safe. Added docs to VolumeControl and co.
removed .updateAllVolumes() in favor of auto updating inside enum instead
2025-10-12 02:20:30 +02:00
lieght
b050e06ceb Minor changes in API design 2025-10-12 01:56:06 +02:00
lieght
7631a10838 Renamed VOLUME to MASTERVOLUME for better naming 2025-10-12 01:02:58 +02:00
lieght
ca25338971 Fixed grammer and spelling mistakes 2025-10-12 00:55:02 +02:00
lieght
42e03e0878 Removed file no longer in use 2025-10-12 00:52:02 +02:00
lieght
9b81ee1e65 Added ability to remove a manager from VolumeTypes 2025-10-12 00:39:16 +02:00
lieght
a4b0f890da Merge remote-tracking branch 'origin/166-audiomanager-opslitsen-om-single-responsibility-principle-te-volgen' into 166-audiomanager-opslitsen-om-single-responsibility-principle-te-volgen 2025-10-12 00:37:21 +02:00
lieght
a766b85a75 Fixed AudioVolumemanager, all volumes calculations are now made in VolumeTypes enum 2025-10-12 00:37:02 +02:00
Stef
34ccddaea5 Hotfix for loading clip volume issue (#174) 2025-10-12 00:04:59 +02:00
lieght
73a2fe3da2 Unit tests for MusicManager.java 2025-10-11 23:08:28 +02:00
Stef
d958b9730a 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.
2025-10-11 23:00:06 +02:00
Bas de Jong
9749d3eee8 Added more flexible dependency injection to MusicManager for unittesting. Moved to event driven design for less complex code and lower runtime complexity. 2025-10-11 20:45:57 +02:00
Bas de Jong
1ecdb9a555 Made all of the updated classes more generic for better flexibility in unittesting 2025-10-11 19:31:55 +02:00
lieght
b101734fd7 Reworked to now use better defined generics and easier to use API. Added AudioResource to be used in changing volume 2025-10-11 06:09:13 +02:00
lieght
123ecc7d3a Working state. Split AudioManager into 3 different branches for easier testing and srp 2025-10-11 04:50:49 +02:00
Ticho Hidding
c1f7a093ef Moves flip dots. all tests pass. can play reversi local. 2025-10-11 00:51:46 +02:00
Ticho Hidding
5dda85248e legal moves now get highlighted in red 2025-10-09 15:27:58 +02:00
Ticho Hidding
5a3490af2e start to reversi logic 2025-10-08 17:27:50 +02:00
Bas de Jong
7f3d858320 AppSettings now also get loaded into the assetmanager 2025-10-08 00:14:40 +02:00
Bas de Jong
e9dfbbd150 Renamed asset folder to resource, made resourceLoader more robust. Completed some TODO's, formatting 2025-10-07 23:54:33 +02:00
Bas de Jong
72e322675e Fixed bugs and oversights 2025-10-07 22:39:47 +02:00
ramollia
e12e48b4fb fast server connection 2025-10-07 12:40:12 +02:00
61 changed files with 323 additions and 2514 deletions

View File

@@ -2,7 +2,7 @@
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,java.lang.foreign.Arena,ofAuto,java.lang.foreign.Arena,global,org.toop.framework.audio.AudioPlayer,play,java.util.Map,remove,java.util.concurrent.Executors,newSingleThreadScheduledExecutor|newFixedThreadPool|newSingleThreadExecutor" />
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,java.lang.foreign.Arena,ofAuto,java.lang.foreign.Arena,global,org.toop.framework.audio.AudioPlayer,play,java.util.Map,remove,java.util.concurrent.Executors,newSingleThreadScheduledExecutor" />
</inspection_tool>
<inspection_tool class="WriteOnlyObject" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>

View File

@@ -4,6 +4,50 @@ import org.toop.app.App;
public final class Main {
static void main(String[] args) {
App.run(args);
App.run(args);
// testMCTS(10);
}
// Voor onderzoek
// private static void testMCTS(int games) {
// var random = new ArtificialPlayer<>(new RandomAI<BitboardReversi>(), "Random AI");
// var v1 = new ArtificialPlayer<>(new MCTSAI<BitboardTicTacToe>(10), "MCTS V1 AI");
// var v2 = new ArtificialPlayer<>(new MCTSAI2<BitboardTicTacToe>(10), "MCTS V2 AI");
// var v2_2 = new ArtificialPlayer<>(new MCTSAI2<BitboardTicTacToe>(100), "MCTS V2_2 AI");
// var v3 = new ArtificialPlayer<>(new MCTSAI3<BitboardTicTacToe>(10), "MCTS V3 AI");
// testAI(games, new Player[]{ v1, v2 });
// // testAI(games, new Player[]{ v1, v3 });
// // testAI(games, new Player[]{ random, v3 });
// // testAI(games, new Player[]{ v2, v3 });
// testAI(games, new Player[]{ v2, v3 });
// // testAI(games, new Player[]{ v3, v2 });
// }
// private static void testAI(int games, Player<BitboardReversi>[] ais) {
// int wins = 0;
// int ties = 0;
// for (int i = 0; i < games; i++) {
// final BitboardReversi match = new BitboardReversi(ais);
// while (!match.isTerminal()) {
// final int currentAI = match.getCurrentTurn();
// final long move = ais[currentAI].getMove(match);
// match.play(move);
// }
// if (match.getWinner() < 0) {
// ties++;
// continue;
// }
// wins += match.getWinner() == 0? 1 : 0;
// }
// System.out.printf("Out of %d games, %s won %d -- tied %d -- lost %d, games against %s\n", games, ais[0].getName(), wins, ties, games - wins - ties, ais[1].getName());
// System.out.printf("Average win rate was: %.2f\n\n", wins / (float)games);
// }
}

View File

@@ -20,10 +20,10 @@ import org.toop.framework.networking.connection.clients.TournamentNetworkingClie
import org.toop.framework.networking.connection.events.NetworkEvents;
import org.toop.framework.networking.connection.types.NetworkingConnector;
import org.toop.framework.networking.server.gateway.NettyGatewayServer;
import org.toop.game.players.ai.mcts.MCTSAI3;
import org.toop.game.players.LocalPlayer;
import org.toop.game.players.ai.RandomAI;
import org.toop.local.AppContext;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
@@ -119,8 +119,7 @@ public final class Server {
return;
}
primary = new ServerView(user, this::sendChallenge, user, clientId);
primary = new ServerView(user, this::sendChallenge, clientId);
WidgetContainer.getCurrentView().transitionNextCustom(primary, "disconnect", this::disconnect);
a.unsubscribe("connecting");
@@ -162,8 +161,7 @@ public final class Server {
.listen(NetworkEvents.GameResultResponse.class, this::handleGameResult, false, "game-result")
.listen(NetworkEvents.GameMoveResponse.class, this::handleReceivedMove, false, "game-move")
.listen(NetworkEvents.YourTurnResponse.class, this::handleYourTurn, false, "your-turn")
.listen(NetworkEvents.ClosedConnection.class, this::closedConnection, false, "closed-connection")
.listen(NetworkEvents.TournamentResultResponse.class, this::handleTournamentResult, false, "tournament-result");
.listen(NetworkEvents.ClosedConnection.class, this::closedConnection, false, "closed-connection");
connectFlow = a;
}
@@ -200,22 +198,30 @@ public final class Server {
return;
}
final String startingPlayer = response.playerToMove();
final int userStartingTurn = startingPlayer.equalsIgnoreCase(user) ? 0 : 1;
final int opponentStartingTurn = 1 - userStartingTurn;
final int myTurn = response.playerToMove().equalsIgnoreCase(response.opponent()) ? 1 : 0;
final GameInformation information = new GameInformation(type);
information.players[userStartingTurn].name = user;
information.players[opponentStartingTurn].name = response.opponent();
Player[] players = new Player[2];
players[userStartingTurn] = new ArtificialPlayer(new MCTSAI3(1000, 8), user);
players[opponentStartingTurn] = new OnlinePlayer(response.opponent());
//information.players[0] = playerInformation;
information.players[0].name = user;
information.players[0].isHuman = true; // Make false and uncomment/comment code at lines HERE To make use of AI.
// information.players[0].computerDifficulty = 5; // HERE
// information.players[0].computerThinkTime = 1; // HERE
information.players[1].name = response.opponent();
switch (type) {
case TICTACTOE -> gameController = new TicTacToeBitController(players);
case REVERSI -> gameController = new ReversiBitController(players);
case TICTACTOE -> {
Player[] players = new Player[2];
players[Math.abs(myTurn-1)] = new OnlinePlayer(response.opponent());
//players[myTurn] = new LocalPlayer(user); // HERE
players[myTurn] = new ArtificialPlayer(new RandomAI(), user); // HERE
gameController = new TicTacToeBitController(players);
}
case REVERSI -> {
Player[] players = new Player[2];
players[Math.abs(myTurn-1)] = new OnlinePlayer(response.opponent());
//players[myTurn] = new LocalPlayer(user); // HERE
players[myTurn] = new ArtificialPlayer(new RandomAI(), user); // HERE
gameController = new ReversiBitController(players);}
default -> new ErrorPopup("Unsupported game type.");
}
@@ -243,13 +249,6 @@ public final class Server {
gameController.gameFinished(response);
}
private void handleTournamentResult(NetworkEvents.TournamentResultResponse response) {
IO.println(response.gameType());
IO.println(Arrays.toString(response.names()));
IO.println(Arrays.toString(response.scoreTypes()));
IO.println(Arrays.toString(response.scores().toArray()));
}
private void handleReceivedMove(NetworkEvents.GameMoveResponse response) {
if (gameController == null) {
return;
@@ -349,8 +348,7 @@ public final class Server {
private void gamesListFromServerHandler(NetworkEvents.GamelistResponse event) {
gameList.clear();
var gl = new java.util.ArrayList<>(List.of(event.gamelist()));
gl.sort(String::compareTo);
var gl = List.of(event.gamelist());
gameList.addAll(gl);
primary.updateGameList(gl);
}

View File

@@ -78,7 +78,6 @@ public abstract class BitGameCanvas implements GameCanvas {
}
canvas.setOnMouseClicked(event -> {
if (event.getButton() != MouseButton.PRIMARY) {
return;
}
@@ -94,6 +93,9 @@ public abstract class BitGameCanvas implements GameCanvas {
}
});
render();
}

View File

@@ -2,13 +2,9 @@ package org.toop.app.canvas;
import javafx.scene.paint.Color;
import org.toop.app.App;
import org.toop.framework.game.games.reversi.BitboardReversi;
import org.toop.framework.game.players.LocalPlayer;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
public class ReversiBitCanvas extends BitGameCanvas {
private TurnBasedGame gameCopy;
private int previousCell;
public ReversiBitCanvas() {
super(Color.GRAY, new Color(0f, 0.4f, 0.2f, 1f), (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3, 8, 8, 5, true);
canvas.setOnMouseMoved(event -> {
@@ -24,9 +20,6 @@ public class ReversiBitCanvas extends BitGameCanvas {
break;
}
}
if (hovered != null) {
checkHoverDots(hovered, cellId);
}
});
}
@@ -38,31 +31,9 @@ public class ReversiBitCanvas extends BitGameCanvas {
@Override
public void redraw(TurnBasedGame gameCopy) {
this.gameCopy = gameCopy;
clearAll();
long[] board = gameCopy.getBoard();
loopOverBoard(board[0], (i) -> drawDot(Color.WHITE, i));
loopOverBoard(board[1], (i) -> drawDot(Color.BLACK, i));
}
public void drawLegalDots(TurnBasedGame gameCopy){
long legal = gameCopy.getLegalMoves();
loopOverBoard(legal, (i) -> drawInnerDot(gameCopy.getCurrentTurn()==0?new Color(1f,1f,1f,0.65f) :new Color(0f,0f,0f,0.65f), i,false));
}
private void checkHoverDots(BitGameCanvas.Cell hovered, int cellId){
if (previousCell == cellId){
return;
}
long backflips = ((BitboardReversi)gameCopy).getFlips(1L << previousCell);
loopOverBoard(backflips, (i) -> drawInnerDot(gameCopy.getCurrentTurn()==1?Color.WHITE:Color.BLACK, i,true));
previousCell = cellId;
if (gameCopy.getPlayer(gameCopy.getCurrentTurn()) instanceof LocalPlayer) {
long legal = gameCopy.getLegalMoves();
if ((legal & (1L << cellId)) != 0) {
long flips = ((BitboardReversi) gameCopy).getFlips(1L << cellId);
loopOverBoard(flips, (i) -> drawInnerDot(gameCopy.getCurrentTurn() == 0 ? Color.WHITE : Color.BLACK, i, false));
}
}
}
}

View File

@@ -5,12 +5,10 @@ import javafx.geometry.Pos;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.app.canvas.GameCanvas;
import org.toop.app.canvas.ReversiBitCanvas;
import org.toop.app.widget.WidgetContainer;
import org.toop.app.widget.view.GameView;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.eventbus.GlobalEventBus;
import org.toop.framework.game.games.reversi.BitboardReversi;
import org.toop.framework.gameFramework.controller.GameController;
import org.toop.framework.gameFramework.model.game.threadBehaviour.SupportsOnlinePlay;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
@@ -18,7 +16,7 @@ import org.toop.framework.gameFramework.model.game.threadBehaviour.ThreadBehavio
import org.toop.framework.gameFramework.model.player.Player;
import org.toop.framework.gameFramework.view.GUIEvents;
import org.toop.framework.networking.connection.events.NetworkEvents;
import org.toop.framework.game.players.LocalPlayer;
import org.toop.game.players.LocalPlayer;
public class GenericGameController implements GameController {
protected final EventFlow eventFlow = new EventFlow();
@@ -37,16 +35,14 @@ public class GenericGameController implements GameController {
// TODO: Change gameType to automatically happen with either dependency injection or something else.
public GenericGameController(GameCanvas canvas, TurnBasedGame game, ThreadBehaviour gameThreadBehaviour, String gameType) {
logger.info("Creating: {}", this.getClass());
logger.info("Creating: " + this.getClass());
this.canvas = canvas;
this.game = game;
this.gameThreadBehaviour = gameThreadBehaviour;
// Tell thread how to send moves
this.gameThreadBehaviour.setOnSendMove(
(id, m) -> GlobalEventBus.get().post(new NetworkEvents.SendMove(id, (short)translateMove(m)))
);
this.gameThreadBehaviour.setOnSendMove((id, m) -> GlobalEventBus.get().post(new NetworkEvents.SendMove(id, (short)translateMove(m))));
// Tell thread how to update UI
this.gameThreadBehaviour.setOnUpdateUI(() -> Platform.runLater(this::updateUI));
@@ -57,37 +53,21 @@ public class GenericGameController implements GameController {
WidgetContainer.getCurrentView().transitionNext(gameView, true);
// Listen to updates
logger.info("Game controller started listening");
eventFlow
.listen(GUIEvents.GameEnded.class, this::onGameFinish, false)
.listen(GUIEvents.PlayerAttemptedMove.class, event -> {
logger.info("User attempting move {}", event.move());
logger.info("Current player's turn {}", getCurrentPlayer().getName());
logger.info("First player {}", game.getPlayer(0).getName());
logger.info("Username {}", getCurrentPlayer().getName());
logger.info("User is class {}, {}", getCurrentPlayer().getClass(), getCurrentPlayer() instanceof LocalPlayer);
if (getCurrentPlayer() instanceof LocalPlayer lp) {
try {
lp.setLastMove(event.move());
} catch (Exception e) {
IO.println(e);
}
}
}, false);
.listen(GUIEvents.PlayerAttemptedMove.class, event -> {if (getCurrentPlayer() instanceof LocalPlayer lp){lp.setLastMove(event.move());}}, false);
}
public void start(){
logger.info("Starting GameManager");
updateUI();
gameThreadBehaviour.start();
logger.info("GameManager started");
}
public void stop(){
logger.info("Stopping GameManager");
removeListeners();
gameThreadBehaviour.stop();
logger.info("GameManager stopped");
}
public Player getCurrentPlayer(){
@@ -118,7 +98,7 @@ public class GenericGameController implements GameController {
}
public Player getPlayer(int player){
if (player < 0 || player > game.getPlayerCount()-1){ // TODO: Make game turn player count
if (player < 0 || player >= 2){ // TODO: Make game turn player count
logger.error("Invalid player index");
throw new IllegalArgumentException("player out of range");
}
@@ -155,18 +135,6 @@ public class GenericGameController implements GameController {
@Override
public void updateUI() {
TurnBasedGame gameCopy = game.deepCopy();
canvas.redraw(gameCopy);
String gameType = game.getClass().getSimpleName().replace("Bitboard","");
gameView.nextPlayer(true, getCurrentPlayer().getName(), game.getPlayer(1-getCurrentPlayerIndex()).getName(),gameType);
if (gameType.equals("Reversi")) {
BitboardReversi reversiGame = (BitboardReversi) game;
BitboardReversi.Score reversiScore = reversiGame.getScore();
gameView.setPlayer1Score(reversiScore.black());
gameView.setPlayer2Score(reversiScore.white());
if (getCurrentPlayer() instanceof LocalPlayer) {
((ReversiBitCanvas)canvas).drawLegalDots(gameCopy);
}
}
canvas.redraw(game.deepCopy());
}
}

View File

@@ -11,19 +11,13 @@ import org.toop.framework.game.players.OnlinePlayer;
import java.util.Arrays;
public class ReversiBitController extends GenericGameController {
private BitboardReversi game;
public ReversiBitController(Player[] players) {
BitboardReversi game = new BitboardReversi();
game.init(players);
ThreadBehaviour thread = Arrays.stream(players).anyMatch(e -> e instanceof OnlinePlayer) ?
new OnlineThreadBehaviour(game) : new LocalThreadBehaviour(game);
super(new ReversiBitCanvas(), game, thread, "Reversi");
}
public BitboardReversi.Score getScore() {
return game.getScore();
}
}

View File

@@ -7,6 +7,7 @@ import org.toop.framework.game.gameThreads.LocalThreadBehaviour;
import org.toop.framework.game.gameThreads.OnlineThreadBehaviour;
import org.toop.framework.game.games.tictactoe.BitboardTicTacToe;
import org.toop.framework.game.players.OnlinePlayer;
import org.toop.framework.networking.server.OnlineGame;
import java.util.Arrays;
@@ -18,6 +19,6 @@ public class TicTacToeBitController extends GenericGameController {
ThreadBehaviour thread = Arrays.stream(players).anyMatch(e -> e instanceof OnlinePlayer) ?
new OnlineThreadBehaviour(game) : new LocalThreadBehaviour(game);
super(new TicTacToeBitCanvas(), game, thread, "TicTacToe");
super(new TicTacToeBitCanvas(), game, thread , "TicTacToe");
}
}

View File

@@ -4,7 +4,6 @@ import org.toop.app.widget.complex.ConfirmWidget;
import org.toop.app.widget.complex.PopupWidget;
import javafx.geometry.Pos;
import org.toop.framework.game.games.reversi.BitboardReversi;
public final class GameOverPopup extends PopupWidget {
public GameOverPopup(boolean winOrTie, String winner) {
@@ -16,6 +15,7 @@ public final class GameOverPopup extends PopupWidget {
else{
confirmWidget.setMessage("It was a tie!");
}
confirmWidget.addButton("ok", this::hide);
add(Pos.CENTER, confirmWidget);

View File

@@ -6,8 +6,6 @@ import javafx.scene.text.Font;
import org.toop.app.widget.Primitive;
import org.toop.app.widget.complex.ViewWidget;
import org.toop.app.widget.popup.GameOverPopup;
import java.util.Objects;
import java.util.function.Consumer;
import javafx.application.Platform;
import javafx.geometry.Pos;
@@ -26,8 +24,6 @@ public final class GameView extends ViewWidget {
private final Text player2Header;
private Circle player1Icon;
private Circle player2Icon;
private final Text player1Score;
private final Text player2Score;
private final Button forfeitButton;
private final Button exitButton;
private final TextField chatInput;
@@ -42,8 +38,6 @@ public final class GameView extends ViewWidget {
player2Header = Primitive.header("");
player1Icon = new Circle();
player2Icon = new Circle();
player1Score = Primitive.header("");
player2Score = Primitive.header("");
if (onForfeit != null) {
forfeitButton = Primitive.button("forfeit", () -> onForfeit.run(), false);
@@ -100,7 +94,7 @@ public final class GameView extends ViewWidget {
}
}
public void nextPlayer(boolean isMe, String currentPlayer, String nextPlayer, String GameType) {
public void nextPlayer(boolean isMe, String currentPlayer, String currentMove, String nextPlayer, char GameType) {
Platform.runLater(() -> {
if (!(hasSet)) {
playerHeader.setText(currentPlayer + " vs. " + nextPlayer);
@@ -118,8 +112,8 @@ public final class GameView extends ViewWidget {
new GameOverPopup(iWon, winner).show(Pos.CENTER);
}
private void setPlayerHeaders(boolean isMe, String currentPlayer, String nextPlayer, String GameType) {
if (Objects.equals(GameType, "TicTacToe")) {
private void setPlayerHeaders(boolean isMe, String currentPlayer, String nextPlayer, char GameType) {
if (GameType == 'T') {
if (isMe) {
player1Header.setText("X: " + currentPlayer);
player2Header.setText("O: " + nextPlayer);
@@ -130,7 +124,7 @@ public final class GameView extends ViewWidget {
}
setPlayerInfoTTT();
}
else if (Objects.equals(GameType, "Reversi")) {
else if (GameType == 'R') {
if (isMe) {
player1Header.setText(currentPlayer);
player2Header.setText(nextPlayer);
@@ -157,16 +151,14 @@ public final class GameView extends ViewWidget {
private void setPlayerInfoReversi() {
var player1box = Primitive.hbox(
player1Icon,
player1Header,
player1Score
player1Header
);
player1box.getStyleClass().add("hboxspacing");
var player2box = Primitive.hbox(
player2Icon,
player2Header,
player2Score
player2Header
);
player2box.getStyleClass().add("hboxspacing");
@@ -180,16 +172,8 @@ public final class GameView extends ViewWidget {
player1Icon.setRadius(player1Header.fontProperty().map(Font::getSize).getValue());
player2Icon.setRadius(player2Header.fontProperty().map(Font::getSize).getValue());
player1Icon.setFill(Color.WHITE);
player2Icon.setFill(Color.BLACK);
player1Icon.setFill(Color.BLACK);
player2Icon.setFill(Color.WHITE);
add(Pos.TOP_RIGHT, playerInfo);
}
public void setPlayer1Score(int score) {
player1Score.setText("(" + Integer.toString(score) + ")");
}
public void setPlayer2Score(int score) {
player2Score.setText("(" + Integer.toString(score) + ")");
}
}

View File

@@ -4,7 +4,6 @@ import javafx.application.Platform;
import org.toop.app.GameInformation;
import org.toop.app.gameControllers.ReversiBitController;
import org.toop.app.gameControllers.TicTacToeBitController;
import org.toop.framework.game.players.LocalPlayer;
import org.toop.framework.gameFramework.controller.GameController;
import org.toop.framework.gameFramework.model.player.Player;
import org.toop.framework.game.players.ArtificialPlayer;
@@ -13,10 +12,11 @@ import org.toop.app.widget.complex.PlayerInfoWidget;
import org.toop.app.widget.complex.ViewWidget;
import org.toop.app.widget.popup.ErrorPopup;
import org.toop.app.widget.tutorial.*;
import org.toop.game.players.LocalPlayer;
import org.toop.game.players.ai.MCTSAI;
import org.toop.game.players.ai.MCTSAI2;
import org.toop.game.players.ai.MCTSAI3;
import org.toop.game.players.ai.MiniMaxAI;
import org.toop.game.players.ai.mcts.MCTSAI1;
import org.toop.game.players.ai.mcts.MCTSAI3;
import org.toop.game.players.ai.mcts.MCTSAI4;
import org.toop.local.AppContext;
import javafx.geometry.Pos;
@@ -54,7 +54,7 @@ public class LocalMultiplayerView extends ViewWidget {
if (information.players[0].isHuman) {
players[0] = new LocalPlayer(information.players[0].name);
} else {
players[0] = new ArtificialPlayer(new MCTSAI1(100), "MCTS AI");
players[0] = new ArtificialPlayer(new MCTSAI(100), "MCTS AI");
}
if (information.players[1].isHuman) {
players[1] = new LocalPlayer(information.players[1].name);
@@ -82,13 +82,13 @@ public class LocalMultiplayerView extends ViewWidget {
if (information.players[0].isHuman) {
players[0] = new LocalPlayer(information.players[0].name);
} else {
// players[0] = new ArtificialPlayer(new RandomAI(), "Random AI");
players[0] = new ArtificialPlayer(new MCTSAI1(100), "MCTS V1 AI");
// players[0] = new ArtificialPlayer(new RandomAI<BitboardReversi>(), "Random AI");
players[0] = new ArtificialPlayer(new MCTSAI3(50), "MCTS V3 AI");
}
if (information.players[1].isHuman) {
players[1] = new LocalPlayer(information.players[1].name);
} else {
players[1] = new ArtificialPlayer(new MCTSAI4(100, 8), "MCTS V4 AI");
players[1] = new ArtificialPlayer(new MCTSAI2(50), "MCTS V2 AI");
}
if (AppSettings.getSettings().getTutorialFlag() && AppSettings.getSettings().getFirstReversi()) {
new ShowEnableTutorialWidget(

View File

@@ -6,8 +6,6 @@ import javafx.scene.control.ComboBox;
import org.toop.app.widget.Primitive;
import org.toop.app.widget.complex.ViewWidget;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
@@ -17,7 +15,6 @@ import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.eventbus.GlobalEventBus;
import org.toop.framework.networking.connection.events.NetworkEvents;
public final class ServerView extends ViewWidget {
@@ -25,65 +22,46 @@ public final class ServerView extends ViewWidget {
private final Consumer<String> onPlayerClicked;
private final long clientId;
private final ComboBox<String> gameListSub;
private final ComboBox<String> gameListTour;
private final ComboBox<String> gameList;
private final ListView<Button> listView;
private Button subscribeButton;
public ServerView(String user, Consumer<String> onPlayerClicked, String userName, long clientId) {
public ServerView(String user, Consumer<String> onPlayerClicked, long clientId) {
this.user = user;
this.onPlayerClicked = onPlayerClicked;
this.clientId = clientId;
this.gameListSub = new ComboBox<>();
this.gameListTour = new ComboBox<>();
this.gameList = new ComboBox<>();
this.listView = new ListView<>();
setupLayout(userName);
setupLayout();
}
private void setupLayout(String userName) {
private void setupLayout() {
var playerHeader = Primitive.header(user, false);
if (userName.equals("host")) { // TODO is fragile
var tournamentButton = Primitive.hbox(
gameListTour,
Primitive.button(
"tournament",
() -> GlobalEventBus.get().post(new NetworkEvents.SendCommand(clientId, "tournament", "start", gameListTour.getValue())),
false,
false
)
);
subscribeButton = Primitive.button(
"subscribe",
() -> new EventFlow().addPostEvent(new NetworkEvents.SendSubscribe(clientId, gameList.getValue())).postEvent(),
false,
true
); // TODO localize
add(Pos.BOTTOM_CENTER, tournamentButton);
} else {
subscribeButton = Primitive.button(
"subscribe",
() -> new EventFlow().addPostEvent(new NetworkEvents.SendSubscribe(clientId, gameListSub.getValue())).postEvent(),
false,
true
); // TODO localize
var subscribe = Primitive.hbox(gameList, subscribeButton);
var subscribe = Primitive.hbox(gameListSub, subscribeButton);
var playerListSection = Primitive.vbox(
playerHeader,
Primitive.separator(),
subscribe,
listView
);
var playerListSection = Primitive.vbox(
playerHeader,
Primitive.separator(),
subscribe,
listView
);
add(Pos.CENTER, playerListSection);
add(Pos.CENTER, playerListSection);
var disconnectButton = Primitive.button(
"disconnect", () -> transitionPrevious(), false);
var disconnectButton = Primitive.button(
"disconnect",
this::transitionPrevious,
false
);
add(Pos.BOTTOM_LEFT, Primitive.vbox(disconnectButton));
}
add(Pos.BOTTOM_LEFT, Primitive.vbox(disconnectButton));
}
public void update(List<String> players) {
@@ -99,13 +77,9 @@ public final class ServerView extends ViewWidget {
public void updateGameList(List<String> games) {
Platform.runLater(() -> {
gameListSub.getItems().clear();
gameListSub.setItems(FXCollections.observableArrayList(games));
gameListSub.getSelectionModel().select(0);
gameListTour.getItems().clear();
gameListTour.setItems(FXCollections.observableArrayList(games));
gameListTour.getSelectionModel().select(0);
gameList.getItems().clear();
gameList.setItems(FXCollections.observableArrayList(games));
gameList.getSelectionModel().select(0);
});
}

View File

@@ -415,9 +415,11 @@ public class EventFlow {
/**
* Posts the event added through {@link #addPostEvent} asynchronously.
*
* @deprecated use {@link #postEvent()} instead.
*/
@Deprecated
public EventFlow asyncPostEvent() {
GlobalEventBus.get().post(this.event);
eventBus.post(this.event);
return this;
}

View File

@@ -1,20 +1,15 @@
package org.toop.framework.eventbus;
import org.apache.logging.log4j.LogManager;
import org.toop.framework.eventbus.bus.AsyncEventBus;
import org.toop.framework.eventbus.bus.DefaultEventBus;
import org.toop.framework.eventbus.bus.DisruptorEventBus;
import org.toop.framework.eventbus.bus.EventBus;
import org.toop.framework.eventbus.events.EventType;
import org.toop.framework.eventbus.store.DefaultSubscriberStore;
import org.toop.framework.eventbus.subscriber.Subscriber;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class GlobalEventBus implements AsyncEventBus {
private static final AsyncEventBus INSTANCE = new DefaultEventBus(
LogManager.getLogger(DefaultEventBus.class),
public class GlobalEventBus implements EventBus {
private static final EventBus INSTANCE = new DisruptorEventBus(
LogManager.getLogger(DisruptorEventBus.class),
new DefaultSubscriberStore()
);
@@ -39,11 +34,6 @@ public class GlobalEventBus implements AsyncEventBus {
INSTANCE.post(event);
}
@Override
public <T extends EventType> void asyncPost(T event) {
INSTANCE.asyncPost(event);
}
@Override
public void shutdown() {
INSTANCE.shutdown();
@@ -53,5 +43,4 @@ public class GlobalEventBus implements AsyncEventBus {
public void reset() {
INSTANCE.reset();
}
}

View File

@@ -1,7 +0,0 @@
package org.toop.framework.eventbus.bus;
import org.toop.framework.eventbus.events.EventType;
public interface AsyncEventBus extends EventBus {
<T extends EventType> void asyncPost(T event);
}

View File

@@ -5,16 +5,12 @@ import org.toop.framework.eventbus.events.EventType;
import org.toop.framework.eventbus.store.SubscriberStore;
import org.toop.framework.eventbus.subscriber.Subscriber;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
public class DefaultEventBus implements AsyncEventBus {
public class DefaultEventBus implements EventBus {
private final Logger logger;
private final SubscriberStore eventsHolder;
private final ExecutorService asyncExecutor = Executors.newCachedThreadPool();
public DefaultEventBus(Logger logger, SubscriberStore eventsHolder) {
this.logger = logger;
this.eventsHolder = eventsHolder;
@@ -40,16 +36,11 @@ public class DefaultEventBus implements AsyncEventBus {
Class<T> eventClass = (Class<T>) subscriber.event();
Consumer<EventType> action = (Consumer<EventType>) subscriber.handler();
action.accept(eventClass.cast(event));
action.accept((EventType) eventClass.cast(event));
}
}
}
@Override
public <T extends EventType> void asyncPost(T event) {
asyncExecutor.submit(() -> post(event));
}
@Override
public void shutdown() {
eventsHolder.reset();
@@ -59,5 +50,4 @@ public class DefaultEventBus implements AsyncEventBus {
public void reset() {
eventsHolder.reset();
}
}

View File

@@ -17,8 +17,8 @@ public abstract class BitboardGame implements TurnBasedGame {
private Player[] players;
// long is 64 bits. Every game has a limit of 64 cells maximum.
protected final long[] playerBitboard;
protected int currentTurn = 0;
private final long[] playerBitboard;
private int currentTurn = 0;
private final int playerCount;
public BitboardGame(int columnSize, int rowSize, int playerCount) {
@@ -74,8 +74,6 @@ public abstract class BitboardGame implements TurnBasedGame {
return playerBitboard.length;
}
public int getAmountOfTurns() { return currentTurn; }
public int getCurrentTurn() {
return getCurrentPlayerIndex();
}

View File

@@ -8,9 +8,6 @@ import org.toop.framework.gameFramework.model.game.threadBehaviour.SupportsOnlin
import org.toop.framework.gameFramework.model.player.Player;
import org.toop.framework.game.players.OnlinePlayer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Handles online multiplayer game logic.
* <p>
@@ -18,9 +15,6 @@ import java.util.concurrent.Executors;
* for the local player while receiving moves from other players.
*/
public class OnlineThreadBehaviour extends AbstractThreadBehaviour implements SupportsOnlinePlay {
private ExecutorService moveExecutor = Executors.newSingleThreadExecutor();
/**
* Creates behaviour and sets the first local player
* (non-online player) from the given array.
@@ -57,30 +51,8 @@ public class OnlineThreadBehaviour extends AbstractThreadBehaviour implements Su
*/
@Override
public void onYourTurn(long clientId) {
logger.info("Yourturn");
if (!isRunning.get()) {
logger.warn("Game is not running!");
return;
}
TurnBasedGame gameCopy = game.deepCopy();
if (gameCopy == null) {
logger.error("Could not deep copy game");
return;
}
logger.info("Successfully collected game copy");
Player player = gameCopy.getPlayer(game.getCurrentTurn());
if (player == null) {
logger.error("Could not find current turn's player");
return;
}
logger.info("Successfully collected current turn's player");
long move = player.getMove(gameCopy);
logger.info("Move set: {}", move);
logger.info("Completed onYourTurn");
if (!isRunning.get()) return;
long move = game.getPlayer(game.getCurrentTurn()).getMove(game.deepCopy());
sendMove(clientId, move);
}

View File

@@ -1,39 +1,31 @@
package org.toop.framework.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.TurnBasedGame;
import org.toop.framework.gameFramework.model.game.threadBehaviour.AbstractThreadBehaviour;
import org.toop.framework.gameFramework.model.player.Player;
import org.toop.framework.gameFramework.view.GUIEvents;
import org.toop.framework.utils.ImmutablePair;
import org.toop.framework.utils.Pair;
import java.time.Duration;
import java.util.concurrent.*;
import java.util.function.Consumer;
import static org.toop.framework.gameFramework.GameState.TURN_SKIPPED;
import static org.toop.framework.gameFramework.GameState.WIN;
public class ServerThreadBehaviour extends AbstractThreadBehaviour implements Runnable {
private final Consumer<ImmutablePair<String, Integer>> onPlayerMove;
private final Consumer<Pair<GameState, Integer>> onGameEnd;
private final ExecutorService moveExecutor = Executors.newSingleThreadExecutor();
private final Duration timeOut;
/**
* Creates a new base behaviour for the specified game.
*
* @param game the turn-based game to control
*/
public ServerThreadBehaviour(
TurnBasedGame game,
Consumer<ImmutablePair<String,
Integer>> onPlayerMove,
Consumer<Pair<GameState, Integer>> onGameEnd,
Duration timeOut
) {
public ServerThreadBehaviour(TurnBasedGame game, Consumer<ImmutablePair<String, Integer>> onPlayerMove, Consumer<Pair<GameState, Integer>> onGameEnd) {
this.onPlayerMove = onPlayerMove;
this.onGameEnd = onGameEnd;
this.timeOut = timeOut;
super(game);
}
@@ -67,42 +59,23 @@ public class ServerThreadBehaviour extends AbstractThreadBehaviour implements Ru
public void run() {
while (isRunning.get()) {
Player currentPlayer = game.getPlayer(game.getCurrentTurn());
long move = currentPlayer.getMove(game.deepCopy());
PlayResult result = game.play(move);
Future<Long> move = moveExecutor.submit(() -> currentPlayer.getMove(game.deepCopy()));
GameState state = result.state();
notifyPlayerMove(new ImmutablePair<>(currentPlayer.getName(), Long.numberOfTrailingZeros(move)));
PlayResult result;
try {
long moveResult = move.get(timeOut.toMillis(), TimeUnit.MILLISECONDS);
result = game.play(moveResult);
GameState state = result.state();
notifyPlayerMove(new ImmutablePair<>(currentPlayer.getName(), Long.numberOfTrailingZeros(moveResult)));
switch (state) {
case WIN, DRAW -> {
isRunning.set(false);
moveExecutor.shutdown();
notifyGameEnd(new ImmutablePair<>(state, game.getWinner()));
}
case NORMAL, TURN_SKIPPED -> { /* continue normally */ }
default -> {
logger.error("Unexpected state {}", state);
isRunning.set(false);
moveExecutor.shutdown();
throw new RuntimeException("Unknown state: " + state);
}
switch (state) {
case WIN, DRAW -> {
isRunning.set(false);
notifyGameEnd(new ImmutablePair<>(state, game.getWinner()));
}
case NORMAL, TURN_SKIPPED -> { /* continue normally */ }
default -> {
logger.error("Unexpected state {}", state);
isRunning.set(false);
throw new RuntimeException("Unknown state: " + state);
}
} catch (InterruptedException | ExecutionException e) {
isRunning.set(false);
notifyGameEnd(new ImmutablePair<>(GameState.DRAW, 0));
moveExecutor.shutdown();
return;
} catch (TimeoutException e) {
isRunning.set(false);
notifyGameEnd(new ImmutablePair<>(GameState.WIN, 1+game.getWinner()%2));
moveExecutor.shutdown();
return;
}
}
}

View File

@@ -4,6 +4,7 @@ import org.toop.framework.game.BitboardGame;
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.framework.game.BitboardGame;
public class BitboardReversi extends BitboardGame {
@@ -320,58 +321,8 @@ public class BitboardReversi extends BitboardGame {
else if (blackCount > whiteCount){
return 0;
}
else {
else{
return 1;
}
}
@Override
public float rateMove(long move) {
final long corners = 0x8100000000000081L;
if ((move & corners) != 0L) {
return 0.4f;
}
final long xSquares = 0x0042000000004200L;
if ((move & xSquares) != 0) {
return -0.4f;
}
final long cSquares = 0x4281000000008142L;
if ((move & cSquares) != 0) {
return -0.1f;
}
return 0.0f;
}
@Override
public long heuristicMove(long legalMoves) {
long bestMove = 0L;
float bestMoveRate = Float.NEGATIVE_INFINITY;
while (legalMoves != 0L) {
final long move = legalMoves & -legalMoves;
final float moveRate = rateMove(move);
if (moveRate > bestMoveRate) {
bestMove = move;
bestMoveRate = moveRate;
}
legalMoves &= ~move;
}
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,7 +2,6 @@ package org.toop.framework.game.games.tictactoe;
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.Player;
import org.toop.framework.game.BitboardGame;
@@ -111,18 +110,4 @@ public class BitboardTicTacToe extends BitboardGame {
public BitboardTicTacToe deepCopy() {
return new BitboardTicTacToe(this);
}
@Override
public float rateMove(long move) {
return 0.0f;
}
@Override
public long heuristicMove(long legalMoves) {
return legalMoves;
}
@Override
public void setFrom(long player1, long player2, int turn) {
}
}

View File

@@ -14,9 +14,7 @@ import org.toop.framework.gameFramework.model.game.TurnBasedGame;
*/
public class ArtificialPlayer extends AbstractPlayer {
/**
* The AI instance used to calculate moves.
*/
/** The AI instance used to calculate moves. */
private final AI ai;
/**
@@ -59,8 +57,4 @@ public class ArtificialPlayer extends AbstractPlayer {
public ArtificialPlayer deepCopy() {
return new ArtificialPlayer(this);
}
public AI getAi() {
return ai;
}
}

View File

@@ -1,4 +1,4 @@
package org.toop.framework.game.players;
package org.toop.game.players;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.gameFramework.model.player.AbstractPlayer;
@@ -45,16 +45,11 @@ public class LocalPlayer extends AbstractPlayer {
long legalMoves = gameCopy.getLegalMoves();
long move;
try {
do {
move = getLastMove();
IO.println("GETTING MOVE");
} while ((legalMoves & move) == 0);
return move;
} catch (Exception e) {
IO.println(e);
}
return -1;
do {
move = getLastMove();
} while ((legalMoves & move) == 0);
return move;
}
/**

View File

@@ -29,8 +29,13 @@ public class ServerPlayer extends AbstractPlayer {
@Override
public long determineMove(TurnBasedGame game) {
lastMove = new CompletableFuture<>();
client.send("SVR GAME YOURTURN {TURNMESSAGE: \"<bericht voor deze beurt>\"}");
return lastMove.join();
System.out.println("Sending yourturn");
client.send("SVR GAME YOURTURN {TURNMESSAGE: \"<bericht voor deze beurt>\"}\n");
try {
return lastMove.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
return 0;
}
}
}

View File

@@ -13,9 +13,4 @@ public interface TurnBasedGame extends DeepCopyable<TurnBasedGame> {
PlayResult play(long move);
PlayResult getState();
boolean isTerminal();
float rateMove(long move);
long heuristicMove(long legalMoves);
void setFrom(long player1, long player2, int turn);
}

View File

@@ -65,9 +65,6 @@ public class NetworkEvents extends EventsBase {
public record GameResultResponse(long clientId, String condition)
implements GenericEvent {}
public record TournamentResultResponse(long clientId, String gameType, String[] names, String[] scoreTypes, List<Integer[]> scores)
implements GenericEvent {}
/** Indicates that a game move has been processed or received. */
public record GameMoveResponse(long clientId, String player, String move, String details)
implements GenericEvent {}
@@ -222,5 +219,4 @@ public class NetworkEvents extends EventsBase {
/** Response to a {@link ChangeAddress} event, carrying the success result. */
public record ChangeAddressResponse(boolean successful, long identifier)
implements ResponseToUniqueEvent {}
}

View File

@@ -3,9 +3,6 @@ package org.toop.framework.networking.connection.handlers;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -97,9 +94,6 @@ public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter {
case "HELP":
helpHandler(recSrvRemoved);
return;
case "RESULTS":
resultsHandler(recSrvRemoved);
return;
default:
// return
}
@@ -109,66 +103,6 @@ public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter {
}
}
private static String extract(String input, String key) {
Pattern p = Pattern.compile(
key + "\\s*:\\s*(\\[[^]]*]|\"[^\"]*\")",
Pattern.CASE_INSENSITIVE
);
Matcher m = p.matcher(input);
return m.find() ? m.group(1) : null;
}
private void resultsHandler(String rec) {
// TODO all of this
IO.println(rec);
String gameTypeRaw = extract(rec, "GAMETYPE");
String usersRaw = extract(rec, "USERS");
String scoreTypesRaw = extract(rec, "SCORETYPES");
String scoresRaw = extract(rec, "SCORES");
if (usersRaw == null) return;
String[] users;
if (usersRaw.length() > 2) {
users = Arrays.stream(usersRaw.substring(1, usersRaw.length() - 1).split(","))
.map(s -> s.trim().replace("\"", ""))
.toArray(String[]::new);
} else {
users = new String[]{};
}
String[] scoreTypes;
if (scoreTypesRaw.length() > 2) {
scoreTypes = Arrays.stream(scoreTypesRaw.substring(1, usersRaw.length() - 1).split(","))
.map(s -> s.trim().replace("\"", ""))
.toArray(String[]::new);
} else {
scoreTypes = new String[]{};
}
if (scoresRaw == null) return;
if (scoresRaw.length() > 2) {
List<Integer[]> scores = Arrays.stream(
scoresRaw.substring(1, scoresRaw.length() - 1) // remove outer []
.split("\\],\\[")
)
.map(part -> part.replace("[", "").replace("]", ""))
.map(part -> Arrays.stream(part.split(","))
.map(String::trim)
.map(Integer::parseInt)
.toArray(Integer[]::new)
)
.toList();
eventBus.post(new NetworkEvents.TournamentResultResponse(this.connectionId, gameTypeRaw, users, scoreTypes, scores));
} else {
eventBus.post(new NetworkEvents.TournamentResultResponse(this.connectionId, gameTypeRaw, users, scoreTypes, new ArrayList<>()));
}
}
private void gameMoveHandler(String rec) {
String[] msg =
Pattern.compile(

View File

@@ -1,7 +0,0 @@
package org.toop.framework.networking.server;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import java.util.concurrent.CompletableFuture;
public record GameResultFuture(OnlineGame<TurnBasedGame> game, CompletableFuture<Integer> result) {}

View File

@@ -1,13 +1,9 @@
package org.toop.framework.networking.server;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface GameServer<GAMETYPE, CLIENT, CHALLENGEIDTYPE> {
GameResultFuture startGame(String gameType, Duration turnTime, CLIENT... clients);
void startGame(String gameType, CLIENT... clients);
void addClient(CLIENT client);
void removeClient(CLIENT client);

View File

@@ -1,10 +0,0 @@
package org.toop.framework.networking.server;
import org.toop.framework.networking.server.client.NettyClient;
import java.time.Duration;
@FunctionalInterface
public interface MatchExecutor {
GameResultFuture submit(String gameType, Duration turnTime, NettyClient... clients);
}

View File

@@ -5,65 +5,44 @@ import org.toop.framework.gameFramework.GameState;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.networking.server.client.NettyClient;
import java.time.Duration;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
public class OnlineTurnBasedGame implements OnlineGame<TurnBasedGame> {
private long id;
private NettyClient[] clients;
private NettyClient[] admins;
private TurnBasedGame game;
private ServerThreadBehaviour gameThread;
private final CompletableFuture<Integer> resultFuture;
public OnlineTurnBasedGame(NettyClient[] admins, TurnBasedGame game, CompletableFuture<Integer> resultFuture, Duration timeOut, NettyClient... clients) {
public OnlineTurnBasedGame(TurnBasedGame game, NettyClient... clients) {
this.game = game;
this.gameThread = new ServerThreadBehaviour(
game,
(pair) -> notifyMoveMade(pair.getLeft(), pair.getRight()),
(pair) -> notifyGameEnd(pair.getLeft(), pair.getRight()),
timeOut
(pair) -> notifyGameEnd(pair.getLeft(), pair.getRight())
);
this.resultFuture = resultFuture;
this.clients = clients;
this.admins = admins;
}
private void notifyMoveMade(String speler, int move){
for (NettyClient admin : admins) {
admin.send(String.format("SVR GAME MOVE {PLAYER: \"%s\", MOVE: \"%s\", DETAILS: \"<reactie spel op zet>\"}", speler, move));
}
for (NettyClient client : clients) {
client.send(String.format("SVR GAME MOVE {PLAYER: \"%s\", MOVE: \"%s\", DETAILS: \"<reactie spel op zet>\"}", speler, move));
client.send(String.format("SVR GAME MOVE {PLAYER: \"%s\", MOVE: \"%s\", DETAILS: \"<reactie spel op zet>\"}\n", speler, move));
}
}
private void notifyGameEnd(GameState state, int winner) {
if (state == GameState.DRAW) {
Arrays.stream(admins).forEach(a -> a.send("SVR GAME END"));
private void notifyGameEnd(GameState state, int winner){
if (state == GameState.DRAW){
for (NettyClient client : clients) {
client.send("SVR GAME DRAW {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"<comment>\"}");
client.send(String.format("SVR GAME DRAW {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"<comment>\"}\n"));
}
} else {
Arrays.stream(admins).forEach(a -> a.send("SVR GAME END"));
clients[winner].send("SVR GAME WIN {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"<comment>\"}");
clients[(winner+1)%2].send("SVR GAME LOSS {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"<comment>\"}");
}
else{
clients[winner].send(String.format("SVR GAME WIN {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"<comment>\"}\n"));
clients[(winner + 1)%2].send(String.format("SVR GAME LOSS {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"<comment>\"}\n"));
}
// Remove game from clients
for (NettyClient client : clients) {
admins = null;
// Remove game fromt clients
for(NettyClient client : clients) {
client.clearGame();
}
if (resultFuture != null) {
if (state.equals(GameState.DRAW)) resultFuture.complete(-1); // Return -1 if draw
else resultFuture.complete(winner); // Return number for winner's index
}
}
@Override
@@ -82,7 +61,7 @@ public class OnlineTurnBasedGame implements OnlineGame<TurnBasedGame> {
}
@Override
public void start() {
public void start(){
this.gameThread.start();
}
}

View File

@@ -1,6 +1,5 @@
package org.toop.framework.networking.server;
import com.google.gson.Gson;
import org.toop.framework.game.players.ServerPlayer;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.networking.server.challenges.gamechallenge.GameChallenge;
@@ -10,9 +9,6 @@ import org.toop.framework.networking.server.stores.ClientStore;
import org.toop.framework.networking.server.stores.SubscriptionStore;
import org.toop.framework.networking.server.stores.TurnBasedGameStore;
import org.toop.framework.networking.server.stores.TurnBasedGameTypeStore;
import org.toop.framework.networking.server.tournaments.*;
import org.toop.framework.networking.server.tournaments.matchmakers.DoubleRoundRobinMatchMaker;
import org.toop.framework.networking.server.tournaments.scoresystems.*;
import org.toop.framework.utils.ImmutablePair;
import java.util.*;
@@ -31,14 +27,13 @@ public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
final private Duration challengeDuration;
final private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final List<NettyClient> admins = new ArrayList<>();
public Server(
Duration challengeDuration,
TurnBasedGameTypeStore turnBasedGameTypeStore,
ClientStore<Long, NettyClient> clientStore,
TurnBasedGameStore gameStore,
SubscriptionStore subStore
) {
this.gameTypesStore = turnBasedGameTypeStore;
this.challengeDuration = challengeDuration;
@@ -51,13 +46,11 @@ public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
@Override
public void addClient(NettyClient client) {
if (admins.isEmpty()) admins.addLast(client);
clientStore.add(client);
}
@Override
public void removeClient(NettyClient client) {
admins.remove(client);
clientStore.remove(client.id());
}
@@ -110,7 +103,7 @@ public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
public void acceptChallenge(Long challengeId) {
for (var challenge : gameChallenges) {
if (challenge.id() == challengeId) {
startGame(challenge.acceptChallenge(), Duration.ofSeconds(10), challenge.getUsers());
startGame(challenge.acceptChallenge(), challenge.getUsers());
break;
}
}
@@ -132,24 +125,12 @@ public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
}
@Override
public GameResultFuture startGame(String gameType, Duration turnTime, NettyClient... clients) {
if (!gameTypesStore.all().containsKey(gameType)) return null;
public void startGame(String gameType, NettyClient... clients) {
if (!gameTypesStore.all().containsKey(gameType)) return;
try {
ServerPlayer[] players = new ServerPlayer[clients.length];
var gameResult = new CompletableFuture<Integer>();
var game = new OnlineTurnBasedGame(
getAdmins().toArray(NettyClient[]::new),
gameTypesStore.create(gameType),
gameResult,
turnTime,
clients
);
var grfReturn = new GameResultFuture(game, gameResult);
var game = new OnlineTurnBasedGame(gameTypesStore.create(gameType), clients);
for (int i = 0; i < clients.length; i++) {
players[i] = new ServerPlayer(clients[i]);
@@ -167,14 +148,11 @@ public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
clients[0].name(),
gameType,
clients[0].name()));
game.start();
return grfReturn;
} catch (Exception e) {
IO.println("ERROR: Failed to start OnlineTurnBasedGame");
e.printStackTrace();
}
return null;
}
@Override
@@ -182,10 +160,6 @@ public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
return clientStore.all().stream().toList();
}
public List<NettyClient> getAdmins() {
return new ArrayList<>(admins); // Clone so the list can't be edited.
}
@Override
public void shutdown() {
scheduler.shutdown();
@@ -244,6 +218,7 @@ public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
}
if (userInGame) { continue; }
//
int first = Math.max(left, right);
int second = Math.min(left, right);
@@ -251,7 +226,7 @@ public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
userNames.remove(first);
userNames.remove(second);
startGame(key, Duration.ofSeconds(10), getUser(userLeft), getUser(userRight));
startGame(key, getUser(userLeft), getUser(userRight));
}
}
}
@@ -285,74 +260,4 @@ public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
return true;
}
public void startTournament(String gameType, NettyClient requestor, boolean shuffle) {
if (!admins.contains(requestor)) {
requestor.send("ERR you do not have the privileges to start a tournament");
return;
}
var tournamentUsers = new ArrayList<>(onlineUsers());
tournamentUsers.removeIf(admins::contains);
Tournament tournament = new Tournament.Builder()
.matchExecutor(this::startGame)
.tournamentRunner(new AsyncTournamentRunner())
.matchMaker(new DoubleRoundRobinMatchMaker())
.addScoreSystem(new MatchCountScoreSystem())
.addScoreSystem(new WinCountScoreSystem())
.addScoreSystem(new DrawCountScoreSystem())
.addScoreSystem(new LoseCountScoreSystem())
.resultBroadcaster(this::endTournament)
.turnTimeout(Duration.ofSeconds(10))
.addPlayers(tournamentUsers.toArray(NettyClient[]::new))
.addAdmins(admins.toArray(NettyClient[]::new))
.build();
new Thread(() -> tournament.run(gameType)).start();
}
public void endTournament(List<IntegerScoreSystem> systems) {
if (systems.isEmpty()) return;
Map<String, List<ImmutablePair<String, Integer>>> combined = new HashMap<>();
for (var system : systems) {
for (var player : system.getScore().keySet()) {
combined.putIfAbsent(player.name(), new ArrayList<>());
combined.get(player.name()).addLast(new ImmutablePair<>(system.scoreName(), system.getScore().get(player)));
}
}
List<String> names = new ArrayList<>();
List<String> systemNames = new ArrayList<>();
List<List<Integer>> scores = new ArrayList<>();
for (var player : combined.entrySet()) {
names.addLast(player.getKey());
scores.addLast(new ArrayList<>());
for (var system : player.getValue()) {
if (!systemNames.contains(system.getLeft())) systemNames.addLast(system.getLeft());
scores.getLast().addLast(system.getRight());
}
}
Gson gson = new Gson();
String namesJson = gson.toJson(names);
String systemNamesJson = gson.toJson(systemNames);
String scoresJson = gson.toJson(scores);
String msg = String.format(
"SVR RESULTS {GAMETYPE: \"%s\", USERS: %s, SCORETYPES: %s, SCORES: %s, TOURNAMENT: 1}",
"none", // TODO gametype
namesJson,
systemNamesJson,
scoresJson
);
for (var user : onlineUsers()) {
user.send(msg);
}
}
}

View File

@@ -56,6 +56,7 @@ public class NettyClient implements Client<OnlineTurnBasedGame, ServerPlayer> {
@Override
public void send(String message) {
IO.println(message);
ctx.channel().writeAndFlush(message + "\r\n");
}

View File

@@ -11,6 +11,8 @@ import org.toop.framework.networking.server.Server;
import org.toop.framework.networking.server.client.Client;
import org.toop.framework.networking.server.parsing.Parser;
import java.util.Arrays;
public class NettyClientSession extends SimpleChannelInboundHandler<String> implements ClientSession<OnlineTurnBasedGame, ServerPlayer> {
private final NettyClient client;
@@ -39,9 +41,13 @@ public class NettyClientSession extends SimpleChannelInboundHandler<String> impl
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
IO.println(msg);
ParsedMessage p = Parser.parse(msg);
if (p == null) return;
IO.println(p.command() + " " + Arrays.toString(p.args()));
handler.handle(p);
}

View File

@@ -1,17 +1,18 @@
package org.toop.framework.networking.server.handlers;
import org.toop.framework.game.players.ServerPlayer;
import org.toop.framework.networking.server.OnlineTurnBasedGame;
import org.toop.framework.networking.server.Server;
import org.toop.framework.networking.server.client.Client;
import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.parsing.ParsedMessage;
import org.toop.framework.utils.Utils;
public class MessageHandler implements Handler<ParsedMessage> {
private final Server server;
private final NettyClient client;
private final Client<OnlineTurnBasedGame, ServerPlayer> client;
public MessageHandler(Server server, NettyClient client) {
public MessageHandler(Server server, Client<OnlineTurnBasedGame, ServerPlayer> client) {
this.server = server;
this.client = client;
}
@@ -27,7 +28,6 @@ public class MessageHandler implements Handler<ParsedMessage> {
case "challenge" -> handleChallenge(message, client);
case "message" -> handleMessage(message, client);
case "help" -> handleHelp(message, client);
case "tournament" -> handleTournament(message, client);
default -> client.send("ERROR Unknown command");
}
}
@@ -41,27 +41,27 @@ public class MessageHandler implements Handler<ParsedMessage> {
return true;
}
private void handleLogin(ParsedMessage p, NettyClient client) {
private void handleLogin(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
if (!hasArgs(p.args())) return;
client.setName(p.args()[0]);
}
private void handleSubscribe(ParsedMessage p, NettyClient client) {
private void handleSubscribe(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
if (!hasArgs(p.args())) return;
server.subscribeClient(client.name(), p.args()[0]);
}
private void handleHelp(ParsedMessage p, NettyClient client) {
private void handleHelp(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
// TODO
}
private void handleMessage(ParsedMessage p, NettyClient client) {
private void handleMessage(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
// TODO
}
private void handleGet(ParsedMessage p, NettyClient client) {
private void handleGet(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
if (!hasArgs(p.args())) return;
switch (p.args()[0]) {
@@ -73,14 +73,10 @@ public class MessageHandler implements Handler<ParsedMessage> {
var names = server.gameTypes().stream().iterator();
client.send("SVR GAMELIST " + Utils.returnQuotedString(names));
}
case "admins" -> {
var names = server.getAdmins().stream().map(Client::name).iterator();
client.send("SVR ADMINS " + Utils.returnQuotedString(names));
}
}
}
private void handleChallenge(ParsedMessage p, NettyClient client) {
private void handleChallenge(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
if (!hasArgs(p.args())) return;
if (p.args().length < 2) return;
@@ -105,21 +101,10 @@ public class MessageHandler implements Handler<ParsedMessage> {
server.challengeClient(client.name(), p.args()[0], p.args()[1]);
}
private void handleMove(ParsedMessage p, NettyClient client) {
private void handleMove(ParsedMessage p, Client<OnlineTurnBasedGame, ServerPlayer> client) {
if(!hasArgs(p.args())) return;
var player = client.player();
if (player == null) return;
// TODO check if not number
player.setMove(1L << Integer.parseInt(p.args()[0]));
}
private void handleTournament(ParsedMessage p, NettyClient client) {
if(!hasArgs(p.args())) return;
if (p.args()[0].equalsIgnoreCase("start") && p.args().length > 1) {
server.startTournament(p.args()[1], client, false); // TODO add shuffle to msg
}
client.player().setMove(1L << Integer.parseInt(p.args()[0]));
}
}

View File

@@ -1,86 +0,0 @@
package org.toop.framework.networking.server.tournaments;
import org.toop.framework.networking.server.GameResultFuture;
import org.toop.framework.networking.server.MatchExecutor;
import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.tournaments.matchmakers.MatchMaker;
import org.toop.framework.networking.server.tournaments.scoresystems.IntegerScoreSystem;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.*;
public class AsyncTournamentRunner implements TournamentRunner {
@Override
public void run(
MatchExecutor matchRunner,
MatchMaker matchMaker,
List<IntegerScoreSystem> scoreSystems,
ResultBroadcaster<IntegerScoreSystem> broadcaster,
Duration turnTime,
String gameType
) {
ExecutorService matchExecutor = Executors.newVirtualThreadPerTaskExecutor();
ExecutorService scoringExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
Queue<TournamentMatch> pendingMatches = new ConcurrentLinkedQueue<>();
matchMaker.forEach(pendingMatches::add);
Set<NettyClient> busyPlayers = ConcurrentHashMap.newKeySet();
List<CompletableFuture<Void>> runningMatches = new CopyOnWriteArrayList<>();
try {
while (!pendingMatches.isEmpty() || !runningMatches.isEmpty()) {
Iterator<TournamentMatch> it = pendingMatches.iterator();
while (it.hasNext()) {
TournamentMatch match = it.next();
NettyClient a = match.getClient0();
NettyClient b = match.getClient1();
// TODO game != null doesn't work here, fix later
if (busyPlayers.contains(a) || busyPlayers.contains(b)) {
continue;
}
busyPlayers.add(a);
busyPlayers.add(b);
it.remove();
CompletableFuture<Void> f =
CompletableFuture.runAsync(() -> {
try {
GameResultFuture game = matchRunner.submit(gameType, turnTime, a, b);
CompletableFuture.runAsync(
() -> scoreSystems.forEach(s -> s.result(match, game.result().join())),
scoringExecutor
).join();
} finally {
a.clearGame();
b.clearGame();
busyPlayers.remove(a);
busyPlayers.remove(b);
}
}, matchExecutor);
runningMatches.add(f);
f.whenComplete((_, _) -> runningMatches.remove(f));
}
Thread.sleep(10); // Safety
}
broadcaster.broadcast(scoreSystems);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
matchExecutor.shutdown();
}
}
}

View File

@@ -1,40 +0,0 @@
package org.toop.framework.networking.server.tournaments;
import org.toop.framework.networking.server.GameResultFuture;
import org.toop.framework.networking.server.MatchExecutor;
import org.toop.framework.networking.server.tournaments.matchmakers.MatchMaker;
import org.toop.framework.networking.server.tournaments.scoresystems.IntegerScoreSystem;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.*;
public class BasicTournamentRunner implements TournamentRunner {
@Override
public void run(
MatchExecutor matchExecutor,
MatchMaker matchMaker,
List<IntegerScoreSystem> scoreSystems,
ResultBroadcaster<IntegerScoreSystem> broadcaster,
Duration turnTime,
String gameType
) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
try {
threadPool.execute(() -> {
for (TournamentMatch match : matchMaker) {
// Play game and await the results
GameResultFuture game = matchExecutor.submit(gameType, turnTime, match.getClient0(), match.getClient1());
scoreSystems.forEach(e -> e.result(match, game.result().join()));
match.getClient0().clearGame();
match.getClient1().clearGame();
}
broadcaster.broadcast(scoreSystems);
});
} finally {
threadPool.shutdown();
}
}
}

View File

@@ -1,10 +0,0 @@
package org.toop.framework.networking.server.tournaments;
import org.toop.framework.networking.server.tournaments.scoresystems.ScoreSystem;
import java.util.List;
@FunctionalInterface
public interface ResultBroadcaster<T extends ScoreSystem<?, ?, ?>> {
void broadcast(List<T> scoreSystem);
}

View File

@@ -1,121 +0,0 @@
package org.toop.framework.networking.server.tournaments;
import org.toop.framework.networking.server.MatchExecutor;
import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.tournaments.matchmakers.MatchMaker;
import org.toop.framework.networking.server.tournaments.scoresystems.IntegerScoreSystem;
import org.toop.framework.networking.server.tournaments.shufflers.Shuffler;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class Tournament {
private final MatchExecutor matchExecutor;
private final List<IntegerScoreSystem> scoreSystems;
private final TournamentRunner tournamentRunner;
private final MatchMaker matchMaker;
private final ResultBroadcaster<IntegerScoreSystem> broadcaster;
private final NettyClient[] players;
private final Duration turnTime;
private final Shuffler shuffler;
private Tournament(Tournament.Builder builder) {
matchExecutor = builder.matchExecutor;
scoreSystems = builder.scoreSystems;
tournamentRunner = builder.tournamentRunner;
matchMaker = builder.matchMaker;
broadcaster = builder.broadcaster;
players = builder.players;
turnTime = builder.turnTime;
shuffler = builder.shuffler;
}
public void run(String gameType) {
Arrays.stream(players).forEach(e -> {
matchMaker.addPlayer(e);
scoreSystems.forEach(k -> k.addPlayer(e));
});
if (shuffler != null) matchMaker.shuffle(shuffler);
tournamentRunner.run(matchExecutor, matchMaker, scoreSystems, broadcaster, turnTime, gameType);
}
public static class Builder {
private MatchExecutor matchExecutor;
private List<IntegerScoreSystem> scoreSystems = new ArrayList<>();
private TournamentRunner tournamentRunner;
private MatchMaker matchMaker;
private ResultBroadcaster<IntegerScoreSystem> broadcaster;
private NettyClient[] players;
private NettyClient[] observors;
private NettyClient[] admins;
private Duration turnTime = Duration.ofSeconds(10);
private Shuffler shuffler;
public Builder matchExecutor(MatchExecutor matchExecutor) {
this.matchExecutor = matchExecutor;
return this;
}
public Builder addScoreSystem(IntegerScoreSystem scoreSystem) {
this.scoreSystems.addLast(scoreSystem);
return this;
}
public Builder tournamentRunner(TournamentRunner tournamentRunner) {
this.tournamentRunner = tournamentRunner;
return this;
}
public Builder matchMaker(MatchMaker matchMaker) {
this.matchMaker = matchMaker;
return this;
}
public Builder resultBroadcaster(ResultBroadcaster<IntegerScoreSystem> broadcaster) {
this.broadcaster = broadcaster;
return this;
}
public Builder addPlayers(NettyClient[] players) {
this.players = players;
return this;
}
public Builder addObservers(NettyClient[] observors) { // TODO
this.observors = observors;
return this;
}
public Builder addAdmins(NettyClient[] admins) { // TODO
this.admins = admins;
return this;
}
public Builder turnTimeout(Duration turnTime) {
this.turnTime = turnTime;
return this;
}
public Builder addMatchShuffler(Shuffler shuffler) {
this.shuffler = shuffler;
return this;
}
public Tournament build() {
Objects.requireNonNull(matchExecutor, "matchExecutor");
Objects.requireNonNull(tournamentRunner, "tournamentRunner");
Objects.requireNonNull(matchMaker, "matchMaker");
Objects.requireNonNull(broadcaster, "resultBroadcaster"); // TODO is not always necessary and needs to be more generic, not just at the end
Objects.requireNonNull(players, "players");
return new Tournament(this);
}
}
}

View File

@@ -1,18 +0,0 @@
package org.toop.framework.networking.server.tournaments;
import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.utils.ImmutablePair;
public class TournamentMatch extends ImmutablePair<NettyClient, NettyClient> {
public TournamentMatch(NettyClient a, NettyClient b) {
super(a, b);
}
public NettyClient getClient0() {
return getLeft();
}
public NettyClient getClient1() {
return getRight();
}
}

View File

@@ -1,13 +0,0 @@
package org.toop.framework.networking.server.tournaments;
import org.toop.framework.networking.server.MatchExecutor;
import org.toop.framework.networking.server.tournaments.matchmakers.MatchMaker;
import org.toop.framework.networking.server.tournaments.scoresystems.IntegerScoreSystem;
import java.time.Duration;
import java.util.List;
public interface TournamentRunner {
void run(MatchExecutor matchExecutor, MatchMaker matchMaker, List<IntegerScoreSystem> scoreSystems,
ResultBroadcaster<IntegerScoreSystem> broadcaster, Duration turnTime, String gameType);
}

View File

@@ -1,81 +0,0 @@
package org.toop.framework.networking.server.tournaments.matchmakers;
import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.tournaments.TournamentMatch;
import org.toop.framework.networking.server.tournaments.shufflers.Shuffler;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
public class DoubleRoundRobinMatchMaker implements MatchMaker {
private final List<NettyClient> players = new ArrayList<>();
public DoubleRoundRobinMatchMaker() {} // TODO let user decide store type
@Override
public void addPlayer(NettyClient player) {
players.addLast(player);
}
@Override
public void shuffle(Shuffler shuffler) {
if (players.size() < 2) return;
shuffler.shuffle(players);
}
@Override
public List<NettyClient> getPlayers() {
return players;
}
@Override
public Iterator<TournamentMatch> iterator() {
return new Iterator<>() {
private int i = 0;
private int j = 1;
private boolean reverse = false;
@Override
public boolean hasNext() {
return players.size() > 1
&& i < players.size() - 1
&& j < players.size();
}
@Override
public TournamentMatch next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
NettyClient home = players.get(i);
NettyClient away = players.get(j);
TournamentMatch match = reverse ? new TournamentMatch(away, home) : new TournamentMatch(home, away);
advance();
return match;
}
private void advance() {
j++;
if (j >= players.size()) {
i++;
j = i + 1;
if (i >= players.size() - 1) {
if (!reverse) {
reverse = true;
i = 0;
j = 1;
}
}
}
}
};
}
}

View File

@@ -1,13 +0,0 @@
package org.toop.framework.networking.server.tournaments.matchmakers;
import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.tournaments.TournamentMatch;
import org.toop.framework.networking.server.tournaments.shufflers.Shuffler;
import java.util.List;
public interface MatchMaker extends Iterable<TournamentMatch> {
void addPlayer(NettyClient player);
List<NettyClient> getPlayers();
void shuffle(Shuffler shuffler);
}

View File

@@ -1,67 +0,0 @@
package org.toop.framework.networking.server.tournaments.matchmakers;
import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.tournaments.TournamentMatch;
import org.toop.framework.networking.server.tournaments.shufflers.Shuffler;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
public class RoundRobinMatchMaker implements MatchMaker {
private final List<NettyClient> players = new ArrayList<>();
public RoundRobinMatchMaker() {} // TODO let user decide store type
@Override
public void addPlayer(NettyClient player) {
players.addLast(player);
}
@Override
public void shuffle(Shuffler shuffler) {
if (players.size() < 2) return;
shuffler.shuffle(players);
}
@Override
public List<NettyClient> getPlayers() {
return players;
}
@Override
public Iterator<TournamentMatch> iterator() {
return new Iterator<>() {
private int i = 0;
private int j = 1;
@Override
public boolean hasNext() {
return players.size() > 1
&& i < players.size() - 1
&& j < players.size();
}
@Override
public TournamentMatch next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
NettyClient home = players.get(i);
NettyClient away = players.get(j);
j++;
if (j >= players.size()) {
i++;
j = i + 1;
}
return new TournamentMatch(home, away);
}
};
}
}

View File

@@ -1,43 +0,0 @@
package org.toop.framework.networking.server.tournaments.scoresystems;
import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.tournaments.TournamentMatch;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class DrawCountScoreSystem implements IntegerScoreSystem {
private final Map<NettyClient, Integer> scores = new ConcurrentHashMap<>();
private final int INIT_SCORE = 0;
private final int WIN_POINTS = 1;
public DrawCountScoreSystem() {} // TODO let user decide store type
@Override
public String scoreName() {
return "draws";
}
@Override
public void addPlayer(NettyClient user) {
scores.putIfAbsent(user, INIT_SCORE);
}
@Override
public void result(TournamentMatch match, Integer result) {
switch (result) {
case 0, 1 -> {}
case -1 -> {
scores.merge(match.getClient0(), WIN_POINTS, Integer::sum);
scores.merge(match.getClient1(), WIN_POINTS, Integer::sum);
}
default -> throw new IllegalArgumentException("Unknown result: " + result);
}
}
@Override
public Map<NettyClient, Integer> getScore() {
return scores;
}
}

View File

@@ -1,6 +0,0 @@
package org.toop.framework.networking.server.tournaments.scoresystems;
import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.tournaments.TournamentMatch;
public interface IntegerScoreSystem extends ScoreSystem<TournamentMatch, Integer, NettyClient> {}

View File

@@ -1,41 +0,0 @@
package org.toop.framework.networking.server.tournaments.scoresystems;
import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.tournaments.TournamentMatch;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class LoseCountScoreSystem implements IntegerScoreSystem {
private final Map<NettyClient, Integer> scores = new ConcurrentHashMap<>();
private final int INIT_SCORE = 0;
private final int WIN_POINTS = 1;
public LoseCountScoreSystem() {} // TODO let user decide store type
@Override
public String scoreName() {
return "loses";
}
@Override
public void addPlayer(NettyClient user) {
scores.putIfAbsent(user, INIT_SCORE);
}
@Override
public void result(TournamentMatch match, Integer result) {
switch (result) {
case 0 -> scores.merge(match.getClient1(), WIN_POINTS, Integer::sum);
case 1 -> scores.merge(match.getClient0(), WIN_POINTS, Integer::sum);
case -1 -> {} // Draw
default -> throw new IllegalArgumentException("Unknown result: " + result);
}
}
@Override
public Map<NettyClient, Integer> getScore() {
return scores;
}
}

View File

@@ -1,37 +0,0 @@
package org.toop.framework.networking.server.tournaments.scoresystems;
import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.tournaments.TournamentMatch;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class MatchCountScoreSystem implements IntegerScoreSystem {
private final Map<NettyClient, Integer> scores = new ConcurrentHashMap<>();
private final int INIT_SCORE = 0;
private final int WIN_POINTS = 1;
public MatchCountScoreSystem() {} // TODO let user decide store type
@Override
public String scoreName() {
return "matches";
}
@Override
public void addPlayer(NettyClient user) {
scores.putIfAbsent(user, INIT_SCORE);
}
@Override
public void result(TournamentMatch match, Integer result) {
scores.merge(match.getClient0(), WIN_POINTS, Integer::sum);
scores.merge(match.getClient1(), WIN_POINTS, Integer::sum);
}
@Override
public Map<NettyClient, Integer> getScore() {
return scores;
}
}

View File

@@ -1,10 +0,0 @@
package org.toop.framework.networking.server.tournaments.scoresystems;
import java.util.Map;
public interface ScoreSystem<MATCHTYPE, SCORETYPE, USERTYPE> {
String scoreName();
void addPlayer(USERTYPE user);
void result(MATCHTYPE match, SCORETYPE result);
Map<USERTYPE, SCORETYPE> getScore();
}

View File

@@ -1,41 +0,0 @@
package org.toop.framework.networking.server.tournaments.scoresystems;
import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.tournaments.TournamentMatch;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class WinCountScoreSystem implements IntegerScoreSystem {
private final Map<NettyClient, Integer> scores = new ConcurrentHashMap<>();
private final int INIT_SCORE = 0;
private final int WIN_POINTS = 1;
public WinCountScoreSystem() {} // TODO let user decide store type
@Override
public String scoreName() {
return "wins";
}
@Override
public void addPlayer(NettyClient user) {
scores.putIfAbsent(user, INIT_SCORE);
}
@Override
public void result(TournamentMatch match, Integer result) {
switch (result) {
case 0 -> scores.merge(match.getClient0(), WIN_POINTS, Integer::sum);
case 1 -> scores.merge(match.getClient1(), WIN_POINTS, Integer::sum);
case -1 -> {} // Draw
default -> throw new IllegalArgumentException("Unknown result: " + result);
}
}
@Override
public Map<NettyClient, Integer> getScore() {
return scores;
}
}

View File

@@ -1,20 +0,0 @@
package org.toop.framework.networking.server.tournaments.shufflers;
import java.util.List;
import java.util.Random;
public class RandomShuffle implements Shuffler {
@Override
public <T> void shuffle(List<T> listToShuffle) {
final int SHUFFLE_AMOUNT = listToShuffle.size() * 2;
Random rand = new Random();
for (int i = 0; i <= SHUFFLE_AMOUNT; i++) {
int index = rand.nextInt(listToShuffle.size());
T match = listToShuffle.get(index);
listToShuffle.remove(index);
listToShuffle.addLast(match);
}
}
}

View File

@@ -1,7 +0,0 @@
package org.toop.framework.networking.server.tournaments.shufflers;
import java.util.List;
public interface Shuffler {
<T> void shuffle(List<T> listToShuffle);
}

View File

@@ -1,298 +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;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class MCTSAI extends AbstractAI {
protected static class Node {
public static final int VIRTUAL_LOSS = -1;
public class MCTSAI extends AbstractAI {
private static class Node {
public TurnBasedGame state;
public long move;
public long unexpandedMoves;
public Node parent;
public int expanded;
public Node[] children;
public AtomicInteger value;
public AtomicInteger visits;
public float heuristic;
public float solved;
public Node(TurnBasedGame state, Node parent, long move) {
final long legalMoves = state.getLegalMoves();
public int visits;
public float value;
public Node(TurnBasedGame state, long move, Node parent) {
this.state = state;
this.move = move;
this.unexpandedMoves = legalMoves;
this.parent = parent;
this.children = new Node[Long.bitCount(legalMoves)];
this.value = new AtomicInteger(0);
this.visits = new AtomicInteger(0);
this.expanded = 0;
this.children = new Node[Long.bitCount(state.getLegalMoves())];
this.heuristic = state.rateMove(move);
this.solved = Float.NaN;
this.visits = 0;
this.value = 0.0f;
}
public Node(TurnBasedGame state) {
this(state, null, 0L);
}
public int getExpanded() {
return children.length - Long.bitCount(unexpandedMoves);
this(state, 0L, null);
}
public boolean isFullyExpanded() {
return unexpandedMoves == 0L;
return expanded >= children.length;
}
public float calculateUCT(float explorationFactor) {
if (visits.get() == 0) {
return Float.POSITIVE_INFINITY;
}
float calculateUCT() {
float exploitation = visits <= 0? 0 : value / visits;
float exploration = 1.41f * (float)(Math.sqrt(Math.log(visits) / visits));
final float exploitation = (float) value.get() / visits.get();
final float exploration = (float)(Math.sqrt(explorationFactor / visits.get()));
final float bias = heuristic * 10.0f / (visits.get() + 1);
return exploitation + exploration + bias;
return exploitation + exploration;
}
public Node bestUCTChild() {
final int expanded = getExpanded();
Node highestUCTChild = null;
float highestUCT = Float.NEGATIVE_INFINITY;
int bestChildIndex = -1;
float bestScore = Float.NEGATIVE_INFINITY;
for (int i = 0; i < expanded; i++) {
final float childUCT = children[i].calculateUCT(2.0f * (float)Math.log(visits.get()));
final float score = calculateUCT();
if (childUCT > highestUCT) {
highestUCTChild = children[i];
highestUCT = childUCT;
if (score > bestScore) {
bestChildIndex = i;
bestScore = score;
}
}
return highestUCTChild;
return bestChildIndex >= 0? children[bestChildIndex] : this;
}
}
protected static final ThreadLocal<Random> random = ThreadLocal.withInitial(Random::new);
protected final int milliseconds;
protected int lastIterations;
private final int milliseconds;
public MCTSAI(int milliseconds) {
this.milliseconds = milliseconds;
this.lastIterations = 0;
}
public MCTSAI(MCTSAI other) {
this.milliseconds = other.milliseconds;
this.lastIterations = other.lastIterations;
}
public int getLastIterations() {
return lastIterations;
@Override
public MCTSAI deepCopy() {
return new MCTSAI(this);
}
protected Node selection(Node root) {
while (Float.isNaN(root.solved) && root.isFullyExpanded() && !root.state.isTerminal()) {
root.value.addAndGet(Node.VIRTUAL_LOSS);
root.visits.incrementAndGet();
@Override
public long getMove(TurnBasedGame game) {
Node root = new Node(game.deepCopy());
root = root.bestUCTChild();
}
long endTime = System.currentTimeMillis() + milliseconds;
root.value.addAndGet(Node.VIRTUAL_LOSS);
root.visits.incrementAndGet();
while (System.currentTimeMillis() <= endTime) {
Node node = selection(root);
long legalMoves = node.state.getLegalMoves();
return root;
}
protected Node expansion(Node leaf) {
synchronized (leaf) {
if (leaf.unexpandedMoves == 0L) {
return leaf;
if (legalMoves != 0) {
node = expansion(node, legalMoves);
}
final long unexpandedMove = leaf.unexpandedMoves & -leaf.unexpandedMoves;
float result = 0.0f;
final TurnBasedGame copiedState = leaf.state.deepCopy();
copiedState.play(unexpandedMove);
final Node expandedChild = new Node(copiedState, leaf, unexpandedMove);
leaf.children[leaf.getExpanded()] = expandedChild;
leaf.unexpandedMoves &= ~unexpandedMove;
return expandedChild;
}
}
protected int 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;
}
if (copiedState.getWinner() >= 0) {
return -1;
}
return 0;
}
protected void backPropagation(Node leaf, int value) {
while (leaf != null) {
value -= Node.VIRTUAL_LOSS;
leaf.value.addAndGet(value);
if (Float.isNaN(leaf.solved)) {
updateSolvedStatus(leaf);
if (node.state.getLegalMoves() != 0) {
result = simulation(node.state, game.getCurrentTurn());
}
value = -value;
leaf = leaf.parent;
backPropagation(node, result);
}
}
protected Node mostVisitedChild(Node root) {
final int expanded = root.getExpanded();
int mostVisitedIndex = -1;
int mostVisits = -1;
Node mostVisitedChild = null;
int mostVisited = -1;
for (int i = 0; i < expanded; i++) {
if (root.children[i].visits.get() > mostVisited) {
mostVisitedChild = root.children[i];
mostVisited = root.children[i].visits.get();
for (int i = 0; i < root.expanded; i++) {
if (root.children[i].visits > mostVisits) {
mostVisitedIndex = i;
mostVisits = root.children[i].visits;
}
}
return mostVisitedChild;
return mostVisitedIndex != -1? root.children[mostVisitedIndex].move : randomSetBit(game.getLegalMoves());
}
protected Node findOrResetRoot(Node root, TurnBasedGame game) {
if (root == null) {
return new Node(game.deepCopy());
private Node selection(Node node) {
while (node.state.getLegalMoves() != 0L && node.isFullyExpanded()) {
node = node.bestUCTChild();
}
if (areStatesEqual(root.state.getBoard(), game.getBoard())) {
return root;
return node;
}
private Node expansion(Node node, long legalMoves) {
for (int i = 0; i < node.expanded; i++) {
legalMoves &= ~node.children[i].move;
}
final int expanded = root.getExpanded();
if (legalMoves == 0L) {
return node;
}
for (int i = 0; i < expanded; i++) {
if (areStatesEqual(root.children[i].state.getBoard(), game.getBoard())) {
root.children[i].parent = null;
return root.children[i];
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 new Node(game.deepCopy());
return -0.2f;
}
protected Node findChildByMove(Node root, long move) {
final int expanded = root.getExpanded();
private void backPropagation(Node node, float value) {
while (node != null) {
node.visits++;
node.value += value;
node = node.parent;
}
}
for (int i = 0; i < expanded; i++) {
if (root.children[i].move == move) {
root.children[i].parent = null;
return root.children[i];
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;
}
}
return null;
}
protected boolean areStatesEqual(long[] state1, long[] state2) {
if (state1.length != state2.length) {
return false;
}
for (int i = 0; i < state1.length; i++) {
if (state1[i] != state2[i]) {
return false;
}
}
return true;
}
protected long randomSetBit(long value) {
if (0L == value) {
return 0;
}
final int bitCount = Long.bitCount(value);
final int randomBitCount = random.get().nextInt(bitCount);
for (int i = 0; i < randomBitCount; i++) {
value &= value - 1;
}
return value & -value;
}
private void updateSolvedStatus(Node node) {
if (node.state.isTerminal()) {
final int winner = node.state.getWinner();
final int mover = 1 - node.state.getCurrentTurn();
node.solved = winner == mover? 1.0f : winner == -1? 0.0f : -1.0f;
return;
}
if (node.isFullyExpanded()) {
boolean allChildrenSolved = true;
boolean foundWinningMove = false;
boolean foundDrawMove = false;
for (final Node child : node.children) {
if (!Float.isNaN(child.solved)) {
if (child.solved == -1.0f) {
foundWinningMove = true;
break;
}
if (child.solved == 0.0f) {
foundDrawMove = true;
}
} else {
allChildrenSolved = false;
}
}
if (foundWinningMove) {
node.solved = 1.0f;
} else if (allChildrenSolved) {
node.solved = foundDrawMove? 0.0f : -1.0f;
}
target--;
}
}
}

View File

@@ -1,38 +0,0 @@
package org.toop.game.players.ai.mcts;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.game.players.ai.MCTSAI;
public class MCTSAI1 extends MCTSAI {
public MCTSAI1(int milliseconds) {
super(milliseconds);
}
public MCTSAI1(MCTSAI1 other) {
super(other);
}
@Override
public MCTSAI1 deepCopy() {
return new MCTSAI1(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 (Float.isNaN(root.solved) && System.nanoTime() < endTime) {
Node leaf = selection(root);
leaf = expansion(leaf);
final int value = simulation(leaf);
backPropagation(leaf, value);
}
lastIterations = root.visits.get();
final Node mostVisitedChild = mostVisitedChild(root);
return mostVisitedChild.move;
}
}

View File

@@ -1,48 +0,0 @@
package org.toop.game.players.ai.mcts;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.game.players.ai.MCTSAI;
public class MCTSAI2 extends MCTSAI {
private Node root;
public MCTSAI2(int milliseconds) {
super(milliseconds);
this.root = null;
}
public MCTSAI2(MCTSAI2 other) {
super(other);
this.root = other.root;
}
@Override
public MCTSAI2 deepCopy() {
return new MCTSAI2(this);
}
@Override
public long getMove(TurnBasedGame game) {
root = findOrResetRoot(root, game);
final long endTime = System.nanoTime() + milliseconds * 1_000_000L;
while (Float.isNaN(root.solved) && System.nanoTime() < endTime) {
Node leaf = selection(root);
leaf = expansion(leaf);
final int value = simulation(leaf);
backPropagation(leaf, value);
}
lastIterations = root.visits.get();
final Node mostVisitedChild = mostVisitedChild(root);
final long move = mostVisitedChild.move;
root = findChildByMove(root, move);
return move;
}
}

View File

@@ -1,76 +0,0 @@
package org.toop.game.players.ai.mcts;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.game.players.ai.MCTSAI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class MCTSAI3 extends MCTSAI {
private final int threads;
private final ExecutorService threadPool;
public MCTSAI3(int milliseconds, int threads) {
super(milliseconds);
this.threads = threads;
this.threadPool = Executors.newFixedThreadPool(threads);
}
public MCTSAI3(MCTSAI3 other) {
super(other);
this.threads = other.threads;
this.threadPool = other.threadPool;
}
@Override
public MCTSAI3 deepCopy() {
return new MCTSAI3(this);
}
@Override
public long getMove(TurnBasedGame game) {
final Node root = new Node(game.deepCopy(), null, 0L);
final long endTime = System.nanoTime() + milliseconds * 1_000_000L;
final CountDownLatch latch = new CountDownLatch(threads);
for (int i = 0; i < threads; i++) {
threadPool.submit(() -> {
try {
iterate(root, endTime);
} finally {
latch.countDown();
}
});
}
try {
final long remaining = endTime - System.nanoTime();
latch.await(remaining, TimeUnit.NANOSECONDS);
lastIterations = root.visits.get();
final Node mostVisitedChild = mostVisitedChild(root);
return mostVisitedChild.move;
} catch (Exception _) {
lastIterations = 0;
final long legalMoves = game.getLegalMoves();
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,82 +0,0 @@
package org.toop.game.players.ai.mcts;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.game.players.ai.MCTSAI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class MCTSAI4 extends MCTSAI {
private final int threads;
private final ExecutorService threadPool;
private Node root;
public MCTSAI4(int milliseconds, int threads) {
super(milliseconds);
this.threads = threads;
this.threadPool = Executors.newFixedThreadPool(threads);
}
public MCTSAI4(MCTSAI4 other) {
super(other);
this.threads = other.threads;
this.threadPool = other.threadPool;
}
@Override
public MCTSAI4 deepCopy() {
return new MCTSAI4(this);
}
@Override
public long getMove(TurnBasedGame game) {
root = findOrResetRoot(root, game);
final long endTime = System.nanoTime() + milliseconds * 1_000_000L;
final CountDownLatch latch = new CountDownLatch(threads);
for (int i = 0; i < threads; i++) {
threadPool.submit(() -> {
try {
iterate(root, endTime);
} finally {
latch.countDown();
}
});
}
try {
final long remaining = endTime - System.nanoTime();
latch.await(remaining, TimeUnit.NANOSECONDS);
lastIterations = root.visits.get();
final Node mostVisitedChild = mostVisitedChild(root);
final long move = mostVisitedChild.move;
root = findChildByMove(root, move);
return move;
} catch (Exception _) {
lastIterations = 0;
final long legalMoves = game.getLegalMoves();
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,77 +0,0 @@
package research;
public class AIData {
public String AI;
public long gamesPlayed;
public double winrate;
public double averageIterations;
public double averageIterations10;
public double averageIterations20;
public double averageIterations30;
public AIData(String AI, long gamesPlayed, double winrate, double averageIterations, double averageIterations10, double averageIterations20, double averageIterations30) {
this.AI = AI;
this.gamesPlayed = gamesPlayed;
this.winrate = winrate;
this.averageIterations = averageIterations;
this.averageIterations10 = averageIterations10;
this.averageIterations20 = averageIterations20;
this.averageIterations30 = averageIterations30;
}
public String getAI() {
return AI;
}
public void setAI(String AI) {
this.AI = AI;
}
public long getGamesPlayed() {
return gamesPlayed;
}
public void setGamesPlayed(long gamesPlayed) {
this.gamesPlayed = gamesPlayed;
}
public double getWinrate() {
return winrate;
}
public void setWinrate(double winrate) {
this.winrate = winrate;
}
public double getAverageIterations() {
return averageIterations;
}
public void setAverageIterations(double averageIterations) {
this.averageIterations = averageIterations;
}
public double getAverageIterations10() {
return averageIterations10;
}
public void setAverageIterations10(double averageIterations10) {
this.averageIterations10 = averageIterations10;
}
public double getAverageIterations20() {
return averageIterations20;
}
public void setAverageIterations20(double averageIterations20) {
this.averageIterations20 = averageIterations20;
}
public double getAverageIterations30() {
return averageIterations30;
}
public void setAverageIterations30(double averageIterations30) {
this.averageIterations30 = averageIterations30;
}
}

View File

@@ -1,612 +0,0 @@
package research;
import org.apache.maven.surefire.shared.io.FileDeleteStrategy;
import org.junit.jupiter.api.*;
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.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.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
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.List;
public class AITest {
private static String fileName = "gameData.csv";
private static List<Matchup> matchupList = new ArrayList<Matchup>();
private static List<AIData> dataList = new ArrayList<AIData>();
private static List<GameData> gameDataList = new ArrayList<GameData>();
@BeforeAll
public static void init() {
var versions = new ArtificialPlayer[4];
versions[0] = new ArtificialPlayer(new MCTSAI1(10), "MCTS V1");
versions[1] = new ArtificialPlayer(new MCTSAI2(10), "MCTS V2");
versions[2] = new ArtificialPlayer(new MCTSAI3(10, 8), "MCTS V3");
versions[3] = new ArtificialPlayer(new MCTSAI4(10, 8), "MCTS V4");
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
}
}
}
// @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) {
matchupList.add(new Matchup(v1, v2));
}
public void addAIData(AIData data) {
dataList.add(data);
}
public void addGameData(GameData data) {
gameDataList.add(data);
}
@Test
public void testAIvsAI() {
while (true) {
for (Matchup m : matchupList) {
playGame(m);
}
}
}
public void playGame(Matchup m) {
long nanocounterAI1 = 0L;
long nanocounterAI2 = 0L;
List<Integer> iterationsAI1 = new ArrayList<>();
List<Integer> iterationsAI2 = new ArrayList<>();
final BitboardReversi match = new BitboardReversi();
ArtificialPlayer[] players = new ArtificialPlayer[2];
players[0] = m.getPlayer1();
players[1] = m.getPlayer2();
match.init(players);
while (!match.isTerminal()) {
final int currentAI = match.getCurrentTurn();
final long startTime = System.nanoTime();
final long move = players[currentAI].getMove(match);
final long endTime = System.nanoTime();
if (players[currentAI].getAi() instanceof MCTSAI) {
final int lastIterations = ((MCTSAI) players[currentAI].getAi()).getLastIterations();
if (currentAI == 0) {
iterationsAI1.add(lastIterations);
nanocounterAI1 += (endTime - startTime);
} else {
iterationsAI2.add(lastIterations);
nanocounterAI2 += (endTime - startTime);
}
}
match.play(move);
}
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) {
boolean matchup1Found = false;
boolean matchup2Found = false;
for (AIData aiData : dataList) {
if (aiData.getAI().equals(matchup.getPlayer1().getName())) {
matchup1Found = true;
} if (aiData.getAI().equals(matchup.getPlayer2().getName())) {
matchup2Found = true;
}
}
if (!(matchup1Found)) {
addAIData(new AIData(matchup.getPlayer1().getName(), 0, 0, 0, 0, 0, 0));
}
if (!(matchup2Found)) {
addAIData(new AIData(matchup.getPlayer2().getName(), 0, 0, 0, 0, 0, 0));
}
for (AIData aiData : dataList) { // set data for player 1
if (aiData.getAI().equals(matchup.getPlayer1().getName())) {
aiData.setGamesPlayed(aiData.getGamesPlayed() + 1);
aiData.setWinrate(calculateWinrate(0, aiData.getWinrate(), aiData.getGamesPlayed(), match.getWinner()));
aiData.setAverageIterations(calculateAverageIterations(aiData.getAverageIterations(), iterationsAI1));
aiData.setAverageIterations10(calculateAverageIterationsStartEnd(0, 10, aiData.getAverageIterations10(), iterationsAI1));
aiData.setAverageIterations20(calculateAverageIterationsStartEnd(10, 20, aiData.getAverageIterations20(), iterationsAI1));
aiData.setAverageIterations30(calculateAverageIterationsStartEnd(20, iterationsAI1.size(), aiData.getAverageIterations30(), iterationsAI1));
}
}
for (AIData aiData : dataList) {
if (aiData.getAI().equals(matchup.getPlayer2().getName())) {
aiData.setGamesPlayed(aiData.getGamesPlayed() + 1);
aiData.setWinrate(calculateWinrate(1, aiData.getWinrate(), aiData.getGamesPlayed(), match.getWinner()));
aiData.setAverageIterations(calculateAverageIterations(aiData.getAverageIterations(), iterationsAI2));
aiData.setAverageIterations10(calculateAverageIterationsStartEnd(0, 10, aiData.getAverageIterations10(), iterationsAI2));
aiData.setAverageIterations20(calculateAverageIterationsStartEnd(10, 20, aiData.getAverageIterations20(), iterationsAI2));
aiData.setAverageIterations30(calculateAverageIterationsStartEnd(20, iterationsAI2.size(), aiData.getAverageIterations30(), iterationsAI2));
}
}
}
public double calculateWinrate(int player, double winrate, long gamesPlayed, int winner) {
double result;
if (winner == 0 && player == 0 || winner == 1 && player == 1) {
return (winrate * (gamesPlayed - 1) + 1) / gamesPlayed;
} else if (winner == 0 && player == 1 || winner == 1 && player == 0) {
return (winrate * (gamesPlayed - 1) + 0) / gamesPlayed;
}
return (winrate * (gamesPlayed - 1) + 0) / gamesPlayed;
}
public double calculateAverageIterations(double averageIterations, List<Integer> thisGameIterations) {
double thisGameIterationsAverage = 0;
for (int iterations = 0; iterations < thisGameIterations.size(); iterations += 1) {
thisGameIterationsAverage += thisGameIterations.get(iterations);
}
thisGameIterationsAverage /= thisGameIterations.size();
return (averageIterations + thisGameIterationsAverage) / 2;
}
public double calculateAverageIterationsStartEnd(int start, int end, double averageIterations, List<Integer> thisGameIterations) {
double thisGameIterationsAverage = 0;
for (int iterations = start; iterations < end; iterations += 1) {
thisGameIterationsAverage += thisGameIterations.get(iterations);
}
thisGameIterationsAverage /= (end - start);
return (averageIterations + thisGameIterationsAverage) / 2;
}
@AfterAll
public static void writeAfterTests() {
try {
writeAIToCsv("Data.csv", dataList);
} catch (IOException e) {
e.printStackTrace();
}
}
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))) {
writer.write("AI Name,Games Played,Winrate,Average Iterations,Average Iterations 0-10, Average Iterations 11-20, Average Iterations 20-30");
writer.newLine();
for (AIData data : dataList) {
writer.write(
data.getAI() + "," +
data.getGamesPlayed() + "," +
data.getWinrate() + "," +
Math.round(data.getAverageIterations()) + "," +
Math.round(data.getAverageIterations10()) + "," +
Math.round(data.getAverageIterations20()) + "," +
Math.round(data.getAverageIterations30()));
writer.newLine();
}
}
}
}
//public class AITest {
// private static int games = 2;
//
// @BeforeAll
// public static void setUp() {
// var versions = new ArtificialPlayer[5];
// versions[0] = new ArtificialPlayer(new RandomAI(), "Random AI");
// versions[1] = new ArtificialPlayer(new MCTSAI1(20), "MCTS V1 AI");
// versions[2] = new ArtificialPlayer(new org.toop.game.players.ai.mcts.MCTSAI2(20), "MCTS V2 AI");
// versions[3] = new ArtificialPlayer(new org.toop.game.players.ai.mcts.MCTSAI3(20, 10), "MCTS V3 AI");
// versions[4] = new ArtificialPlayer(new MCTSAI4(20, 10), "MCTS V4 AI");
//
// 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;
// addMatchup(versions[playerIndex1], versions[playerIndex2]);
// }
// }
//
// }
//
// @BeforeEach
// public void setUpEach() {
// matchupList = new ArrayList<>();
// }
//
// @Test
// public void testIterationsInRealGame() {
// for (int i = 0; i < matchups.size(); i++) {
// testAIVSAI(games, getMatchup(i));
// }
// }
//
//
// private void testAIVSAI(int games, ArtificialPlayer[] ais) {
//
// List<List<Integer>> gamesList = new ArrayList<>();
// for (int i = 0; i < games; i++) {
// final BitboardReversi match = new BitboardReversi();
// match.init(ais);
//
// List<Integer> iterations1 = new ArrayList<>();
// List<Integer> iterations2 = new ArrayList<>();
//
// while (!match.isTerminal()) {
// final int currentAI = match.getCurrentTurn();
// final long move = ais[currentAI].getMove(match);
// if (ais[currentAI].getAi() instanceof MCTSAI) {
// final int lastIterations = ((MCTSAI) ais[currentAI].getAi()).getLastIterations();
// if (currentAI == 0) {
// iterations1.add(lastIterations);
// } else if (currentAI == 1) {
// iterations2.add(lastIterations);
// }
// }
// match.play(move);
// }
// int winner = match.getWinner();
// iterations1.addFirst(winner);
//// iterations1.add(-999);
// iterations1.addAll(iterations2);
//
// gamesList.add(iterations1);
// }
// matchupList.add(gamesList);
// }
//
// @Test
// public void testIterationsAtFixedMove() {
// for (ArtificialPlayer[] matchup : matchups) {
// List<List<Integer>> gamesList = new ArrayList<>();
// for (int j = 0; j < games; j++) {
// final BitboardReversi match = new BitboardReversi();
// match.init(matchup);
//
// List<Integer> iterations = new ArrayList<>();
//
// for (Long move : fixedMoveSet) {
// match.play(move);
// if (move == 32L) {
// break;
// }
// }
//// iterations.add(-999);
// var player = matchup[match.getCurrentTurn()];
// for (int k = 0; k < 10; k++) {
// player.getMove(match);
// if (player.getAi() instanceof MCTSAI) {
// iterations.add(((MCTSAI) player.getAi()).getLastIterations());
// }
// }
// gamesList.add(iterations);
// }
// matchupList.add(gamesList);
// }
// }
//
//
// @Test
// public void testIterationsInFixedGame() {
// for (ArtificialPlayer[] matchup : matchups) {
// List<List<Integer>> gamesList = new ArrayList<>();
// for (int j = 0; j < games; j++) {
// final BitboardReversi match = new BitboardReversi();
// match.init(matchup);
//
// List<Integer> iterations = new ArrayList<>();
//
// iterations.add(-999);
//
// for (Long move : fixedMoveSet) {
// var player = matchup[match.getCurrentTurn()];
// player.getMove(match);
// if (player.getAi() instanceof MCTSAI) {
// iterations.add(((MCTSAI) player.getAi()).getLastIterations());
// }
// match.play(move);
// }
//
// gamesList.add(iterations);
// }
// matchupList.add(gamesList);
// }
// }
//
// @AfterEach
// public void tearDown() {
// data.add(matchupList);
// }
//
// @AfterAll
// public static void writeAfterTests() {
// try {
// writeToCsv("Data.csv", data);
// } catch (IOException e) {
//
// }
// }
//
//
// public static void writeToCsv(String filepath, List<List<List<List<Integer>>>> data) throws IOException {
// try (BufferedWriter writer = new BufferedWriter(new FileWriter(filepath))) {
//
// writer.write("TestID,Matchup,GameNr,Winner");
// for (int i = 0; i < data.size(); i++) {
// writer.write(",Iterations");
// }
//
// writer.newLine();
//
// for (int TestID = 0; TestID < data.size(); TestID++) {
// List<List<List<Integer>>> testCase = data.get(TestID);
//
// for (int matchupNr = 0; matchupNr < testCase.size(); matchupNr++) {
// List<List<Integer>> matchup = testCase.get(matchupNr);
//
// for (int gameNr = 0; gameNr < matchup.size(); gameNr++) {
// List<Integer> game = matchup.get(gameNr);
// writer.write((TestID + 1) + "," + (getMatchupName(matchupNr)) + "," + (gameNr + 1));
// for (int i = 0; i < game.size(); i++) {
// if (i == 0) {
// writer.write("," + getWinnerFromMatchup(game.get(i), matchupNr));
// } else {
// writer.write("," + game.get(i));
// }
// }
// writer.newLine();
// }
// }
// }
// }
//
// }
//
//
// private static final List<List<List<List<Integer>>>> data = new ArrayList<>();
// private List<List<List<Integer>>> matchupList = new ArrayList<>();
// private static final List<String> matchupNames = new ArrayList<>();
// private static final List<ArtificialPlayer[]> matchups = new ArrayList<>();
//
// private static String getMatchupName(int matchupNr) {
// return matchupNames.get(matchupNr);
// }
//
// private static ArtificialPlayer[] getMatchup(int matchupNr) {
// return matchups.get(matchupNr);
// }
//
// private static String getWinnerFromMatchup(Integer winner, int matchupNr) {
// String matchup = matchupNames.get(matchupNr);
//
// String[] parts = matchup.split(" vs ");
//
// if (parts.length != 2) {
// return "Invalid matchup formatting.";
// }
//
// return winner == 0 ? parts[0] : winner == 1 ? parts[1] : winner == -999 ? "NVT" : "Tie";
// }
//
// private static void addMatchup(ArtificialPlayer player1, ArtificialPlayer player2) {
// matchups.add(new ArtificialPlayer[]{player1, player2});
// matchupNames.add(player1.getName() + " vs " + player2.getName());
// }
//}
// private final Long[] fixedMoveSet = new Long[]{17592186044416L,
// 35184372088832L,
// 67108864L,
// 8796093022208L,
// 2251799813685248L,
// 288230376151711744L,
// 70368744177664L,
// 1125899906842624L,
// 137438953472L,
// 140737488355328L,
// 4503599627370496L,
// 2305843009213693952L,
// 18014398509481984L,
// 274877906944L,
// 576460752303423488L,
// -9223372036854775808L,
// 549755813888L,
// 1152921504606846976L,
// 144115188075855872L,
// 72057594037927936L,
// 36028797018963968L,
// 17179869184L,
// 2199023255552L,
// 1048576L,
// 4398046511104L,
// 281474976710656L,
// 9007199254740992L,
// 2147483648L,
// 1073741824L,
// 33554432L,
// 262144L,
// 8388608L,
// 8192L,
// 4611686018427387904L,
// 4294967296L,
// 524288L,
// 4096L,
// 16777216L,
// 65536L,
// 32L,
// 2048L,
// 8L,
// 4L,
// 8589934592L,
// 16L,
// 2097152L,
// 4194304L,
// 1024L,
// 512L,
// 16384L,
// 536870912L,
// 1099511627776L,
// 64L,
// 562949953421312L,
// 128L,
// 1L,
// 32768L,
// 2L,
// 256L,
// 131072L};
// }

View File

@@ -1,32 +0,0 @@
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
) {}

View File

@@ -1,30 +0,0 @@
package research;
import org.toop.framework.game.players.ArtificialPlayer;
import java.util.ArrayList;
import java.util.List;
public class Matchup {
public ArtificialPlayer player1;
public ArtificialPlayer player2;
public Matchup(ArtificialPlayer player1, ArtificialPlayer player2) {
this.player1 = player1;
this.player2 = player2;
}
public Matchup() {}
public String toString() {
return player1.toString() + " VS " + player2.toString();
}
public ArtificialPlayer getPlayer1() {
return player1;
}
public ArtificialPlayer getPlayer2() {
return player2;
}
}