81 Commits

Author SHA1 Message Date
fd9aabb6a1 refactor dddview
All checks were successful
Linux arm64 / Build (push) Successful in 46s
2025-05-18 11:45:23 +02:00
1b31643f0b spawn popup at center 2025-05-18 11:45:17 +02:00
fefb826b38 free frame buffer 2025-05-18 11:37:57 +02:00
56f2aa3c56 close window on game end 2025-05-18 11:36:14 +02:00
0e8ee9eacd blocking popups 2025-05-18 11:25:10 +02:00
d3f0de81b1 remove left overs
All checks were successful
Linux arm64 / Build (push) Successful in 46s
2025-05-18 10:34:11 +02:00
70d7d81956 fix warnings
All checks were successful
Linux arm64 / Build (push) Successful in 47s
2025-05-17 18:52:45 +02:00
646eb6492e feat: working castling
All checks were successful
Linux arm64 / Build (push) Successful in 47s
2025-05-17 18:51:23 +02:00
01f0caf672 feat: working pgn simulation
All checks were successful
Linux arm64 / Build (push) Successful in 56s
2025-05-17 17:52:30 +02:00
e52988e511 disable castling buttons
Some checks failed
Linux arm64 / Build (push) Failing after 22s
2025-05-17 17:22:55 +02:00
6bc164937b prevent concurrent modification 2025-05-17 17:22:23 +02:00
e37da34f68 Merge pull request 'dev' (#12) from dev into main
All checks were successful
Linux arm64 / Build (push) Successful in 49s
Reviewed-on: #12
2025-05-17 15:05:23 +00:00
97d11f7105 suppres warning
All checks were successful
Linux arm64 / Build (push) Successful in 38s
2025-05-17 17:04:40 +02:00
1da185678c test: don't wait on game (when possible)
All checks were successful
Linux arm64 / Build (push) Successful in 38s
2025-05-17 17:03:30 +02:00
90daf662ea add CommanderSender (Fixes #9)
All checks were successful
Linux arm64 / Build (push) Successful in 36s
2025-05-17 16:48:04 +02:00
b33e333276 merge branch 3dunstable
All checks were successful
Linux arm64 / Build (push) Successful in 38s
2025-05-17 15:58:36 +02:00
17537e09c6 removes old models
All checks were successful
Linux arm64 / Build (push) Successful in 46s
2025-05-17 15:31:56 +02:00
c80805fc66 refactor: fbo 2025-05-17 12:38:29 +02:00
0a1582cf07 round fps 2025-05-17 12:37:57 +02:00
96945d949a remove redundant interface 2025-05-17 10:22:07 +02:00
fl.du.pr Grens
459e458028 feat: popups 2025-05-16 17:53:01 +02:00
fl.du.pr Grens
6cec5d9e31 feat: spinning turn 2025-05-16 16:20:48 +02:00
l
94870e65c2 imgui (#10)
Co-authored-by: Persson-dev <sim16.prib@gmail.com>
Reviewed-on: #10
2025-05-16 13:35:38 +00:00
9ff433c257 fix null error 2025-05-16 10:44:02 +02:00
dba8e8fb1e feat: add imgui 2025-05-16 10:41:33 +02:00
Janet-Doe
39c7ebefe6 fix coloring bug on double click 2025-05-14 16:46:12 +02:00
Janet-Doe
264391ba81 functional move in 3D view 2025-05-14 15:57:01 +02:00
Janet-Doe
ce977c3b48 debug 2025-05-14 15:56:08 +02:00
Janet-Doe
046f680937 onclick functional 2025-05-14 10:10:14 +02:00
11e6a94ed7 rules package
All checks were successful
Linux arm64 / Build (push) Successful in 47s
2025-05-10 18:09:45 +02:00
c1bf9bcaf9 remove PawnIdentifier 2025-05-10 18:08:55 +02:00
810a0f2159 feat: add ai castling (Fixes #4)
All checks were successful
Linux arm64 / Build (push) Successful in 41s
2025-05-06 17:15:55 +02:00
58f02f681c rename GameAdapter
All checks were successful
Linux arm64 / Build (push) Successful in 51s
2025-05-06 11:53:26 +02:00
83d5737789 You spin me right 'round, baby, right 'round 2025-05-06 09:55:48 +02:00
271ff420a2 small refactor
All checks were successful
Linux arm64 / Build (push) Successful in 46s
2025-05-04 11:37:15 +02:00
120de055a9 chore: bump junit version to 5.11.4 2025-05-04 10:57:05 +02:00
e391e703e1 feat: add actions
All checks were successful
Linux arm64 / Build (push) Successful in 1m50s
2025-05-04 10:29:29 +02:00
a595b62fca refactor misc 2025-05-03 20:49:03 +02:00
b18b53f195 feat: pgn parser 2025-05-03 20:46:20 +02:00
b2a6b23681 fix: lastmove cache bug 2025-05-03 18:36:38 +02:00
16ea1700d8 refactor: alphabeta time printing 2025-05-02 19:56:00 +02:00
64bed4ea3a refactor: add signals to alphabetaai 2025-05-02 19:47:03 +02:00
2a3ea10389 fix warnings 2025-04-30 20:43:44 +02:00
3b225d9e23 feat: add audio 2025-04-30 20:41:10 +02:00
3b38e0da1f Super IA (#5)
Reviewed-on: #5
Co-authored-by: Persson-dev <sim16.prib@gmail.com>
Co-committed-by: Persson-dev <sim16.prib@gmail.com>
2025-04-30 18:28:01 +00:00
ecb0fdc623 Begin Audio (#7)
Reviewed-on: #7
Co-authored-by: Persson-dev <sim16.prib@gmail.com>
Co-committed-by: Persson-dev <sim16.prib@gmail.com>
2025-04-30 18:27:30 +00:00
ffd77d9777 world: add ejectPiece 2025-04-30 19:16:27 +02:00
ec98b05d61 free opengl resources 2025-04-28 18:23:29 +02:00
0fb24263e0 camel case 2025-04-28 18:08:10 +02:00
533e6260d5 refactor movePiece 2025-04-28 18:06:37 +02:00
0c6ab1df4b add 3d position cache 2025-04-28 17:52:29 +02:00
5f70daea91 board model add constants 2025-04-28 17:32:24 +02:00
dd043794d6 add comment 2025-04-28 17:31:46 +02:00
de4ed869ea feat: change board cells color 2025-04-28 11:04:41 +02:00
b57fa1482b add small example 2025-04-27 20:18:31 +02:00
cbbce43ede raycast cursor 2025-04-27 20:12:38 +02:00
1b61eca58b models: remove uv 2025-04-27 11:02:55 +02:00
9bc09cf812 add world interface 2025-04-27 11:00:14 +02:00
c488f3b4e0 rename PieceModel 2025-04-27 10:14:05 +02:00
9f35bd3c30 opengl package 2025-04-27 10:12:52 +02:00
6ca5d1294f better light 2025-04-26 19:24:52 +02:00
b62dcffcb1 refactor 2025-04-26 18:18:27 +02:00
65c904478f enable face culling 2025-04-26 17:26:27 +02:00
f8ae19fee8 set pieces color 2025-04-26 12:35:37 +02:00
1b22de17d8 change background color 2025-04-26 12:30:29 +02:00
24104fedf5 fix pieces rotation 2025-04-26 12:23:14 +02:00
5b6fce11bc refactor board model 2025-04-26 12:20:00 +02:00
ab309ae48a fix aspectRatio 2025-04-26 12:14:22 +02:00
7af9807127 omg ça marche 2025-04-26 12:00:48 +02:00
cff2d92070 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 2025-04-25 17:03:28 +02:00
098b605799 yodaaaaaa 2025-04-24 11:12:53 +02:00
ce0424ff8b osekour 2025-04-22 17:05:26 +02:00
4cbad80aaf begin 3d 2025-04-22 15:02:24 +02:00
4bd16026e1 begin 3d 2025-04-22 15:02:24 +02:00
2b5c2ad663 fix gradle input 2025-04-22 14:44:57 +02:00
Janet-Doe
0e2b07741d addo en passant test 2025-04-22 14:42:13 +02:00
b83726925e fix console 2025-04-18 10:33:10 +02:00
30eb12145d add hungry ai 2025-04-17 08:53:21 +02:00
17ef882d44 remove board from constructor 2025-04-16 19:29:31 +02:00
bee5e613bb refactor dumb ai castling 2025-04-16 19:25:04 +02:00
923ace22f1 add general ai 2025-04-16 19:21:47 +02:00
110 changed files with 3910 additions and 1304 deletions

4
.gitattributes vendored
View File

@@ -3,7 +3,7 @@
#
# Linux start script should use lf
/gradlew text eol=lf
# These are Windows script files and should use crlf
*.bat text eol=crlf
*.glb filter=lfs diff=lfs merge=lfs -text
*.fbx filter=lfs diff=lfs merge=lfs -text

25
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Linux arm64
run-name: Build And Test
on: [push]
jobs:
Build:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build with Gradle
run: ./gradlew assemble
- name: Test
run: ./gradlew test

5
.gitignore vendored
View File

@@ -6,4 +6,7 @@ build
app/bin
.vscode
.vscode
*.wav
imgui.ini

View File

@@ -16,8 +16,10 @@ repositories {
mavenCentral()
}
def os = "linux";
def lwjgl_version = "3.3.6"
def lwjgl_natives = "natives-linux"
def lwjgl_natives = "natives-$os"
def imgui_version = "1.87.0"
dependencies {
// Use JUnit Jupiter for testing.
@@ -26,11 +28,17 @@ dependencies {
implementation "org.lwjgl:lwjgl:$lwjgl_version"
implementation "org.lwjgl:lwjgl-opengl:$lwjgl_version"
implementation "org.lwjgl:lwjgl-glfw:$lwjgl_version"
implementation "org.lwjgl:lwjgl-assimp:$lwjgl_version"
implementation "org.joml:joml:1.10.8"
implementation "org.lwjgl:lwjgl::$lwjgl_natives"
implementation "org.lwjgl:lwjgl-opengl::$lwjgl_natives"
implementation "org.lwjgl:lwjgl-glfw::$lwjgl_natives"
implementation "org.lwjgl:lwjgl-assimp::$lwjgl_natives"
implementation "io.github.spair:imgui-java-binding:$imgui_version"
implementation "io.github.spair:imgui-java-natives-$os:$imgui_version"
implementation "io.github.spair:imgui-java-app:$imgui_version"
}
application {
@@ -45,6 +53,10 @@ jar {
}
}
run {
standardInput = System.in
}
tasks.named('test') {
// Use JUnit Platform for unit tests.
useJUnitPlatform()

View File

@@ -1,23 +1,30 @@
package chess;
import chess.view.consolerender.Colors;
import java.util.Scanner;
import chess.view.consolerender.Colors;
public class App {
public static void main(String[] args) {
System.out.println(Colors.RED + "Credits: Grenier Lilas, Pribylski Simon." + Colors.RESET);
System.out.println("""
Pick the version to use:
1 - Console
2 - Window.""");
switch (new Scanner(System.in).nextLine()) {
2 - Window
3 - 3D.""");
Scanner scan = new Scanner(System.in);
String line = scan.nextLine();
scan.close();
switch (line) {
case "1", "Console", "console":
ConsoleMain.main(args);
break;
case "2", "Window", "window":
SwingMain.main(args);
break;
case "3", "3D", "3d":
OpenGLMain.main(args);
break;
default:
System.out.println("Invalid input");
break;

View File

@@ -5,26 +5,17 @@ package chess;
import chess.controller.CommandExecutor;
import chess.controller.commands.NewGameCommand;
import chess.model.ChessBoard;
import chess.model.Game;
import chess.simulator.PromoteTest;
import chess.view.consolerender.Console;
public class ConsoleMain {
public static void main(String[] args) {
Game game = new Game(new ChessBoard());
Game game = new Game();
CommandExecutor commandExecutor = new CommandExecutor(game);
PromoteTest promoteTest = new PromoteTest(commandExecutor);
commandExecutor.addListener(promoteTest);
Console console = new Console(commandExecutor);
commandExecutor.addListener(console);
promoteTest.onComplete.connect(() -> {
console.setCaptureInput(true);
});
commandExecutor.executeCommand(new NewGameCommand());
}
}

View File

@@ -1,33 +1,26 @@
package chess;
import chess.ai.DumbAI;
import chess.controller.CommandExecutor;
import chess.controller.commands.NewGameCommand;
import chess.controller.event.GameAdaptator;
import chess.model.ChessBoard;
import chess.model.Color;
import chess.model.Game;
import chess.pgn.PgnExport;
import chess.view.render.Window;
import chess.pgn.PgnFileSimulator;
import chess.view.DDDrender.DDDView;
public class OpenGLMain {
public static void main(String[] args) {
Game game = new Game(new ChessBoard());
public static void main(String[] args) {
Game game = new Game();
CommandExecutor commandExecutor = new CommandExecutor(game);
Window window = new Window(commandExecutor);
commandExecutor.addListener(window);
// DumbAI ai = new DumbAI(commandExecutor, Color.Black);
// commandExecutor.addListener(ai);
// DumbAI ai2 = new DumbAI(commandExecutor, Color.White);
// commandExecutor.addListener(ai2);
PgnFileSimulator fileSimulator = new PgnFileSimulator(commandExecutor, "games/FoolCheckmate.pgn");
DDDView ddd = new DDDView(commandExecutor);
commandExecutor.addListener(ddd);
commandExecutor.addListener(fileSimulator);
commandExecutor.executeCommand(new NewGameCommand());
window.run();
ddd.run();
commandExecutor.close();
}
}

View File

@@ -1,36 +1,45 @@
package chess;
import chess.ai.DumbAI;
import chess.ai.minimax.AlphaBetaAI;
import chess.ai.minimax.AlphaBetaConsolePrinter;
import chess.controller.CommandExecutor;
import chess.controller.commands.NewGameCommand;
import chess.controller.event.GameAdaptator;
import chess.model.ChessBoard;
import chess.controller.event.GameAdapter;
import chess.model.Color;
import chess.model.Game;
import chess.pgn.PgnExport;
import chess.view.audio.GameAudio;
import chess.view.simplerender.Window;
public class SwingMain {
public static void main(String[] args) {
Game game = new Game(new ChessBoard());
Game game = new Game();
CommandExecutor commandExecutor = new CommandExecutor(game);
Window window = new Window(commandExecutor, false);
Window window = new Window(commandExecutor, true);
commandExecutor.addListener(window);
DumbAI ai = new DumbAI(commandExecutor, Color.Black);
AlphaBetaAI ai = new AlphaBetaAI(commandExecutor, Color.Black, 5);
commandExecutor.addListener(ai);
DumbAI ai2 = new DumbAI(commandExecutor, Color.White);
commandExecutor.addListener(ai2);
AlphaBetaConsolePrinter aiResults = new AlphaBetaConsolePrinter(ai);
aiResults.connect();
commandExecutor.addListener(new GameAdaptator(){
// AI ai2 = new AlphaBetaAI(commandExecutor, Color.White, 5);
// commandExecutor.addListener(ai2);
// Window window2 = new Window(ai2.getSimulation(), false);
// ai2.getSimulation().addListener(window2);
commandExecutor.addListener(new GameAdapter(){
@Override
public void onGameEnd() {
System.out.println(PgnExport.exportGame(game));
}
});
commandExecutor.addListener(new GameAudio());
commandExecutor.executeCommand(new NewGameCommand());
}
}

View File

@@ -0,0 +1,41 @@
package chess.ai;
import java.util.List;
import chess.ai.actions.AIAction;
import chess.ai.actions.AIActions;
import chess.controller.CommandExecutor;
import chess.controller.event.GameAdapter;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Piece;
public abstract class AI extends GameAdapter {
protected final CommandExecutor commandExecutor;
protected final Color color;
public AI(CommandExecutor commandExecutor, Color color) {
this.commandExecutor = commandExecutor;
this.color = color;
}
protected abstract void play();
@Override
public void onPlayerTurn(Color color, boolean undone) {
if (this.color != color || undone)
return;
play();
}
protected List<AIAction> getAllowedActions() {
return AIActions.getAllowedActions(this.commandExecutor);
}
protected Piece pieceAt(Coordinate coordinate) {
return AIActions.pieceAt(coordinate, this.commandExecutor);
}
}

View File

@@ -3,100 +3,25 @@ package chess.ai;
import java.util.List;
import java.util.Random;
import chess.controller.Command;
import chess.ai.actions.AIAction;
import chess.controller.CommandExecutor;
import chess.controller.commands.CastlingCommand;
import chess.controller.commands.GetAllowedCastlingsCommand;
import chess.controller.commands.GetPieceAtCommand;
import chess.controller.commands.GetPlayerMovesCommand;
import chess.controller.commands.MoveCommand;
import chess.controller.commands.PromoteCommand;
import chess.controller.commands.GetAllowedCastlingsCommand.CastlingResult;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.controller.event.GameAdaptator;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
import chess.model.Piece;
public class DumbAI extends GameAdaptator {
public class DumbAI extends AI {
private final Color player;
private final CommandExecutor commandExecutor;
private final Random random = new Random();
public DumbAI(CommandExecutor commandExecutor, Color color) {
this.player = color;
this.commandExecutor = commandExecutor;
super(commandExecutor, color);
}
@Override
public void onPlayerTurn(Color color) {
if (color != player)
return;
protected void play() {
List<AIAction> actions = getAllowedActions();
GetPlayerMovesCommand cmd = new GetPlayerMovesCommand();
sendCommand(cmd);
int randomAction = this.random.nextInt(actions.size());
GetAllowedCastlingsCommand cmd2 = new GetAllowedCastlingsCommand();
sendCommand(cmd2);
CastlingResult castlings = cmd2.getCastlingResult();
List<Move> moves = cmd.getMoves();
switch (castlings) {
case Both: {
int randomMove = this.random.nextInt(moves.size() + 2);
if (randomMove < moves.size() - 2)
break;
this.commandExecutor.executeCommand(new CastlingCommand(randomMove == moves.size()));
return;
}
case Small: {
int randomMove = this.random.nextInt(moves.size() + 1);
if (randomMove != moves.size())
break;
this.commandExecutor.executeCommand(new CastlingCommand(false));
return;
}
case Big: {
int randomMove = this.random.nextInt(moves.size() + 1);
if (randomMove != moves.size())
break;
this.commandExecutor.executeCommand(new CastlingCommand(true));
return;
}
default:
break;
}
int randomMove = this.random.nextInt(moves.size());
this.commandExecutor.executeCommand(new MoveCommand(moves.get(randomMove)));
}
@Override
public void onPromotePawn(Coordinate pieceCoords) {
Piece pawn = pieceAt(pieceCoords);
if (pawn.getColor() != this.player)
return;
int promote = this.random.nextInt(PromoteType.values().length);
this.commandExecutor.executeCommand(new PromoteCommand(PromoteType.values()[promote]));
}
private Piece pieceAt(Coordinate coordinate) {
GetPieceAtCommand command = new GetPieceAtCommand(coordinate);
sendCommand(command);
return command.getPiece();
}
private void sendCommand(Command command) {
this.commandExecutor.executeCommand(command);
actions.get(randomAction).applyAction();
}
}

View File

@@ -0,0 +1,54 @@
package chess.ai;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import chess.ai.actions.AIAction;
import chess.ai.actions.AIActionMove;
import chess.controller.CommandExecutor;
import chess.model.Color;
import chess.model.Move;
import chess.model.Piece;
public class HungryAI extends AI {
private final PieceCost pieceCost;
private final Random random;
public HungryAI(CommandExecutor commandExecutor, Color color) {
super(commandExecutor, color);
this.pieceCost = new PieceCost(color);
this.random = new Random();
}
private int getMoveCost(Move move) {
Piece piece = pieceAt(move.getDeadPieceCoords());
return -(int) pieceCost.getCost(piece);
}
private List<AIAction> getBestMoves() {
List<AIAction> actions = getAllowedActions();
List<AIAction> bestMoves = new ArrayList<>();
int bestCost = 0;
for (AIAction action : actions) {
if (action instanceof AIActionMove move) {
int moveCost = getMoveCost(move.getMove());
if (moveCost == bestCost) {
bestMoves.add(move);
} else if (moveCost > bestCost) {
bestMoves.clear();
bestMoves.add(move);
bestCost = moveCost;
}
}
}
return bestMoves;
}
@Override
protected void play() {
List<AIAction> bestMoves = getBestMoves();
bestMoves.get(this.random.nextInt(bestMoves.size())).applyAction();
}
}

View File

@@ -0,0 +1,67 @@
package chess.ai;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
import chess.model.pieces.Bishop;
import chess.model.pieces.King;
import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
public class PieceCost implements PieceVisitor<Float> {
private final Color player;
public static final float BISHOP = 30;
public static final float KING = 900;
public static final float KNIGHT = 30;
public static final float PAWN = 10;
public static final float QUEEN = 90;
public static final float ROOK = 50;
public PieceCost(Color color) {
this.player = color;
}
public float getCost(Piece piece) {
if (piece == null)
return 0;
float cost = visit(piece);
if (piece.getColor() != player)
cost = -cost;
return cost;
}
@Override
public Float visitPiece(Bishop bishop) {
return BISHOP;
}
@Override
public Float visitPiece(King king) {
return KING;
}
@Override
public Float visitPiece(Knight knight) {
return KNIGHT;
}
@Override
public Float visitPiece(Pawn pawn) {
return PAWN;
}
@Override
public Float visitPiece(Queen queen) {
return QUEEN;
}
@Override
public Float visitPiece(Rook rook) {
return ROOK;
}
}

View File

@@ -0,0 +1,129 @@
package chess.ai;
import java.util.Arrays;
import java.util.List;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Piece;
import chess.model.PieceVisitor;
import chess.model.pieces.Bishop;
import chess.model.pieces.King;
import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
public class PiecePosCost implements PieceVisitor<List<Float>> {
private final Color color;
private static final List<Float> BISHOP = Arrays.asList(
-2.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -2.0f,
-1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f,
-1.0f, 0.0f, 0.5f, 1.0f, 1.0f, 0.5f, 0.0f, -1.0f,
-1.0f, 0.5f, 0.5f, 1.0f, 1.0f, 0.5f, 0.5f, -1.0f,
-1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, -1.0f,
-1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f,
-1.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, -1.0f,
-2.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -2.0f);
private static final List<Float> KING = Arrays.asList(
-3.0f, -4.0f, -4.0f, -5.0f, -5.0f, -4.0f, -4.0f, -3.0f,
-3.0f, -4.0f, -4.0f, -5.0f, -5.0f, -4.0f, -4.0f, -3.0f,
-3.0f, -4.0f, -4.0f, -5.0f, -5.0f, -4.0f, -4.0f, -3.0f,
-3.0f, -4.0f, -4.0f, -5.0f, -5.0f, -4.0f, -4.0f, -3.0f,
-2.0f, -3.0f, -3.0f, -4.0f, -4.0f, -3.0f, -3.0f, -2.0f,
-1.0f, -2.0f, -2.0f, -2.0f, -2.0f, -2.0f, -2.0f, -1.0f,
2.0f, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.0f, 2.0f,
2.0f, 3.0f, 1.0f, 0.0f, 0.0f, 1.0f, 3.0f, 2.0f);
private static final List<Float> KNIGHT = Arrays.asList(
-5.0f, -4.0f, -3.0f, -3.0f, -3.0f, -3.0f, -4.0f, -5.0f,
-4.0f, -2.0f, 0.0f, 0.0f, 0.0f, 0.0f, -2.0f, -4.0f,
-3.0f, 0.0f, 1.0f, 1.5f, 1.5f, 1.0f, 0.0f, -3.0f,
-3.0f, 0.5f, 1.5f, 2.0f, 2.0f, 1.5f, 0.5f, -3.0f,
-3.0f, 0.0f, 1.5f, 2.0f, 2.0f, 1.5f, 0.0f, -3.0f,
-3.0f, 0.5f, 1.0f, 1.5f, 1.5f, 1.0f, 0.5f, -3.0f,
-4.0f, -2.0f, 0.0f, 0.5f, 0.5f, 0.0f, -2.0f, -4.0f,
-5.0f, -4.0f, -3.0f, -3.0f, -3.0f, -3.0f, -4.0f, -5.0f);
private static final List<Float> PAWN = Arrays.asList(
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
5.0f, 5.0f, 5.0f, 5.0f, 5.0f, 5.0f, 5.0f, 5.0f,
1.0f, 1.0f, 2.0f, 3.0f, 3.0f, 2.0f, 1.0f, 1.0f,
0.5f, 0.5f, 1.0f, 2.5f, 2.5f, 1.0f, 0.5f, 0.5f,
0.0f, 0.0f, 1.0f, 2.0f, 2.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -1.0f, 0.0f, 0.0f, -1.0f, -0.5f, 0.5f,
0.5f, 1.0f, 1.0f, -2.0f, -2.0f, 1.0f, 1.0f, 0.5f,
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
private static final List<Float> QUEEN = Arrays.asList(
-2.0f, -1.0f, -1.0f, -0.5f, -0.5f, -1.0f, -1.0f, -2.0f,
-1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f,
-1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, -1.0f,
-0.5f, 0.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, -0.5f,
0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, -0.5f,
-1.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, -1.0f,
-1.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f,
-2.0f, -1.0f, -1.0f, -0.5f, -0.5f, -1.0f, -1.0f, -2.0f);
private static final List<Float> ROOK = Arrays.asList(
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.5f,
-0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f,
-0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f,
-0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f,
-0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f,
-0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f,
0.0f, 0.0f, 0.0f, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f);
public PiecePosCost(Color color) {
this.color = color;
}
public float getEvaluation(Piece piece, Coordinate coordinate) {
if (piece == null)
return 0;
List<Float> positions = visit(piece);
int x = piece.getColor() == Color.Black ? (Coordinate.VALUE_MAX - 1 - coordinate.getX()) : coordinate.getX();
int y = piece.getColor() == Color.Black ? (Coordinate.VALUE_MAX - 1 - coordinate.getY()) : coordinate.getY();
Coordinate newCoords = new Coordinate(x, y);
assert newCoords.isValid();
float result = positions.get(newCoords.toIndex());
if (piece.getColor() != color)
return -result;
return result;
}
@Override
public List<Float> visitPiece(Bishop bishop) {
return BISHOP;
}
@Override
public List<Float> visitPiece(King king) {
return KING;
}
@Override
public List<Float> visitPiece(Knight knight) {
return KNIGHT;
}
@Override
public List<Float> visitPiece(Pawn pawn) {
return PAWN;
}
@Override
public List<Float> visitPiece(Queen queen) {
return QUEEN;
}
@Override
public List<Float> visitPiece(Rook rook) {
return ROOK;
}
}

View File

@@ -0,0 +1,33 @@
package chess.ai.actions;
import chess.controller.CommandExecutor;
import chess.controller.CommandSender;
import chess.controller.commands.UndoCommand;
public abstract class AIAction implements CommandSender {
private final CommandExecutor commandExecutor;
public AIAction(CommandExecutor commandExecutor) {
this.commandExecutor = commandExecutor;
}
@Override
public CommandExecutor getCommandExecutor() {
return this.commandExecutor;
}
public void undoAction(CommandExecutor commandExecutor) {
sendCommand(new UndoCommand(), commandExecutor);
}
public void undoAction() {
undoAction(this.commandExecutor);
}
public void applyAction() {
applyAction(this.commandExecutor);
}
public abstract void applyAction(CommandExecutor commandExecutor);
}

View File

@@ -0,0 +1,20 @@
package chess.ai.actions;
import chess.controller.CommandExecutor;
import chess.controller.commands.CastlingCommand;
public class AIActionCastling extends AIAction{
private final boolean bigCastling;
public AIActionCastling(CommandExecutor commandExecutor, boolean bigCastling) {
super(commandExecutor);
this.bigCastling = bigCastling;
}
@Override
public void applyAction(CommandExecutor commandExecutor) {
sendCommand(new CastlingCommand(this.bigCastling), commandExecutor);
}
}

View File

@@ -0,0 +1,25 @@
package chess.ai.actions;
import chess.controller.CommandExecutor;
import chess.controller.commands.MoveCommand;
import chess.model.Move;
public class AIActionMove extends AIAction{
private final Move move;
public AIActionMove(CommandExecutor commandExecutor, Move move) {
super(commandExecutor);
this.move = move;
}
public Move getMove() {
return move;
}
@Override
public void applyAction(CommandExecutor commandExecutor) {
sendCommand(new MoveCommand(move), commandExecutor);
}
}

View File

@@ -0,0 +1,26 @@
package chess.ai.actions;
import chess.controller.CommandExecutor;
import chess.controller.commands.MoveCommand;
import chess.controller.commands.PromoteCommand;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.model.Move;
public class AIActionMoveAndPromote extends AIAction{
private final Move move;
private final PromoteType promoteType;
public AIActionMoveAndPromote(CommandExecutor commandExecutor, Move move, PromoteType promoteType) {
super(commandExecutor);
this.move = move;
this.promoteType = promoteType;
}
@Override
public void applyAction(CommandExecutor commandExecutor) {
sendCommand(new MoveCommand(move), commandExecutor);
sendCommand(new PromoteCommand(promoteType), commandExecutor);
}
}

View File

@@ -0,0 +1,87 @@
package chess.ai.actions;
import java.util.ArrayList;
import java.util.List;
import chess.controller.Command;
import chess.controller.Command.CommandResult;
import chess.controller.CommandExecutor;
import chess.controller.commands.GetAllowedCastlingsCommand;
import chess.controller.commands.GetAllowedCastlingsCommand.CastlingResult;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.controller.commands.GetPieceAtCommand;
import chess.controller.commands.GetPlayerMovesCommand;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
import chess.model.Piece;
import chess.model.pieces.Pawn;
public class AIActions {
public static List<AIAction> getAllowedActions(CommandExecutor commandExecutor) {
List<Move> moves = getAllowedMoves(commandExecutor);
CastlingResult castlingResult = getAllowedCastlings(commandExecutor);
List<AIAction> actions = new ArrayList<>(moves.size() + 10);
for (Move move : moves) {
Piece movingPiece = pieceAt(move.getStart(), commandExecutor);
if (movingPiece instanceof Pawn) {
int enemyLineY = movingPiece.getColor() == Color.White ? 0 : 7;
if (move.getFinish().getY() == enemyLineY) {
PromoteType[] promotes = PromoteType.values();
for (PromoteType promote : promotes) {
actions.add(new AIActionMoveAndPromote(commandExecutor, move, promote));
}
continue;
}
}
actions.add(new AIActionMove(commandExecutor, move));
}
switch (castlingResult) {
case Both:
actions.add(new AIActionCastling(commandExecutor, true));
actions.add(new AIActionCastling(commandExecutor, false));
break;
case Small:
actions.add(new AIActionCastling(commandExecutor, false));
break;
case Big:
actions.add(new AIActionCastling(commandExecutor, true));
break;
case None:
break;
}
return actions;
}
private static CastlingResult getAllowedCastlings(CommandExecutor commandExecutor) {
GetAllowedCastlingsCommand cmd2 = new GetAllowedCastlingsCommand();
sendCommand(cmd2, commandExecutor);
return cmd2.getCastlingResult();
}
private static List<Move> getAllowedMoves(CommandExecutor commandExecutor) {
GetPlayerMovesCommand cmd = new GetPlayerMovesCommand();
sendCommand(cmd, commandExecutor);
return cmd.getMoves();
}
private static CommandResult sendCommand(Command command, CommandExecutor commandExecutor) {
CommandResult result = commandExecutor.executeCommand(command);
assert result != CommandResult.NotAllowed : "Command not allowed!";
return result;
}
public static Piece pieceAt(Coordinate coordinate, CommandExecutor commandExecutor) {
GetPieceAtCommand command = new GetPieceAtCommand(coordinate);
commandExecutor.executeCommand(command);
return command.getPiece();
}
}

View File

@@ -0,0 +1,83 @@
package chess.ai.minimax;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import chess.ai.AI;
import chess.ai.actions.AIAction;
import chess.controller.CommandExecutor;
import chess.model.Color;
import common.Signal1;
public class AlphaBetaAI extends AI {
private final int searchDepth;
private static final float MAX_FLOAT = Float.MAX_VALUE;
private static final float MIN_FLOAT = -MAX_FLOAT;
private final ExecutorService threadPool;
public final Signal1<Integer> onStartEval = new Signal1<>();
public final Signal1<Float> onCompleteEval = new Signal1<>();
public final Signal1<Float> onProgress = new Signal1<>();
public AlphaBetaAI(CommandExecutor commandExecutor, Color color, int searchDepth) {
super(commandExecutor, color);
this.searchDepth = searchDepth;
int threadCount = Runtime.getRuntime().availableProcessors();
this.threadPool = Executors.newFixedThreadPool(threadCount,
new AlphaBetaThreadCreator(commandExecutor, color, threadCount));
}
private AIAction getBestMove() {
List<AIAction> actions = getAllowedActions();
List<Future<Float>> moveEvaluations = new ArrayList<>(actions.size());
float bestMoveValue = MIN_FLOAT;
AIAction bestMove = null;
this.onStartEval.emit(actions.size());
for (AIAction action : actions) {
moveEvaluations.add(this.threadPool.submit(() -> {
return AlphaBetaThreadCreator.getMoveValue(action, this.searchDepth);
}));
}
for (int i = 0; i < actions.size(); i++) {
this.onProgress.emit((float) i / (float) actions.size());
AIAction action = actions.get(i);
float value = MIN_FLOAT;
try {
value = moveEvaluations.get(i).get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
if (value > bestMoveValue) {
bestMoveValue = value;
bestMove = action;
}
}
this.onCompleteEval.emit(bestMoveValue);
return bestMove;
}
@Override
public void onGameEnd() {
this.threadPool.close();
}
@Override
protected void play() {
AIAction move = getBestMove();
move.applyAction();
}
}

View File

@@ -0,0 +1,27 @@
package chess.ai.minimax;
public class AlphaBetaConsolePrinter {
private final AlphaBetaAI ai;
private long lastTime;
public void connect() {
ai.onStartEval.connect((moveCount) -> {
this.lastTime = System.currentTimeMillis();
System.out.println("Evaluating " + moveCount + " moves ...");
});
ai.onProgress.connect((progress) -> {
System.out.printf("Progress : %.2f %% \r", progress * 100.0f);
});
ai.onCompleteEval.connect((bestMove) -> {
System.out.println("Best move : " + bestMove + " ");
System.out.println("Took " + (System.currentTimeMillis() - this.lastTime) + "ms");
});
}
public AlphaBetaConsolePrinter(AlphaBetaAI ai) {
this.ai = ai;
}
}

View File

@@ -0,0 +1,101 @@
package chess.ai.minimax;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import chess.ai.PieceCost;
import chess.ai.PiecePosCost;
import chess.ai.actions.AIAction;
import chess.model.ChessBoard;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Piece;
public class AlphaBetaThread extends Thread {
private final GameSimulation simulation;
private final PieceCost pieceCost;
private final PiecePosCost piecePosCost;
private static final int GREAT_MOVE = 9999;
private static final float MAX_FLOAT = Float.MAX_VALUE;
private static final float MIN_FLOAT = -MAX_FLOAT;
public AlphaBetaThread(Runnable task, GameSimulation simulation, Color color) {
super(task);
this.simulation = simulation;
this.pieceCost = new PieceCost(color);
this.piecePosCost = new PiecePosCost(color);
}
private float getEndGameEvaluation() {
Color currentTurn = this.simulation.getPlayerTurn();
if (this.simulation.getBoard().isKingInCheck(currentTurn))
return GREAT_MOVE;
return getBoardEvaluation() - PieceCost.PAWN;
}
private float getBoardEvaluation() {
final ChessBoard board = this.simulation.getBoard();
float result = 0;
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
Coordinate coordinate = new Coordinate(i, j);
Piece piece = board.pieceAt(coordinate);
result += pieceCost.getCost(piece) + piecePosCost.getEvaluation(piece, coordinate);
}
}
return result;
}
public float getMoveValue(AIAction move, int searchDepth) {
move.applyAction(this.simulation.getCommandExecutor());
float value = -negaMax(searchDepth - 1, MIN_FLOAT, MAX_FLOAT);
move.undoAction(this.simulation.getCommandExecutor());
return value;
}
private float negaMax(int depth, float alpha, float beta) {
float value = MIN_FLOAT;
List<AIAction> moves = this.simulation.getAllowedActions();
if (moves.isEmpty())
return -getEndGameEvaluation();
List<Entry<AIAction, Float>> movesCost = new ArrayList<>(moves.size());
for (AIAction move : moves) {
move.applyAction();
movesCost.add(Map.entry(move, -getBoardEvaluation()));
move.undoAction();
}
Collections.sort(movesCost, (first, second) -> {
return Float.compare(first.getValue(), second.getValue());
});
if (depth == 1)
return -movesCost.getFirst().getValue();
for (var moveEntry : movesCost) {
AIAction move = moveEntry.getKey();
move.applyAction();
value = Float.max(value, -negaMax(depth - 1, -beta, -alpha));
move.undoAction();
alpha = Float.max(alpha, value);
if (alpha >= beta)
return value;
}
return value;
}
public GameSimulation getSimulation() {
return simulation;
}
}

View File

@@ -0,0 +1,36 @@
package chess.ai.minimax;
import java.util.concurrent.ThreadFactory;
import chess.ai.actions.AIAction;
import chess.controller.CommandExecutor;
import chess.model.Color;
public class AlphaBetaThreadCreator implements ThreadFactory{
private final Color color;
private final GameSimulation simulations[];
private int currentThread = 0;
public AlphaBetaThreadCreator(CommandExecutor commandExecutor, Color color, int threadCount) {
this.color = color;
simulations = new GameSimulation[threadCount];
for (int i = 0; i < threadCount; i++) {
simulations[i] = new GameSimulation();
commandExecutor.addListener(simulations[i]);
}
}
public static float getMoveValue(AIAction move, int searchDepth) {
AlphaBetaThread t = (AlphaBetaThread) Thread.currentThread();
return t.getMoveValue(move, searchDepth);
}
@Override
public Thread newThread(Runnable r) {
AlphaBetaThread t = new AlphaBetaThread(r, simulations[currentThread], color);
currentThread++;
return t;
}
}

View File

@@ -0,0 +1,81 @@
package chess.ai.minimax;
import java.util.List;
import chess.ai.actions.AIAction;
import chess.ai.actions.AIActions;
import chess.controller.CommandExecutor;
import chess.controller.CommandSender;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.controller.event.EmptyGameDispatcher;
import chess.controller.event.GameAdapter;
import chess.model.ChessBoard;
import chess.model.Color;
import chess.model.Game;
import chess.model.Move;
import chess.model.PermissiveGame;
public class GameSimulation extends GameAdapter implements CommandSender {
private final CommandExecutor simulation;
private final Game gameSimulation;
public GameSimulation() {
this.gameSimulation = new PermissiveGame();
this.simulation = new CommandExecutor(gameSimulation, new EmptyGameDispatcher());
}
@Override
public void onPawnPromoted(PromoteType promotion) {
sendPawnPromotion(promotion);
}
@Override
public void onCastling(boolean bigCastling, Move kingMove, Move rookMove) {
if (bigCastling)
sendBigCastling();
else
sendCastling();
}
@Override
public void onMove(Move move, boolean captured) {
sendMove(move);
}
@Override
public void onGameStart() {
sendStartGame();
}
@Override
public void onPlayerTurn(Color color, boolean undone) {
if (undone)
sendUndo();
}
public CommandExecutor getCommandExecutor() {
return simulation;
}
public Game getGame() {
return gameSimulation;
}
public ChessBoard getBoard() {
return this.gameSimulation.getBoard();
}
public Color getPlayerTurn() {
return this.gameSimulation.getPlayerTurn();
}
public List<AIAction> getAllowedActions() {
return AIActions.getAllowedActions(this.simulation);
}
public void close() {
this.simulation.close();
}
}

View File

@@ -1,7 +1,10 @@
package chess.controller;
import java.util.List;
import chess.controller.Command.CommandResult;
import chess.controller.commands.UndoCommand;
import chess.controller.event.AsyncGameDispatcher;
import chess.controller.event.GameDispatcher;
import chess.controller.event.GameListener;
import chess.model.Game;
@@ -17,9 +20,9 @@ public class CommandExecutor {
}
public CommandExecutor(Game game) {
this(game, new GameDispatcher());
this(game, new AsyncGameDispatcher());
}
public CommandExecutor(Game game, GameDispatcher dispatcher) {
this.game = game;
this.dispatcher = dispatcher;
@@ -41,6 +44,13 @@ public class CommandExecutor {
return result;
}
public void executeCommands(List<? extends Command> commands) {
for (Command command : commands) {
CommandResult result = executeCommand(command);
assert result != CommandResult.NotAllowed;
}
}
private void processResult(Command command, CommandResult result) {
switch (result) {
case NotAllowed:
@@ -52,19 +62,21 @@ public class CommandExecutor {
return;
case Moved:
boolean notifyPlayerTurn = true;
this.dispatcher.onBoardUpdate();
if (checkGameStatus()) {
if (!(command instanceof UndoCommand) && checkGameStatus()) {
this.dispatcher.onGameEnd();
return;
notifyPlayerTurn = false;
}
switchPlayerTurn();
switchPlayerTurn(command instanceof UndoCommand, notifyPlayerTurn);
return;
}
}
private void switchPlayerTurn() {
private void switchPlayerTurn(boolean undone, boolean notifyPlayerTurn) {
this.game.switchPlayerTurn();
this.dispatcher.onPlayerTurn(this.game.getPlayerTurn());
if (notifyPlayerTurn)
this.dispatcher.onPlayerTurn(this.game.getPlayerTurn(), undone);
}
/**
@@ -107,6 +119,6 @@ public class CommandExecutor {
}
public void close() {
this.dispatcher.stopService();
this.dispatcher.close();
}
}

View File

@@ -0,0 +1,112 @@
package chess.controller;
import java.util.List;
import chess.controller.Command.CommandResult;
import chess.controller.commands.CastlingCommand;
import chess.controller.commands.GetAllowedCastlingsCommand;
import chess.controller.commands.GetAllowedMovesPieceCommand;
import chess.controller.commands.GetPieceAtCommand;
import chess.controller.commands.GetPlayerMovesCommand;
import chess.controller.commands.MoveCommand;
import chess.controller.commands.NewGameCommand;
import chess.controller.commands.PromoteCommand;
import chess.controller.commands.SurrenderCommand;
import chess.controller.commands.UndoCommand;
import chess.controller.commands.GetAllowedCastlingsCommand.CastlingResult;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
import chess.model.Piece;
public interface CommandSender {
CommandExecutor getCommandExecutor();
default CommandResult sendCommand(Command cmd, CommandExecutor commandExecutor) {
CommandResult result = commandExecutor.executeCommand(cmd);
if (result == CommandResult.NotAllowed) {
// maybe do something
}
return result;
}
default CommandResult sendCommand(Command cmd) {
return sendCommand(cmd, getCommandExecutor());
}
// inputs
default CastlingResult getAllowedCastlings() {
GetAllowedCastlingsCommand cmd = new GetAllowedCastlingsCommand();
sendCommand(cmd);
return cmd.getCastlingResult();
}
default boolean canDoCastling() {
CastlingResult castlings = getAllowedCastlings();
return castlings == CastlingResult.Both || castlings == CastlingResult.Small;
}
default boolean canDoBigCastling() {
CastlingResult castlings = getAllowedCastlings();
return castlings == CastlingResult.Both || castlings == CastlingResult.Big;
}
default Piece getPieceAt(int x, int y) {
return getPieceAt(new Coordinate(x, y));
}
default Piece getPieceAt(Coordinate coordinate) {
GetPieceAtCommand cmd = new GetPieceAtCommand(coordinate);
sendCommand(cmd);
return cmd.getPiece();
}
default boolean isCellEmpty(int x, int y) {
return getPieceAt(x, y) == null;
}
default List<Move> getPlayerMoves() {
GetPlayerMovesCommand cmd = new GetPlayerMovesCommand();
sendCommand(cmd);
return cmd.getMoves();
}
default List<Coordinate> getPieceAllowedMoves(Coordinate piecePos) {
GetAllowedMovesPieceCommand cmd = new GetAllowedMovesPieceCommand(piecePos);
sendCommand(cmd);
return cmd.getDestinations();
}
// outputs
default CommandResult sendCastling() {
return sendCommand(new CastlingCommand(false));
}
default CommandResult sendBigCastling() {
return sendCommand(new CastlingCommand(true));
}
default CommandResult sendMove(Move move) {
return sendCommand(new MoveCommand(move));
}
default CommandResult sendStartGame() {
return sendCommand(new NewGameCommand());
}
default CommandResult sendPawnPromotion(PromoteType promote) {
return sendCommand(new PromoteCommand(promote));
}
default CommandResult sendSurrender(Color player) {
return sendCommand(new SurrenderCommand(player));
}
default CommandResult sendUndo() {
return sendCommand(new UndoCommand());
}
}

View File

@@ -49,6 +49,10 @@ public class CastlingCommand extends PlayerCommand {
board.applyMove(this.kingMove);
board.applyMove(this.rookMove);
board.setLastMove(this.kingMove);
outputSystem.onCastling(this.bigCastling, kingMove, rookMove);
return CommandResult.Moved;
}

View File

@@ -7,7 +7,7 @@ import chess.model.Coordinate;
import chess.model.Game;
import chess.model.Move;
import chess.model.Piece;
import chess.model.visitor.PiecePathChecker;
import chess.model.rules.PiecePathChecker;
public class MoveCommand extends PlayerCommand {
@@ -37,7 +37,6 @@ public class MoveCommand extends PlayerCommand {
return result;
case Moved:
outputSystem.onMove(this.move);
game.saveTraitPiecesPos();
return result;
@@ -53,7 +52,7 @@ public class MoveCommand extends PlayerCommand {
final ChessBoard board = game.getBoard();
// we must promote the pending pawn before
if (board.pawnShouldBePromoted())
if (game.pawnShouldBePromoted())
return CommandResult.NotAllowed;
Piece piece = board.pieceAt(move.getStart());
@@ -76,11 +75,14 @@ public class MoveCommand extends PlayerCommand {
}
if (tryPromote(game, outputSystem)) {
outputSystem.onMove(this.move, this.deadPiece != null);
return CommandResult.ActionNeeded;
}
board.setLastMove(this.move);
outputSystem.onMove(this.move, this.deadPiece != null);
return CommandResult.Moved;
}

View File

@@ -51,7 +51,7 @@ public class NewGameCommand extends Command {
game.reset();
outputSystem.onGameStart();
outputSystem.onPlayerTurn(game.getPlayerTurn());
outputSystem.onPlayerTurn(game.getPlayerTurn(), false);
return CommandResult.NotMoved;
}

View File

@@ -9,9 +9,9 @@ import chess.model.Game;
import chess.model.Piece;
import chess.model.pieces.Bishop;
import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
import chess.model.visitor.PawnIdentifier;
public class PromoteCommand extends PlayerCommand {
@@ -28,6 +28,7 @@ public class PromoteCommand extends PlayerCommand {
public PromoteCommand(PromoteType promoteType) {
this.promoteType = promoteType;
assert this.promoteType != null;
this.pieceCoords = null;
this.oldPawn = null;
}
@@ -42,7 +43,7 @@ public class PromoteCommand extends PlayerCommand {
return CommandResult.NotAllowed;
Piece pawn = board.pieceAt(this.pieceCoords);
if (!new PawnIdentifier(game.getPlayerTurn()).isPawn(pawn))
if (!(pawn instanceof Pawn))
return CommandResult.NotAllowed;
int destY = this.pieceCoords.getY();
@@ -55,6 +56,11 @@ public class PromoteCommand extends PlayerCommand {
this.oldPawn = pawn;
board.pieceComes(createPiece(this.promoteType, pawn.getColor()), this.pieceCoords);
outputSystem.onPawnPromoted(this.promoteType);
// invalidate the last move cache
board.setLastMove(null);
return CommandResult.Moved;
}

View File

@@ -0,0 +1,113 @@
package chess.controller.event;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
public class AsyncGameDispatcher extends GameDispatcher {
private final List<GameListener> listeners;
private final ExecutorService executor;
public AsyncGameDispatcher() {
this.listeners = new ArrayList<>();
this.executor = Executors.newSingleThreadExecutor();
}
@Override
public void addListener(GameListener listener) {
this.listeners.add(listener);
}
private void asyncForEachCall(Consumer<GameListener> func) {
this.executor.execute(() -> this.listeners.forEach(func));
}
@Override
public void onPlayerTurn(Color color, boolean undone) {
asyncForEachCall((l) -> l.onPlayerTurn(color, undone));
}
@Override
public void onWin(Color color) {
asyncForEachCall((l) -> l.onWin(color));
}
@Override
public void onKingInCheck() {
asyncForEachCall((l) -> l.onKingInCheck());
}
@Override
public void onKingInMat() {
asyncForEachCall((l) -> l.onKingInMat());
}
@Override
public void onPatSituation() {
asyncForEachCall((l) -> l.onPatSituation());
}
@Override
public void onSurrender(Color color) {
asyncForEachCall((l) -> l.onSurrender(color));
}
@Override
public void onGameStart() {
asyncForEachCall((l) -> l.onGameStart());
}
@Override
public void onPromotePawn(Coordinate pieceCoords) {
asyncForEachCall((l) -> l.onPromotePawn(pieceCoords));
}
@Override
public void onBoardUpdate() {
asyncForEachCall((l) -> l.onBoardUpdate());
}
@Override
public void onGameEnd() {
asyncForEachCall((l) -> l.onGameEnd());
}
@Override
public void onMove(Move move, boolean captured) {
asyncForEachCall((l) -> l.onMove(move, captured));
}
@Override
public void onMoveNotAllowed(Move move) {
asyncForEachCall((l) -> l.onMoveNotAllowed(move));
}
@Override
public void onDraw() {
asyncForEachCall((l) -> l.onDraw());
}
@Override
public void onCastling(boolean bigCastling, Move kingMove, Move rookMove) {
asyncForEachCall((l) -> l.onCastling(bigCastling, kingMove, rookMove));
}
@Override
public void onPawnPromoted(PromoteType promotion) {
asyncForEachCall((l) -> l.onPawnPromoted(promotion));
}
@Override
public void close() {
this.executor.shutdown();
}
}

View File

@@ -1,5 +1,6 @@
package chess.controller.event;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
@@ -31,7 +32,7 @@ public class EmptyGameDispatcher extends GameDispatcher {
}
@Override
public void onMove(Move move) {
public void onMove(Move move, boolean captured) {
}
@Override
@@ -43,7 +44,7 @@ public class EmptyGameDispatcher extends GameDispatcher {
}
@Override
public void onPlayerTurn(Color color) {
public void onPlayerTurn(Color color, boolean undone) {
}
@Override
@@ -57,5 +58,21 @@ public class EmptyGameDispatcher extends GameDispatcher {
@Override
public void onWin(Color winner) {
}
@Override
public void onCastling(boolean bigCastling, Move kingMove, Move rookMove) {
}
@Override
public void onPawnPromoted(PromoteType promotion) {
}
@Override
public void addListener(GameListener listener) {
}
@Override
public void close() {
}
}

View File

@@ -1,13 +1,14 @@
package chess.controller.event;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
public abstract class GameAdaptator implements GameListener {
public abstract class GameAdapter implements GameListener {
@Override
public void onPlayerTurn(Color color) {}
public void onPlayerTurn(Color color, boolean undone) {}
@Override
public void onWin(Color color) {}
@@ -37,7 +38,7 @@ public abstract class GameAdaptator implements GameListener {
public void onGameEnd() {}
@Override
public void onMove(Move move) {}
public void onMove(Move move, boolean captured) {}
@Override
public void onMoveNotAllowed(Move move) {}
@@ -45,4 +46,10 @@ public abstract class GameAdaptator implements GameListener {
@Override
public void onDraw() {}
@Override
public void onCastling(boolean bigCastling, Move kingMove, Move rookMove) {}
@Override
public void onPawnPromoted(PromoteType promotion) {}
}

View File

@@ -1,100 +1,9 @@
package chess.controller.event;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
public abstract class GameDispatcher extends GameAdapter {
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
public class GameDispatcher implements GameListener {
private final List<GameListener> listeners;
private final ExecutorService executor;
public GameDispatcher() {
this.listeners = new ArrayList<>();
this.executor = Executors.newSingleThreadExecutor();
}
public void addListener(GameListener listener) {
this.listeners.add(listener);
}
private void asyncForEachCall(Consumer<GameListener> func) {
this.executor.execute(() -> this.listeners.forEach(func));
}
@Override
public void onPlayerTurn(Color color) {
asyncForEachCall((l) -> l.onPlayerTurn(color));
}
@Override
public void onWin(Color color) {
asyncForEachCall((l) -> l.onWin(color));
}
@Override
public void onKingInCheck() {
asyncForEachCall((l) -> l.onKingInCheck());
}
@Override
public void onKingInMat() {
asyncForEachCall((l) -> l.onKingInMat());
}
@Override
public void onPatSituation() {
asyncForEachCall((l) -> l.onPatSituation());
}
@Override
public void onSurrender(Color color) {
asyncForEachCall((l) -> l.onSurrender(color));
}
@Override
public void onGameStart() {
asyncForEachCall((l) -> l.onGameStart());
}
@Override
public void onPromotePawn(Coordinate pieceCoords) {
asyncForEachCall((l) -> l.onPromotePawn(pieceCoords));
}
@Override
public void onBoardUpdate() {
asyncForEachCall((l) -> l.onBoardUpdate());
}
@Override
public void onGameEnd() {
asyncForEachCall((l) -> l.onGameEnd());
}
@Override
public void onMove(Move move) {
asyncForEachCall((l) -> l.onMove(move));
}
@Override
public void onMoveNotAllowed(Move move) {
asyncForEachCall((l) -> l.onMoveNotAllowed(move));
}
@Override
public void onDraw() {
asyncForEachCall((l) -> l.onDraw());
}
public void stopService() {
this.executor.shutdown();
}
public abstract void addListener(GameListener listener);
public abstract void close();
}

View File

@@ -1,5 +1,6 @@
package chess.controller.event;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
@@ -15,7 +16,7 @@ public interface GameListener {
* Invoked when a draw occurs (same position is repeated three times)
*/
void onDraw();
/**
* Invoked when the game has ended (by a win or a draw)
*/
@@ -25,7 +26,7 @@ public interface GameListener {
* Invoked when the game has started
*/
void onGameStart();
/**
* Invoked when a king is in check
*/
@@ -35,46 +36,71 @@ public interface GameListener {
* Invoked when a checkmate occurs
*/
void onKingInMat();
/**
* Invoked when a valid move on the board occurs
*
* @param move the move to be processed
* @param captured whether the move is a result of a capture
*/
void onMove(Move move);
void onMove(Move move, boolean captured);
/**
* Invoked when a sent move is not allowed
*
* @param move the move to be processed
*/
void onMoveNotAllowed(Move move);
/**
* Invoked when a pat situation occurs
*/
void onPatSituation();
/**
* Invoked when it's the player turn
* @param color the color of the player who should play
*
* @param color the color of the player who should play
* @param undone true if it's a result of an undo command
*/
void onPlayerTurn(Color color);
void onPlayerTurn(Color color, boolean undone);
/**
* Invoked when a pawn should be promoted
*
* @param pieceCoords the coordinates of the pawn
*/
void onPromotePawn(Coordinate pieceCoords);
/**
* Invoked when a players surrenders
*
* @param coward the player who gave up
*/
void onSurrender(Color coward);
/**
* Invoked when a player wins (by checkmate or if the other one surrenders)
*
* @param winner
*/
void onWin(Color winner);
/**
* Invoked when a castling is done
*
* @param bigCastling if it's queen side castling
* @param kingMove the king's move
* @param rookMove the rook's move
*/
void onCastling(boolean bigCastling, Move kingMove, Move rookMove);
/**
* Invoked when a pawn is promoted
*
* @param promotion the type of promotion
* @param player the player who promoted the pawns
*/
void onPawnPromoted(PromoteType promotion);
}

View File

@@ -4,9 +4,9 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import chess.model.visitor.KingIdentifier;
import chess.model.visitor.PawnIdentifier;
import chess.model.visitor.PiecePathChecker;
import chess.model.pieces.King;
import chess.model.pieces.Pawn;
import chess.model.rules.PiecePathChecker;
public class ChessBoard {
public static class Cell {
@@ -31,6 +31,10 @@ public class ChessBoard {
private Move lastMove;
private Piece lastEjectedPiece;
private List<Move> cachedAllowedMoves = null;
private final Coordinate kingPos[];
public ChessBoard() {
this.cells = new Cell[Coordinate.VALUE_MAX][Coordinate.VALUE_MAX];
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
@@ -41,6 +45,7 @@ public class ChessBoard {
this.lastVirtualMove = null;
this.lastMove = null;
this.lastEjectedPiece = null;
this.kingPos = new Coordinate[Color.values().length];
}
public void applyMove(Move move) {
@@ -98,6 +103,8 @@ public class ChessBoard {
public void pieceComes(Piece piece, Coordinate coordinate) {
cellAt(coordinate).setPiece(piece);
if (piece instanceof King)
this.kingPos[piece.getColor().ordinal()] = coordinate;
}
public void pieceLeaves(Coordinate coordinate) {
@@ -105,18 +112,31 @@ public class ChessBoard {
}
public Coordinate findKing(Color color) {
KingIdentifier kingIdentifier = new KingIdentifier(color);
return kingPos[color.ordinal()];
}
public List<Coordinate> getAllowedStarts(Coordinate finish, Color color) {
List<Coordinate> starts = new ArrayList<>();
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
Coordinate coordinate = new Coordinate(i, j);
Piece piece = pieceAt(coordinate);
if (kingIdentifier.isKing(piece)) {
return coordinate;
}
Coordinate attackCoords = new Coordinate(i, j);
Piece attackPiece = pieceAt(attackCoords);
if (attackPiece == null || attackPiece.getColor() != color)
continue;
Move move = new Move(attackCoords, finish);
PiecePathChecker piecePathChecker = new PiecePathChecker(this,
move);
if (!piecePathChecker.isValid())
continue;
applyMove(move);
if (!isKingInCheck(color))
starts.add(attackCoords);
undoLastMove();
}
}
assert false : "No king found ?!";
return null;
return starts;
}
public boolean isKingInCheck(Color color) {
@@ -127,7 +147,7 @@ public class ChessBoard {
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
Coordinate attackCoords = new Coordinate(i, j);
Piece attackPiece = pieceAt(attackCoords);
if (attackPiece == null)
if (attackPiece == null || attackPiece.getColor() == color)
continue;
PiecePathChecker checker = new PiecePathChecker(this, new Move(attackCoords, kingPos));
@@ -143,6 +163,10 @@ public class ChessBoard {
}
public List<Move> getAllowedMoves(Color player) {
if (this.cachedAllowedMoves != null) {
return this.cachedAllowedMoves;
}
List<Move> result = new ArrayList<>();
for (int x = 0; x < Coordinate.VALUE_MAX; x++) {
@@ -159,6 +183,11 @@ public class ChessBoard {
Coordinate destination = new Coordinate(i, j);
Move move = new Move(start, destination);
Piece destPiece = pieceAt(destination);
if (destPiece != null && destPiece.getColor() == player)
continue;
PiecePathChecker piecePathChecker = new PiecePathChecker(this,
move);
if (!piecePathChecker.isValid())
@@ -172,6 +201,7 @@ public class ChessBoard {
}
}
}
this.cachedAllowedMoves = result;
return result;
}
@@ -266,13 +296,12 @@ public class ChessBoard {
*/
private Coordinate pawnPromotePosition(Color color) {
int enemyLineY = color == Color.White ? 0 : 7;
PawnIdentifier identifier = new PawnIdentifier(color);
for (int x = 0; x < Coordinate.VALUE_MAX; x++) {
Coordinate pieceCoords = new Coordinate(x, enemyLineY);
Piece piece = pieceAt(pieceCoords);
if (identifier.isPawn(piece))
if (piece instanceof Pawn)
return pieceCoords;
}
@@ -300,10 +329,11 @@ public class ChessBoard {
public void setLastMove(Move lastMove) {
this.lastMove = lastMove;
this.cachedAllowedMoves = null;
}
@Override
public boolean equals(Object obj){
public boolean equals(Object obj) {
if (obj instanceof ChessBoard board)
return board.hashCode() == this.hashCode();
return false;

View File

@@ -20,8 +20,8 @@ public class Game {
Draw, Check, CheckMate, OnGoing, Pat;
}
public Game(ChessBoard board) {
this.board = board;
public Game() {
this.board = new ChessBoard();
this.movesHistory = new Stack<>();
this.traitsPos = new HashMap<>();
}
@@ -70,24 +70,27 @@ public class Game {
playerTurn = Color.getEnemy(playerTurn);
}
public GameStatus checkGameStatus() {
final Color enemy = Color.getEnemy(getPlayerTurn());
// this is the bottleneck of algorithms using this chess engine
public GameStatus checkGameStatus(Color color) {
if (checkDraw())
return GameStatus.Draw;
if (this.board.isKingInCheck(enemy))
if (this.board.hasAllowedMoves(enemy))
if (this.board.isKingInCheck(color))
if (this.board.hasAllowedMoves(color))
return GameStatus.Check;
else
return GameStatus.CheckMate;
if (!board.hasAllowedMoves(enemy))
if (!board.hasAllowedMoves(color))
return GameStatus.Pat;
return GameStatus.OnGoing;
}
public GameStatus checkGameStatus() {
return checkGameStatus(Color.getEnemy(getPlayerTurn()));
}
public void addAction(PlayerCommand command) {
this.movesHistory.add(command);
}
@@ -119,4 +122,8 @@ public class Game {
return this.movesHistory;
}
public boolean pawnShouldBePromoted() {
return this.board.pawnShouldBePromoted();
}
}

View File

@@ -0,0 +1,20 @@
package chess.model;
public class PermissiveGame extends Game {
@Override
public GameStatus checkGameStatus(Color color) {
return GameStatus.OnGoing;
}
@Override
public void undoTraitPiecesPos() {
}
@Override
public void saveTraitPiecesPos() {
}
}

View File

@@ -28,7 +28,4 @@ public abstract class Piece {
public abstract <T> T accept(PieceVisitor<T> visitor);
@Override
public abstract boolean equals(Object other);
}

View File

@@ -15,13 +15,4 @@ public class Bishop extends Piece {
return visitor.visitPiece(this);
}
@Override
public int hashCode() {
return 0;
}
public boolean equals(Object obj) {
return (obj instanceof Bishop && ((Bishop) obj).getColor() == this.getColor());
}
}

View File

@@ -14,13 +14,4 @@ public class King extends Piece {
public <T> T accept(PieceVisitor<T> visitor) {
return visitor.visitPiece(this);
}
@Override
public int hashCode() {
return 1;
}
public boolean equals(Object obj) {
return (obj instanceof King && ((King) obj).getColor() == this.getColor());
}
}

View File

@@ -15,12 +15,4 @@ public class Knight extends Piece {
return visitor.visitPiece(this);
}
@Override
public int hashCode() {
return 2;
}
public boolean equals(Object obj) {
return (obj instanceof Knight && ((Knight) obj).getColor() == this.getColor());
}
}

View File

@@ -19,12 +19,4 @@ public class Pawn extends Piece {
return getColor() == Color.White ? 1 : -1;
}
@Override
public int hashCode() {
return 3;
}
public boolean equals(Object obj) {
return (obj instanceof Pawn && ((Pawn) obj).getColor() == this.getColor());
}
}

View File

@@ -15,12 +15,5 @@ public class Queen extends Piece {
return visitor.visitPiece(this);
}
@Override
public int hashCode() {
return 4;
}
public boolean equals(Object obj) {
return (obj instanceof Queen && ((Queen) obj).getColor() == this.getColor());
}
}

View File

@@ -15,12 +15,4 @@ public class Rook extends Piece {
return visitor.visitPiece(this);
}
@Override
public int hashCode() {
return 5;
}
public boolean equals(Object other) {
return (other instanceof Rook && ((Rook) other).getColor() == this.getColor());
}
}

View File

@@ -1,4 +1,4 @@
package chess.model.visitor;
package chess.model.rules;
import chess.model.Coordinate;
import chess.model.Direction;

View File

@@ -1,4 +1,4 @@
package chess.model.visitor;
package chess.model.rules;
import chess.model.ChessBoard;
import chess.model.Color;
@@ -104,10 +104,11 @@ public class PiecePathChecker implements PieceVisitor<Boolean> {
Coordinate middle = lastMove.getMiddle();
if (middle.equals(this.move.getFinish())
&& new PawnIdentifier(pieceToEat.getColor()).isPawn(pieceToEat)) {
&& pieceToEat instanceof Pawn) {
this.move.setDeadPieceCoords(lastMove.getFinish());
return true;
}
return false;
}

View File

@@ -1,52 +0,0 @@
package chess.model.visitor;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
import chess.model.pieces.*;
public class KingIdentifier implements PieceVisitor<Boolean> {
private final Color color;
public KingIdentifier(Color color) {
this.color = color;
}
public boolean isKing(Piece piece) {
if (piece == null)
return false;
return visit(piece);
}
@Override
public Boolean visitPiece(Bishop bishop) {
return false;
}
@Override
public Boolean visitPiece(King king) {
return king.getColor() == color;
}
@Override
public Boolean visitPiece(Knight knight) {
return false;
}
@Override
public Boolean visitPiece(Pawn pawn) {
return false;
}
@Override
public Boolean visitPiece(Queen queen) {
return false;
}
@Override
public Boolean visitPiece(Rook rook) {
return false;
}
}

View File

@@ -1,52 +0,0 @@
package chess.model.visitor;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
import chess.model.pieces.*;
public class PawnIdentifier implements PieceVisitor<Boolean> {
private final Color color;
public PawnIdentifier(Color color) {
this.color = color;
}
public boolean isPawn(Piece piece) {
if (piece == null)
return false;
return visit(piece);
}
@Override
public Boolean visitPiece(Bishop bishop) {
return false;
}
@Override
public Boolean visitPiece(King king) {
return false;
}
@Override
public Boolean visitPiece(Knight knight) {
return false;
}
@Override
public Boolean visitPiece(Pawn pawn) {
return pawn.getColor() == color;
}
@Override
public Boolean visitPiece(Queen queen) {
return false;
}
@Override
public Boolean visitPiece(Rook rook) {
return false;
}
}

View File

@@ -4,6 +4,7 @@ import java.util.List;
import chess.controller.CommandExecutor;
import chess.controller.PlayerCommand;
import chess.controller.Command.CommandResult;
import chess.controller.commands.CastlingCommand;
import chess.controller.commands.GetPieceAtCommand;
import chess.controller.commands.GetPlayerMovesCommand;
@@ -11,7 +12,6 @@ import chess.controller.commands.MoveCommand;
import chess.controller.commands.NewGameCommand;
import chess.controller.commands.PromoteCommand;
import chess.controller.event.EmptyGameDispatcher;
import chess.model.ChessBoard;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Game;
@@ -22,24 +22,24 @@ import chess.model.pieces.Pawn;
public class PgnExport {
// public static void main(String[] args) {
// final Game game = new Game(new ChessBoard());
// final CommandExecutor commandExecutor = new CommandExecutor(game);
// final Game game = new Game();
// final CommandExecutor commandExecutor = new CommandExecutor(game);
// DumbAI ai1 = new DumbAI(commandExecutor, Color.White);
// commandExecutor.addListener(ai1);
// DumbAI ai1 = new DumbAI(commandExecutor, Color.White);
// commandExecutor.addListener(ai1);
// DumbAI ai2 = new DumbAI(commandExecutor, Color.Black);
// commandExecutor.addListener(ai2);
// DumbAI ai2 = new DumbAI(commandExecutor, Color.Black);
// commandExecutor.addListener(ai2);
// commandExecutor.addListener(new GameAdaptator() {
// @Override
// public void onGameEnd() {
// System.out.println(exportGame(game));
// commandExecutor.close();
// }
// });
// commandExecutor.addListener(new GameAdapter() {
// @Override
// public void onGameEnd() {
// System.out.println(exportGame(game));
// commandExecutor.close();
// }
// });
// commandExecutor.executeCommand(new NewGameCommand());
// commandExecutor.executeCommand(new NewGameCommand());
// }
private static final PiecePgnName piecePgnName = new PiecePgnName();
@@ -57,15 +57,15 @@ public class PgnExport {
}
private static String gameEnd(Game game) {
switch (game.checkGameStatus()) {
switch (game.checkGameStatus(game.getPlayerTurn())) {
case Draw:
case Pat:
return "1/2-1/2";
case CheckMate:
if (game.getPlayerTurn() == Color.White)
return "1-0";
return "0-1";
return "0-1";
return "1-0";
default:
return "";
@@ -89,7 +89,7 @@ public class PgnExport {
Piece otherPiece = pieceAt(cmdExec, move.getStart());
// checking type of piece
if (otherPiece.hashCode() != movingPiece.hashCode())
if (!otherPiece.getClass().equals(movingPiece.getClass()))
continue;
String startPos = toString(pieceMove.getStart());
@@ -138,7 +138,7 @@ public class PgnExport {
}
private static String checkCheckMate(Game game) {
switch (game.checkGameStatus()) {
switch (game.checkGameStatus(game.getPlayerTurn())) {
case CheckMate:
return "#";
@@ -150,7 +150,10 @@ public class PgnExport {
}
}
private static String printMove(PlayerCommand cmd, PlayerCommand nextCommand, Game virtualGame,
private record MoveResult(String move, CommandResult commandResult) {
}
private static MoveResult printMove(PlayerCommand cmd, PlayerCommand nextCommand, Game virtualGame,
CommandExecutor executor) {
String result = "";
if (cmd instanceof MoveCommand move) {
@@ -178,20 +181,19 @@ public class PgnExport {
result += castling(castlingCommand);
}
executor.executeCommand(cmd);
CommandResult commandResult = executor.executeCommand(cmd);
// check or checkmate
result += checkCheckMate(virtualGame);
result += " ";
return result;
return new MoveResult(result, commandResult);
}
public static String exportGame(Game game) {
ChessBoard board = new ChessBoard();
Game virtualGame = new Game(board);
Game virtualGame = new Game();
CommandExecutor executor = new CommandExecutor(virtualGame, new EmptyGameDispatcher());
executor.executeCommand(new NewGameCommand());
@@ -201,17 +203,33 @@ public class PgnExport {
int tour = 1;
String lastMove = null;
for (int i = 0; i < commands.size(); i++) {
PlayerCommand cmd = commands.get(i);
PlayerCommand nextCommand = null;
if (i != commands.size() - 1) {
nextCommand = commands.get(i + 1);
}
if (virtualGame.getPlayerTurn() == Color.White) {
MoveResult moveResult = printMove(cmd, nextCommand, virtualGame, executor);
if (moveResult.commandResult() == CommandResult.Moved && virtualGame.getPlayerTurn() == Color.Black) {
result += tour + ".";
tour++;
}
result += printMove(cmd, nextCommand, virtualGame, executor);
if (moveResult.commandResult() == CommandResult.ActionNeeded) {
lastMove = moveResult.move();
continue;
}
if (lastMove != null && moveResult.commandResult() == CommandResult.Moved){
result += lastMove;
lastMove = null;
continue;
}
result += moveResult.move();
}
return result + " " + gameEnd(virtualGame);
}

View File

@@ -0,0 +1,12 @@
package chess.pgn;
import chess.controller.CommandExecutor;
import common.AssetManager;
public class PgnFileSimulator extends PgnSimulator{
public PgnFileSimulator(CommandExecutor commandExecutor, String fileName) {
super(commandExecutor, AssetManager.getResourceAsString(fileName));
}
}

View File

@@ -0,0 +1,172 @@
package chess.pgn;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import chess.controller.CommandExecutor;
import chess.controller.PlayerCommand;
import chess.controller.commands.CastlingCommand;
import chess.controller.commands.MoveCommand;
import chess.controller.commands.NewGameCommand;
import chess.controller.commands.PromoteCommand;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.controller.event.EmptyGameDispatcher;
import chess.model.ChessBoard;
import chess.model.Coordinate;
import chess.model.Game;
import chess.model.Move;
import chess.model.Piece;
import chess.model.pieces.Bishop;
import chess.model.pieces.King;
import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
public class PgnImport {
private static final Map<String, Class<? extends Piece>> pieceMap = Map.of(
"K", King.class,
"Q", Queen.class,
"R", Rook.class,
"B", Bishop.class,
"N", Knight.class);
private static final Map<String, PromoteType> promoteMap = Map.of(
"Q", PromoteType.Queen,
"R", PromoteType.Rook,
"B", PromoteType.Bishop,
"N", PromoteType.Knight);
public static List<PlayerCommand> importGame(String pgnContent) {
String[] parts = pgnContent.split("\n\n");
// we just ignore headers
return getMoves(parts[parts.length - 1]);
}
private static List<PlayerCommand> getMoves(String unparsedMoves) {
String[] moves = unparsedMoves.replaceAll("\\{.*?\\}", "") // Remove comments
.replaceAll("\\n", " ") // Remove new lines
.split("[\\s.]+"); // Split by whitespace and dots (trimming it also)
Game virtualGame = new Game();
CommandExecutor commandExecutor = new CommandExecutor(virtualGame, new EmptyGameDispatcher());
List<PlayerCommand> instructions = new ArrayList<>();
commandExecutor.executeCommand(new NewGameCommand());
for (int i = 0; i < moves.length; i++) {
if (i % 3 == 0)
continue;
String move = moves[i];
if (move.equals("1-0") || move.equals("0-1") || move.equals("1/2-1/2")) {
break; // End of the game
}
List<PlayerCommand> cmds = parseMove(move, virtualGame);
commandExecutor.executeCommands(cmds);
instructions.addAll(cmds);
}
return instructions;
}
private static List<PlayerCommand> parseMove(String move, Game game) {
if (move.equals("O-O-O"))
return Arrays.asList(new CastlingCommand(true));
if (move.equals("O-O"))
return Arrays.asList(new CastlingCommand(false));
move = move.replaceAll("[x|#|\\+]", "");
PromoteCommand promoteCommand = null;
if (move.contains("=")) {
String promoteString = move.substring(move.length() - 1);
promoteCommand = new PromoteCommand(promoteMap.get(promoteString));
move = move.substring(0, move.length() - 2);
}
Class<? extends Piece> pieceType = pieceMap.get(move.substring(0, 1));
if (pieceType == null)
pieceType = Pawn.class;
else
move = move.substring(1);
assert move.length() == 3 || move.length() == 2;
Coordinate ambiguity = new Coordinate(-1, -1);
// ambiguity
if (move.length() == 3) {
ambiguity = getAmbiguityPattern(move.charAt(0));
move = move.substring(1);
}
Coordinate dest = stringToCoordinate(move);
Coordinate start = getStartCoord(dest, pieceType, ambiguity, game);
List<PlayerCommand> cmds = new ArrayList<>();
cmds.add(new MoveCommand(new Move(start, dest)));
if (promoteCommand != null)
cmds.add(promoteCommand);
return cmds;
}
private static Coordinate getStartCoord(Coordinate dest, Class<? extends Piece> pieceType, Coordinate ambiguity,
Game game) {
final ChessBoard board = game.getBoard();
List<Coordinate> starts = board.getAllowedStarts(dest, game.getPlayerTurn());
assert !starts.isEmpty() : "No moves allowed!";
for (Coordinate start : starts) {
Piece piece = board.pieceAt(start);
if (piece.getClass().equals(pieceType) && coordPatternMatch(start, ambiguity))
return start;
}
assert false : "There is a small problem ...";
return null;
}
private static int getXCoord(char xPos) {
return xPos - 'a';
}
private static int getYCoord(char yPos) {
return Coordinate.VALUE_MAX - 1 - (yPos - '1');
}
private static boolean coordPatternMatch(Coordinate coord, Coordinate pattern) {
if (pattern.getX() != -1 && coord.getX() != pattern.getX())
return false;
if (pattern.getY() != -1 && coord.getY() != pattern.getY())
return false;
return true;
}
private static Coordinate getAmbiguityPattern(char amb) {
if (Character.isDigit(amb))
return new Coordinate(-1, getYCoord(amb));
return new Coordinate(getXCoord(amb), -1);
}
private static Coordinate stringToCoordinate(String coordinates) {
char xPos = coordinates.charAt(0);
char yPos = coordinates.charAt(1);
return new Coordinate(getXCoord(xPos), getYCoord(yPos));
}
}

View File

@@ -0,0 +1,30 @@
package chess.pgn;
import java.util.List;
import chess.controller.CommandExecutor;
import chess.controller.PlayerCommand;
import chess.controller.event.GameAdapter;
import common.Signal0;
public class PgnSimulator extends GameAdapter {
private final CommandExecutor commandExecutor;
private final String pgn;
public final Signal0 onComplete = new Signal0();
public PgnSimulator(CommandExecutor commandExecutor, String pgn) {
this.commandExecutor = commandExecutor;
this.pgn = pgn;
}
@Override
public void onGameStart() {
List<PlayerCommand> cmds = PgnImport.importGame(this.pgn);
this.commandExecutor.executeCommands(cmds);
this.onComplete.emit();
}
}

View File

@@ -1,31 +0,0 @@
package chess.simulator;
import chess.controller.CommandExecutor;
import chess.model.Coordinate;
import chess.model.Move;
import java.util.Arrays;
import java.util.List;
public class CastlingTest extends Simulator {
public CastlingTest(CommandExecutor commandExecutor) {
super(commandExecutor);
}
@Override
protected List<Move> getMoves() {
return Arrays.asList(
// white pawn
new Move(new Coordinate(6, 6), new Coordinate(6, 4)),
// black knight
new Move(new Coordinate(1, 0), new Coordinate(0, 2)),
// white bishop
new Move(new Coordinate(5, 7), new Coordinate(7, 5)),
// black pawn
new Move(new Coordinate(1, 1), new Coordinate(1, 2)),
// white knight
new Move(new Coordinate(6, 7), new Coordinate(5, 5)),
// black pawn, bis
new Move(new Coordinate(2, 1), new Coordinate(2, 2)));
}
}

View File

@@ -1,28 +0,0 @@
package chess.simulator;
import java.util.Arrays;
import java.util.List;
import chess.controller.CommandExecutor;
import chess.model.Coordinate;
import chess.model.Move;
public class FoolCheckMate extends Simulator {
public FoolCheckMate(CommandExecutor commandExecutor) {
super(commandExecutor);
}
@Override
public List<Move> getMoves() {
return Arrays.asList(
// white pawn
new Move(new Coordinate(5, 6), new Coordinate(5, 5)),
// black pawn
new Move(new Coordinate(4, 1), new Coordinate(4, 3)),
// 2nd white pawn
new Move(new Coordinate(6, 6), new Coordinate(6, 4)),
// black queen
new Move(new Coordinate(3, 0), new Coordinate(7, 4)));
}
}

View File

@@ -1,40 +0,0 @@
package chess.simulator;
import java.util.Arrays;
import java.util.List;
import chess.controller.CommandExecutor;
import chess.model.Coordinate;
import chess.model.Move;
public class PromoteTest extends Simulator{
public PromoteTest(CommandExecutor commandExecutor) {
super(commandExecutor);
}
@Override
protected List<Move> getMoves() {
return Arrays.asList(
// white pawn
new Move(new Coordinate(5, 6), new Coordinate(5, 4)),
// black pawn
new Move(new Coordinate(4, 1), new Coordinate(4, 3)),
// white pawn capture
new Move(new Coordinate(5, 4), new Coordinate(4, 3)),
// black king
new Move(new Coordinate(4, 0), new Coordinate(4, 1)),
// white pawn moves
new Move(new Coordinate(4, 3), new Coordinate(4, 2)),
// black king
new Move(new Coordinate(4, 1), new Coordinate(5, 2)),
// white pawn moves
new Move(new Coordinate(4, 2), new Coordinate(4, 1)),
// black king
new Move(new Coordinate(5, 2), new Coordinate(6, 2))
// white pawn moves
// new Move(new Coordinate(4, 1), new Coordinate(4, 0))
);
}
}

View File

@@ -1,39 +0,0 @@
package chess.simulator;
import java.util.List;
import chess.controller.CommandExecutor;
import chess.controller.commands.MoveCommand;
import chess.controller.event.GameAdaptator;
import chess.model.Move;
import common.Signal0;
public abstract class Simulator extends GameAdaptator {
protected final CommandExecutor commandExecutor;
public final Signal0 onComplete = new Signal0();
private int currentMove = 0;
public Simulator(CommandExecutor commandExecutor) {
this.commandExecutor = commandExecutor;
}
@Override
public void onGameStart() {
for (Move move : getMoves()) {
this.commandExecutor.executeCommand(new MoveCommand(move));
}
}
@Override
public void onBoardUpdate() {
currentMove++;
if (currentMove == getMoves().size()) {
onComplete.emit();
}
}
protected abstract List<Move> getMoves();
}

View File

@@ -0,0 +1,86 @@
package chess.view.DDDrender;
import org.joml.Matrix4f;
import org.joml.Vector2f;
import org.joml.Vector3f;
import org.joml.Vector4f;
public class Camera {
public static final float fov = 70.0f;
public static final float zNear = 0.01f;
public static final float zFar = 1000.0f;
private static final Vector3f up = new Vector3f(0.0f, -1.0f, 0.0f);
private static final Vector3f center = new Vector3f(0.0f, 0.0f, 0.0f);
private final float distance = 1.5f;
private final float camHeight = 1.5f;
private float aspectRatio;
private float angle;
private Vector3f pos;
public Camera() {
this.pos = new Vector3f(0.0f, camHeight, 0.0f);
setRotateAngle((float) Math.PI);
}
public void move(float x, float y) {
this.pos.x += x;
this.pos.y += y;
}
public void setRotateAngle(float angle) {
this.angle = angle;
updatePostion();
}
private void updatePostion() {
final float finalX = (float) Math.sin(angle);
final float finalZ = (float) -Math.cos(angle);
this.pos.set(distance * finalX, this.pos.get(1), distance * finalZ);
}
public float getRotateAngle() {
return angle;
}
public Vector3f getPos() {
return pos;
}
public float getFov() {
return fov;
}
public void setAspectRatio(float aspectRatio) {
this.aspectRatio = aspectRatio;
}
public Matrix4f getPerspectiveMatrix() {
return new Matrix4f().perspective((float) (Math.toRadians(fov)), aspectRatio, zNear, zFar);
}
public Matrix4f getViewMatrix() {
return new Matrix4f().lookAt(pos, center, up);
}
public Vector2f getCursorWorldFloorPos(Vector2f screenPos, int windowWidth, int windowHeight) {
float relativeX = (screenPos.x / (float) windowWidth * 2.0f) - 1.0f;
float relativeY = 1.0f - (screenPos.y / (float) windowHeight * 2.0f);
Vector4f rayClip = new Vector4f(relativeX, relativeY, -1.0f, 1.0f);
Vector4f rayEye = getPerspectiveMatrix().invert().transform(rayClip);
rayEye = new Vector4f(rayEye.x, rayEye.y, -1.0f, 0.0f);
Vector4f rayWorld = getViewMatrix().invert().transform(rayEye);
float lambda = -this.pos.y / rayWorld.y;
return new Vector2f(lambda * rayWorld.x + this.pos.x, lambda *
rayWorld.z + this.pos.z);
}
}

View File

@@ -0,0 +1,27 @@
package chess.view.DDDrender;
import java.io.Closeable;
import java.io.IOException;
import java.util.List;
import chess.view.DDDrender.opengl.VertexArray;
public class DDDModel implements Closeable {
private final List<VertexArray> vaos;
public DDDModel(List<VertexArray> vaos) {
this.vaos = vaos;
}
public List<VertexArray> getVaos() {
return vaos;
}
@Override
public void close() throws IOException {
for (VertexArray vertexArray : vaos) {
vertexArray.close();
}
}
}

View File

@@ -0,0 +1,21 @@
package chess.view.DDDrender;
import org.joml.Vector2f;
import chess.model.Coordinate;
class DDDPlacement {
public static Vector2f coordinatesToVector(Coordinate coo) {
return coordinatesToVector(coo.getX(), coo.getY());
}
public static Vector2f coordinatesToVector(float x, float y) {
return new Vector2f(1.0f - 0.125f - x * 0.250f, 1.0f - 0.125f - y * 0.250f);
}
public static Coordinate vectorToCoordinates(Vector2f pos) {
int x = (int) ((1.0f - pos.x) * 4.0f);
int y = (int) ((1.0f - pos.y) * 4.0f);
return new Coordinate(x, y);
}
}

View File

@@ -0,0 +1,454 @@
package chess.view.DDDrender;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import chess.controller.commands.*;
import imgui.ImGui;
import imgui.ImVec2;
import imgui.flag.ImGuiCond;
import imgui.flag.ImGuiWindowFlags;
import org.joml.Vector2f;
import org.joml.Vector3f;
import chess.controller.Command.CommandResult;
import chess.controller.CommandExecutor;
import chess.controller.CommandSender;
import chess.controller.event.GameAdapter;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
import chess.model.Piece;
import chess.view.DDDrender.world.BoardEntity;
import chess.view.DDDrender.world.PieceEntity;
import chess.view.DDDrender.world.World;
public class DDDView extends GameAdapter implements CommandSender {
private static final Vector3f BLACK = new Vector3f(0.3f, 0.3f, 0.3f);
private static final Vector3f WHITE = new Vector3f(1.0f, 1.0f, 1.0f);
private static final Vector3f RED = new Vector3f(1.0f, 0.0f, 0.0f);
private static final Vector3f YELLOW = new Vector3f(1.0f, 1.0f, 0.0f);
private static final Vector3f BLUE = new Vector3f(0.0f, 0.0f, 1.0f);
private final CommandExecutor commandExecutor;
private final Window window;
private final World world;
private BoardEntity boardEntity;
private final Camera camera;
private Coordinate click = null;
private static final float animationTime = 0.5f; // in seconds
private static final int animationTurns = 1;
private float moveProgress = 0.0f;
private String waitingPopup = null;
public DDDView(CommandExecutor commandExecutor) {
this.commandExecutor = commandExecutor;
this.world = new World();
this.camera = new Camera();
this.window = new Window(new Renderer(), this.world, this.camera);
}
@Override
public CommandExecutor getCommandExecutor() {
return this.commandExecutor;
}
private void cancelClick() {
this.click = null;
}
private void setClick(Coordinate coordinate) {
this.click = coordinate;
}
// Invoked when a cell is clicked
private void onCellClick(Coordinate coordinate) {
if (this.click == null) { // case: first click
List<Coordinate> allowedMoves = getPieceAllowedMoves(coordinate);
if (allowedMoves.isEmpty()) { // case: no movement possible for piece
System.out.println("This piece cannot be moved at the moment.");
return;
}
setClick(coordinate);
previewMoves(coordinate);
// this.boardEntity.setCellColor(coordinate, BLUE);
System.out.println("First click on " + coordinate);
return;
}
// case: second click
GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(this.click);
if (sendCommand(movesCommand) == CommandResult.NotAllowed) { // case: invalid piece to move
cancelPreview(this.click);
System.out.println("Nothing to do here.");
cancelClick();
return;
}
List<Coordinate> allowedMoves = movesCommand.getDestinations();
if (allowedMoves.isEmpty()) { // case: no movement possible for piece
cancelPreview(this.click);
System.out.println("This piece cannot be moved at the moment.");
cancelClick();
return;
}
if (allowedMoves.contains(coordinate)) { // case: valid attempt to move
System.out.println("Move on " + coordinate);
cancelPreview(this.click);
sendMove(new Move(click, coordinate));
cancelClick();
return;
}
if (!(coordinate == this.click)) {
System.out.println("New click on " + coordinate); // cases: invalid move, selecting another piece
cancelPreview(this.click);
previewMoves(coordinate);
setClick(coordinate);
return;
}
System.out.println("Cancelling click."); // case: cancelling previous click
cancelClick();
}
private void previewMoves(Coordinate coordinate) {
List<Coordinate> allowedMoves = getPieceAllowedMoves(coordinate);
if (allowedMoves.isEmpty())
return;
this.boardEntity.setCellColor(coordinate, RED);
this.world.getPiece(coordinate).setColor(RED);
for (Coordinate destCoord : allowedMoves) {
this.boardEntity.setCellColor(destCoord, YELLOW);
}
}
// Invoked when a cell is hovered
private void onCellEnter(Coordinate coordinate) {
if (this.click == null) {
// small test turning a cell red when hovered
this.boardEntity.setCellColor(coordinate, RED);
PieceEntity pEntity = this.world.getPiece(coordinate);
if (pEntity == null)
return;
pEntity.setColor(RED);
List<Coordinate> allowedMoves = getPieceAllowedMoves(coordinate);
if (allowedMoves.isEmpty())
return;
for (Coordinate destCoord : allowedMoves) {
this.boardEntity.setCellColor(destCoord, YELLOW);
}
}
}
// Invoked when a cell is not hovered anymore
private void onCellExit(Coordinate coordinate) {
if (this.click == null) {
this.boardEntity.resetCellColor(coordinate);
Piece p = getPieceAt(coordinate);
if (p == null)
return;
PieceEntity pEntity = this.world.getPiece(coordinate);
if (pEntity == null)
return;
pEntity.setColor(p.getColor() == Color.White ? WHITE : BLACK);
List<Coordinate> allowedMoves = getPieceAllowedMoves(coordinate);
if (allowedMoves.isEmpty())
return;
for (Coordinate destCoord : allowedMoves) {
this.boardEntity.resetCellColor(destCoord);
}
}
}
private void cancelPreview(Coordinate coordinate) {
this.boardEntity.resetCellColor(coordinate);
Piece p = getPieceAt(coordinate);
if (p == null)
return;
this.world.getPiece(coordinate).setColor(p.getColor() == Color.White ? WHITE : BLACK);
List<Coordinate> allowedMoves = getPieceAllowedMoves(coordinate);
if (allowedMoves.isEmpty())
return;
for (Coordinate destCoord : allowedMoves) {
this.boardEntity.resetCellColor(destCoord);
}
}
@Override
public void onGameStart() {
this.window.scheduleTask(() -> {
try {
initBoard();
} catch (IOException e) {
e.printStackTrace();
}
// start listening to mouse events
this.window.OnCellClick.connect(this::onCellClick);
this.window.OnCellEnter.connect(this::onCellEnter);
this.window.OnCellExit.connect(this::onCellExit);
this.window.OnImGuiTopRender.connect(this::onHeaderRender);
this.window.OnImGuiBottomRender.connect(this::onFooterRender);
synchronized (this) {
notifyAll();
}
});
synchronized (this) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void onHeaderRender() {
ImGui.text("FPS : " + (int) ImGui.getIO().getFramerate());
}
private void onFooterRender() {
ImGui.beginDisabled(!canDoCastling());
if (ImGui.button("Roque")) {
sendCastling();
}
ImGui.endDisabled();
ImGui.sameLine();
ImGui.beginDisabled(!canDoBigCastling());
if (ImGui.button("Grand Roque")) {
sendBigCastling();
}
ImGui.endDisabled();
ImGui.sameLine();
if (ImGui.button("Annuler le coup précédent")) {
sendUndo();
}
openPopup();
renderPopups();
}
private void openPopup() {
if (waitingPopup != null) {
ImGui.openPopup(waitingPopup);
waitingPopup = null;
}
}
private void initBoard() throws IOException {
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
Coordinate pos = new Coordinate(i, j);
Piece piece = getPieceAt(pos);
if (piece == null)
continue;
Vector2f pieceBoardPos = DDDPlacement.coordinatesToVector(pos);
Vector3f pieceWorldPos = new Vector3f(pieceBoardPos.x(), 0, pieceBoardPos.y());
PieceEntity entity = new PieceEntity(piece, pieceWorldPos,
piece.getColor() == Color.White ? WHITE : BLACK,
piece.getColor() == Color.White ? 0.0f : (float) Math.PI);
this.world.addPiece(entity, pos);
}
}
this.boardEntity = new BoardEntity();
this.world.addEntity(this.boardEntity);
}
/**
* @param begin begin
* @param middle control point
* @param end end
* @param t between 0 and 1
* @return the point
*/
private Vector3f bezierCurve(Vector3f begin, Vector3f middle, Vector3f end, float t) {
return begin.mul((1.0f - t) * (1.0f - t)).add(middle.mul(2.0f * t * (1.0f - t))).add(end.mul(t * t));
}
private void pieceTick(float progress, PieceEntity piece, Move move) {
float height = 1; // how much the piece is raised
Vector2f pieceStartBoard = DDDPlacement.coordinatesToVector(move.getStart());
Vector2f pieceDestinationBoard = DDDPlacement.coordinatesToVector(move.getFinish());
Vector3f start = new Vector3f(pieceStartBoard.x(), 0, pieceStartBoard.y());
Vector3f top = new Vector3f(pieceDestinationBoard.x() - pieceStartBoard.x(), height,
pieceDestinationBoard.y() - pieceStartBoard.y());
Vector3f end = new Vector3f(pieceDestinationBoard.x(), 0, pieceDestinationBoard.y());
piece.setPosition(bezierCurve(start, top, end, progress));
}
private void move3DPieces(List<Move> moves) {
final List<PieceEntity> pEntities = new ArrayList<>(moves.size());
final List<Consumer<Float>> consumers = new ArrayList<>(moves.size());
this.moveProgress = 0.0f;
for (Move move : moves) {
final PieceEntity pEntity = this.world.getPiece(move.getStart());
final Consumer<Float> moveConsumer = (delta) -> {
this.moveProgress += delta / animationTime / (float) moves.size();
pieceTick(this.moveProgress, pEntity, move);
};
pEntities.add(pEntity);
consumers.add(moveConsumer);
this.window.addRegularTask(moveConsumer);
}
try {
Thread.sleep((long) (animationTime * 1000.0f));
} catch (InterruptedException e) {
}
for (int i = 0; i < moves.size(); i++) {
final Move move = moves.get(i);
final Consumer<Float> moveConsumer = consumers.get(i);
final PieceEntity pEntity = pEntities.get(i);
this.window.removeRegularTask(moveConsumer);
Vector2f pieceDestBoardPos = DDDPlacement.coordinatesToVector(move.getFinish());
Vector3f pieceDestWorldPos = new Vector3f(pieceDestBoardPos.x(), 0, pieceDestBoardPos.y());
if (move.getDeadPieceCoords() != null) {
this.world.ejectPiece(move.getDeadPieceCoords());
}
pEntity.setPosition(pieceDestWorldPos);
this.world.movePiece(pEntity, move);
}
}
@Override
public void onMove(Move move, boolean captured) {
move3DPieces(List.of(move));
}
private void cameraTick(float delta) {
int oddAnimationTurn = (2 * (animationTurns - 1)) + 1;
final float angle = (float) Math.PI;
this.camera.setRotateAngle(this.camera.getRotateAngle() + angle * delta * oddAnimationTurn / animationTime);
}
private void cameraRotate() {
float end = this.camera.getRotateAngle() + (float) Math.PI;
Consumer<Float> rotationConsumer = this::cameraTick;
this.window.addRegularTask(rotationConsumer);
try {
Thread.sleep((long) (animationTime * 1000.0f));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
this.window.removeRegularTask(rotationConsumer);
this.camera.setRotateAngle(end);
}
@Override
public void onPlayerTurn(Color color, boolean undone) {
cameraRotate();
}
public void run() {
this.window.run();
// free OpenGL resources
try {
this.window.close();
this.world.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private void renderPopup(String title, String text) {
ImVec2 center = ImGui.getMainViewport().getCenter();
ImGui.setNextWindowPos(center, ImGuiCond.Appearing, new ImVec2(0.5f, 0.5f));
if (ImGui.beginPopupModal(title, null, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoMove)) {
ImGui.text(text);
if (ImGui.button("Close")) {
ImGui.closeCurrentPopup();
synchronized (this) {
notifyAll();
}
}
ImGui.endPopup();
}
}
private void renderPopups() {
renderPopup("Check", "Your king is in check");
renderPopup("Checkmate", "Checkmate, it's a win!");
renderPopup("Promotion", "Please promote your pawn.");
renderPopup("Pat", "It's a pat!");
renderPopup("Tie", "It's a tie!");
renderPopup("White surrender", "The white player has surrendered!");
renderPopup("Black surrender", "The black player has surrendered!");
renderPopup("White victory", "The white player has won !");
renderPopup("Black victory", "The black player has won !");
renderPopup("End", "End of the game, thank you for playing!");
}
private void openPopup(String title) {
this.waitingPopup = title;
// block the current thread until the popup is closed
synchronized (this) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void onKingInCheck() {
openPopup("Check");
}
@Override
public void onDraw() {
openPopup("Tie");
}
@Override
public void onKingInMat() {
openPopup("Checkmate");
}
@Override
public void onGameEnd() {
openPopup("End");
this.window.stop();
}
@Override
public void onWin(Color color) {
openPopup(color == Color.White ? "Black victory" : "White victory");
}
@Override
public void onPatSituation() {
openPopup("Pat");
}
@Override
public void onSurrender(Color color) {
openPopup(color == Color.White ? "White surrender" : "Black surrender");
}
@Override
public void onCastling(boolean bigCastling, Move kingMove, Move rookMove) {
move3DPieces(List.of(kingMove, rookMove));
}
}

View File

@@ -0,0 +1,77 @@
package chess.view.DDDrender;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT;
import java.io.Closeable;
import java.io.IOException;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.opengl.GL30;
import chess.view.DDDrender.opengl.FrameBuffer;
import chess.view.DDDrender.opengl.VertexArray;
import chess.view.DDDrender.shader.BoardShader;
import chess.view.DDDrender.shader.PieceShader;
import chess.view.DDDrender.shader.ShaderProgram;
public class Renderer implements Closeable {
private BoardShader boardShader;
private PieceShader pieceShader;
private FrameBuffer frameBuffer;
public Renderer() {
this.boardShader = new BoardShader();
this.pieceShader = new PieceShader();
}
public void Init(float windowWidth, float widowHeight) {
boardShader.LoadShader();
pieceShader.LoadShader();
this.frameBuffer = new FrameBuffer((int) windowWidth, (int) (widowHeight * 8.0f / 10.0f));
}
public void Update(Camera cam) {
this.boardShader.Start();
this.boardShader.SetCamMatrix(cam);
this.pieceShader.Start();
this.pieceShader.SetCamMatrix(cam);
}
public void Render(DDDModel model, Vector3f color, Matrix4f transform) {
this.pieceShader.Start();
this.pieceShader.setModelColor(color);
this.pieceShader.setModelTransform(transform);
Render(model);
}
public void Render(DDDModel model) {
for (int i = 0; i < model.getVaos().size(); i++) {
VertexArray vao = model.getVaos().get(i);
RenderVao(this.pieceShader, vao);
}
}
public void RenderVao(ShaderProgram shader, VertexArray vertexArray) {
shader.Start();
vertexArray.Bind();
GL30.glDrawElements(GL30.GL_TRIANGLES, vertexArray.GetVertexCount(), GL_UNSIGNED_INT, 0);
vertexArray.Unbind();
}
public FrameBuffer getFrameBuffer() {
return frameBuffer;
}
public BoardShader getBoardShader() {
return boardShader;
}
@Override
public void close() throws IOException {
this.boardShader.close();
this.pieceShader.close();
this.frameBuffer.close();
}
}

View File

@@ -0,0 +1,319 @@
package chess.view.DDDrender;
import common.AssetManager;
import common.Signal0;
import org.joml.Vector2f;
import org.lwjgl.*;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.*;
import org.lwjgl.system.*;
import chess.model.Coordinate;
import chess.view.DDDrender.opengl.FrameBuffer;
import chess.view.DDDrender.world.Entity;
import chess.view.DDDrender.world.World;
import common.Signal1;
import imgui.ImFontConfig;
import imgui.ImGui;
import imgui.ImVec2;
import imgui.flag.ImGuiWindowFlags;
import imgui.gl3.ImGuiImplGl3;
import imgui.glfw.ImGuiImplGlfw;
import imgui.type.ImBoolean;
import imgui.type.ImInt;
import java.io.Closeable;
import java.io.IOException;
import java.nio.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.function.Consumer;
import static org.lwjgl.glfw.Callbacks.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.system.MemoryStack.*;
import static org.lwjgl.system.MemoryUtil.*;
public class Window implements Closeable {
// The window handle
private long window;
private final ImGuiImplGl3 implGl3 = new ImGuiImplGl3();
private final ImGuiImplGlfw implGlfw = new ImGuiImplGlfw();
private Renderer renderer;
private final Camera cam;
private final World world;
private final Queue<Runnable> tasks;
private final List<Consumer<Float>> regularTasks;
private Coordinate lastCell = null;
public final Signal1<Coordinate> OnCellClick = new Signal1<>();
public final Signal1<Coordinate> OnCellEnter = new Signal1<>();
public final Signal1<Coordinate> OnCellExit = new Signal1<>();
public final Signal0 OnImGuiTopRender = new Signal0();
public final Signal0 OnImGuiBottomRender = new Signal0();
private ImInt detailLevel = new ImInt(10);
private ImBoolean pixelatedFrame = new ImBoolean(true);
private boolean shouldBeClosed = false;
public Window(Renderer renderer, World world, Camera camera) {
this.renderer = new Renderer();
this.cam = camera;
this.tasks = new ConcurrentLinkedDeque<>();
this.world = world;
this.regularTasks = new ArrayList<>();
}
public synchronized void addRegularTask(Consumer<Float> task) {
this.regularTasks.add(task);
}
public synchronized void removeRegularTask(Consumer<Float> task) {this.regularTasks.remove(task);}
public synchronized void scheduleTask(Runnable runnable) {
this.tasks.add(runnable);
}
public synchronized Runnable getNextTask() {
return this.tasks.poll();
}
public void run() {
System.out.println("LWJGL " + Version.getVersion() + "!");
init();
loop();
// Free the window callbacks and destroy the window
glfwFreeCallbacks(window);
glfwDestroyWindow(window);
// Terminate GLFW and free the error callback
glfwTerminate();
glfwSetErrorCallback(null).free();
implGl3.shutdown();
implGlfw.shutdown();
ImGui.destroyContext();
}
private void initImGui() {
ImGui.setCurrentContext(ImGui.createContext());
implGl3.init("#version 330");
implGlfw.init(window, true);
ImFontConfig config = new ImFontConfig();
config.setFontDataOwnedByAtlas(false);
try {
ImGui.getIO().getFonts().addFontFromMemoryTTF(AssetManager.getResource("fonts/comic.ttf").readAllBytes(),
50.0f, config);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void init() {
// Setup an error callback. The default implementation
// will print the error message in System.err.
GLFWErrorCallback.createPrint(System.err).set();
// Initialize GLFW. Most GLFW functions will not work before doing this.
if (!glfwInit())
throw new IllegalStateException("Unable to initialize GLFW");
// Configure GLFW
glfwDefaultWindowHints(); // optional, the current window hints are already the default
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // the window will stay hidden after creation
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // the window will be resizable
// Create the window
window = glfwCreateWindow(1000, 1000, "3DChess", NULL, NULL);
if (window == NULL)
throw new RuntimeException("Failed to create the GLFW window");
// Get the thread stack and push a new frame
try (MemoryStack stack = stackPush()) {
IntBuffer pWidth = stack.mallocInt(1); // int*
IntBuffer pHeight = stack.mallocInt(1); // int*
// Get the window size passed to glfwCreateWindow
glfwGetWindowSize(window, pWidth, pHeight);
// Get the resolution of the primary monitor
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
// Center the window
glfwSetWindowPos(
window,
(vidmode.width() - pWidth.get(0)) / 2,
(vidmode.height() - pHeight.get(0)) / 2);
glfwSetWindowSize(window, vidmode.width(), vidmode.height());
// Make the OpenGL context current
glfwMakeContextCurrent(window);
GL.createCapabilities();
initImGui();
renderer.Init(vidmode.width(), vidmode.height());
} // the stack frame is popped automatically
// Enable v-sync
glfwSwapInterval(1);
// Make the window visible
glfwShowWindow(window);
}
private void render(float delta, float aspectRatio) {
final FrameBuffer frameBuffer = this.renderer.getFrameBuffer();
cam.setAspectRatio(frameBuffer.getAspectRatio());
frameBuffer.Bind();
frameBuffer.Clear();
renderer.Update(cam);
renderWorld();
frameBuffer.Unbind();
}
private void renderWorld() {
for (Entity entity : this.world.getEntites()) {
entity.render(this.renderer);
}
}
private synchronized void executeTasks(float delta) {
Runnable task = getNextTask();
while (task != null) {
task.run();
task = getNextTask();
}
for (Consumer<Float> consumer : regularTasks) {
consumer.accept(delta);
}
}
private void checkCursor(float cursorPosX, float cursorPosY, int windowWidth, int windowHeight) {
Vector2f cursorPos = this.cam.getCursorWorldFloorPos(new Vector2f(cursorPosX, cursorPosY), windowWidth,
windowHeight);
Coordinate selectedCell = DDDPlacement.vectorToCoordinates(cursorPos);
if (this.lastCell == null) {
this.lastCell = selectedCell;
if (selectedCell.isValid())
this.OnCellEnter.emit(selectedCell);
} else if (!this.lastCell.equals(selectedCell)) {
if (this.lastCell.isValid())
this.OnCellExit.emit(this.lastCell);
if (selectedCell.isValid())
this.OnCellEnter.emit(selectedCell);
this.lastCell = selectedCell;
}
if (ImGui.getIO().getMouseClicked(0)) {
if (this.lastCell != null && this.lastCell.isValid())
this.OnCellClick.emit(this.lastCell);
}
}
private void newFrame() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer
implGlfw.newFrame();
implGl3.newFrame();
ImGui.newFrame();
}
private void renderWindow() {
ImGui.showDemoWindow();
final FrameBuffer frameBuffer = this.renderer.getFrameBuffer();
final int frameWidth = (int) ImGui.getIO().getDisplaySizeX();
final int frameHeight = (int) (ImGui.getIO().getDisplaySizeY() * 8.0f / 10.0f);
ImGui.setNextWindowSize(ImGui.getIO().getDisplaySize());
ImGui.setNextWindowPos(new ImVec2());
ImGui.begin("Main Window", ImGuiWindowFlags.NoDecoration);
this.OnImGuiTopRender.emit();
ImVec2 mousePos = ImGui.getIO().getMousePos();
ImVec2 framePos = ImGui.getCursorScreenPos();
checkCursor(mousePos.x - framePos.x, frameHeight - (mousePos.y - framePos.y), frameWidth, frameHeight);
ImGui.image(frameBuffer.getRenderTexture(), new ImVec2(frameWidth, frameHeight));
this.OnImGuiBottomRender.emit();
if(ImGui.sliderInt("Niveau de détail", detailLevel.getData(), 1, 10)){
frameBuffer.Resize((int) ((float) frameWidth * detailLevel.get() / 10.0f), (int) ((float) frameHeight * detailLevel.get() / 10.0f));
}
if (ImGui.checkbox("Minecraft", pixelatedFrame)) {
frameBuffer.SetPixelScaling(pixelatedFrame.get());
}
ImGui.end();
}
private void loop() {
// Set the clear color
glClearColor(0.4f, 0.4f, 0.6f, 1.0f);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glCullFace(GL_FRONT);
glFrontFace(GL_CW);
glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
int width[] = new int[1];
int height[] = new int[1];
glfwGetWindowSize(window, width, height);
// Run the rendering loop until the user has attempted to close
// the window or has pressed the ESCAPE key.
while (!shouldBeClosed && !glfwWindowShouldClose(window)) {
newFrame();
render(ImGui.getIO().getDeltaTime(), (float) width[0] / (float) height[0]);
renderWindow();
ImGui.render();
implGl3.renderDrawData(ImGui.getDrawData());
glfwSwapBuffers(window); // swap the color buffers
// Poll for window events. The key callback above will only be
// invoked during this call.
glfwPollEvents();
executeTasks(ImGui.getIO().getDeltaTime());
glfwGetWindowSize(window, width, height);
glViewport(0, 0, width[0], height[0]);
}
}
public void stop() {
shouldBeClosed = true;
}
@Override
public void close() throws IOException {
shouldBeClosed = true;
this.renderer.close();
}
}

View File

@@ -0,0 +1,98 @@
package chess.view.DDDrender.loader;
import org.joml.Vector3f;
import chess.view.DDDrender.opengl.ElementBuffer;
import chess.view.DDDrender.opengl.VertexArray;
import chess.view.DDDrender.opengl.VertexBuffer;
public class BoardModelLoader {
private static final int BOARD_WIDTH = 8;
private static final int BOARD_HEIGHT = 8;
private static final int BOARD_SIZE = BOARD_WIDTH * BOARD_HEIGHT;
private static final int SQUARE_VERTEX_COUNT = 4;
private static final Vector3f WHITE = new Vector3f(1, 1, 1);
private static final Vector3f BLACK = new Vector3f(0, 0, 0);
private static float[] GetBoardPositions() {
float[] positions = new float[BOARD_SIZE * SQUARE_VERTEX_COUNT * 3];
for (int i = 0; i < BOARD_WIDTH; i++) {
for (int j = 0; j < BOARD_HEIGHT; j++) {
float x = i / (float) BOARD_WIDTH;
float dx = (i + 1) / (float) BOARD_WIDTH;
float z = j / (float) BOARD_HEIGHT;
float dz = (j + 1) / (float) BOARD_HEIGHT;
float trueX = 2 * x - 1;
float trueZ = 2 * z - 1;
float trueDX = 2 * dx - 1;
float trueDZ = 2 * dz - 1;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3] = trueX;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 1] = 0.0f;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 2] = trueZ;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 3] = trueDX;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 4] = 0.0f;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 5] = trueZ;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 6] = trueX;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 7] = 0.0f;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 8] = trueDZ;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 9] = trueDX;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 10] = 0.0f;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 11] = trueDZ;
}
}
return positions;
}
private static float[] GetBoardColors() {
float[] colors = new float[BOARD_SIZE * SQUARE_VERTEX_COUNT * 3];
for (int i = 0; i < BOARD_WIDTH; i++) {
for (int j = 0; j < BOARD_HEIGHT; j++) {
Vector3f color = (i + j) % 2 == 0 ? WHITE : BLACK;
int squareIndex = i * BOARD_WIDTH + j;
for (int k = 0; k < SQUARE_VERTEX_COUNT; k++) {
colors[squareIndex * SQUARE_VERTEX_COUNT * 3 + k * 3] = color.x;
colors[squareIndex * SQUARE_VERTEX_COUNT * 3 + k * 3 + 1] = color.y;
colors[squareIndex * SQUARE_VERTEX_COUNT * 3 + k * 3 + 2] = color.z;
}
}
}
return colors;
}
private static int[] GetBoardIndicies() {
int[] indices = new int[BOARD_SIZE * 6];
for (int i = 0; i < BOARD_SIZE; i++) {
indices[i * 6] = i * 4;
indices[i * 6 + 1] = i * 4 + 2;
indices[i * 6 + 2] = i * 4 + 1;
indices[i * 6 + 3] = i * 4 + 1;
indices[i * 6 + 4] = i * 4 + 2;
indices[i * 6 + 5] = i * 4 + 3;
}
return indices;
}
public static VertexArray GetBoardModel() {
ElementBuffer eBuffer = new ElementBuffer(GetBoardIndicies());
VertexArray vao = new VertexArray(eBuffer);
VertexBuffer positionBuffer = new VertexBuffer(GetBoardPositions(), 3);
positionBuffer.AddVertexAttribPointer(0, 3, 0);
VertexBuffer colorBuffer = new VertexBuffer(GetBoardColors(), 3);
colorBuffer.AddVertexAttribPointer(1, 3, 0);
vao.Bind();
vao.BindVertexBuffer(positionBuffer);
vao.BindVertexBuffer(colorBuffer);
vao.Unbind();
return vao;
}
}

View File

@@ -0,0 +1,130 @@
package chess.view.DDDrender.loader;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
import org.lwjgl.assimp.AIFace.Buffer;
import org.lwjgl.assimp.AIMesh;
import org.lwjgl.assimp.AINode;
import org.lwjgl.assimp.AIScene;
import org.lwjgl.assimp.AIVector3D;
import org.lwjgl.assimp.Assimp;
import org.lwjgl.system.MemoryUtil;
import chess.view.DDDrender.DDDModel;
import chess.view.DDDrender.opengl.ElementBuffer;
import chess.view.DDDrender.opengl.VertexArray;
import chess.view.DDDrender.opengl.VertexBuffer;
import common.AssetManager;
public class ModelLoader {
private static final int VERTEX_SIZE = 3;
private static final int VERTEX_POSITION_INDEX = 0;
private static final int VERTEX_NORMAL_INDEX = 1;
private static float[] toFloatArray(List<Float> list) {
float[] result = new float[list.size()];
for (int i = 0; i < list.size(); i++) {
result[i] = list.get(i);
}
return result;
}
private static int[] toIntArray(List<Integer> list) {
int[] result = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
result[i] = list.get(i);
}
return result;
}
private static VertexArray processMesh(AIMesh mesh, AIScene scene) {
List<Float> positions = new ArrayList<>();
List<Float> normals = new ArrayList<>();
List<Integer> indicies = new ArrayList<>();
Buffer faces = mesh.mFaces();
int faceNumber = mesh.mNumFaces();
for (int i = 0; i < faceNumber; i++) {
IntBuffer faceIndicies = faces.get(i).mIndices();
for (int j = 0; j < faceIndicies.capacity(); j++) {
indicies.add(faceIndicies.get(j));
}
}
int vertNumber = mesh.mNumVertices();
org.lwjgl.assimp.AIVector3D.Buffer vertecies = mesh.mVertices();
for (int i = 0; i < vertNumber; i++) {
AIVector3D vertex = vertecies.get(i);
positions.add(vertex.x());
positions.add(vertex.y());
positions.add(vertex.z());
}
org.lwjgl.assimp.AIVector3D.Buffer vertexNormals = mesh.mNormals();
for (int i = 0; i < vertNumber; i++) {
AIVector3D normal = vertexNormals.get(i);
normals.add(normal.x());
normals.add(normal.y());
normals.add(normal.z());
}
VertexBuffer positionVBO = new VertexBuffer(toFloatArray(positions), VERTEX_SIZE);
positionVBO.AddVertexAttribPointer(VERTEX_POSITION_INDEX, VERTEX_SIZE, 0);
VertexBuffer normalVBO = new VertexBuffer(toFloatArray(normals), VERTEX_SIZE);
normalVBO.AddVertexAttribPointer(VERTEX_NORMAL_INDEX, VERTEX_SIZE, 0);
VertexArray vao = new VertexArray(new ElementBuffer(toIntArray(indicies)));
vao.Bind();
vao.BindVertexBuffer(positionVBO);
vao.BindVertexBuffer(normalVBO);
vao.Unbind();
return vao;
}
private static void processNode(AINode node, AIScene scene, List<VertexArray> meshes) {
for (int i = 0; i < node.mNumChildren(); i++) {
AINode child = AINode.create(node.mChildren().get(i));
processNode(child, scene, meshes);
}
for (int i = 0; i < node.mNumMeshes(); i++) {
AIMesh mesh = AIMesh.create(scene.mMeshes().get(node.mMeshes().get(i)));
meshes.add(processMesh(mesh, scene));
}
}
public static DDDModel loadModel(String filename) throws IOException {
InputStream input = AssetManager.getResource(filename);
byte[] buffer = input.readAllBytes();
ByteBuffer data = MemoryUtil.memCalloc(buffer.length);
data.put(buffer);
data.flip();
AIScene scene = Assimp.aiImportFileFromMemory(
data,
Assimp.aiProcess_Triangulate | Assimp.aiProcess_PreTransformVertices | Assimp.aiProcess_GlobalScale
| Assimp.aiProcess_ValidateDataStructure,
"");
if (scene == null)
System.err.println(Assimp.aiGetErrorString());
List<VertexArray> vertecies = new ArrayList<>();
processNode(scene.mRootNode(), scene, vertecies);
MemoryUtil.memFree(data);
return new DDDModel(vertecies);
}
}

View File

@@ -0,0 +1,75 @@
package chess.view.DDDrender.loader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import chess.model.Color;
import chess.model.Piece;
import chess.model.PieceVisitor;
import chess.model.pieces.Bishop;
import chess.model.pieces.King;
import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
import chess.view.DDDrender.DDDModel;
public class Piece3DModel implements PieceVisitor<String> {
private static final String basePath = "3d/";
private static final Map<String, DDDModel> cache = new HashMap<>();
public DDDModel getModel(Piece piece) throws IOException {
if (piece == null)
return null;
String path = basePath + colorToString(piece.getColor()) + "-" + visit(piece) + ".fbx";
return getModel(path);
}
private DDDModel getModel(String path) throws IOException {
DDDModel model = cache.get(path);
if (model != null)
return model;
model = ModelLoader.loadModel(path);
cache.put(path, model);
return model;
}
private String colorToString(Color color) {
return color == Color.Black ? "black" : "white";
}
@Override
public String visitPiece(Bishop bishop) {
return "bishop";
}
@Override
public String visitPiece(King king) {
return "king";
}
@Override
public String visitPiece(Knight knight) {
return "knight";
}
@Override
public String visitPiece(Pawn pawn) {
return "pawn";
}
@Override
public String visitPiece(Queen queen) {
return "queen";
}
@Override
public String visitPiece(Rook rook) {
return "rook";
}
}

View File

@@ -1,10 +1,13 @@
package chess.view.render;
package chess.view.DDDrender.opengl;
import java.io.Closeable;
import java.io.IOException;
import org.lwjgl.opengl.GL30;
public class ElementBuffer {
private int id;
private int indiciesCount;
public class ElementBuffer implements Closeable {
private final int id;
private final int indiciesCount;
public ElementBuffer(int[] indicies) {
this.indiciesCount = indicies.length;
@@ -15,10 +18,6 @@ public class ElementBuffer {
Unbind();
}
public void Destroy() {
GL30.glDeleteBuffers(this.id);
}
public void Bind() {
GL30.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.id);
}
@@ -30,4 +29,9 @@ public class ElementBuffer {
public int GetIndiciesCount() {
return this.indiciesCount;
}
@Override
public void close() throws IOException {
GL30.glDeleteBuffers(this.id);
}
}

View File

@@ -0,0 +1,116 @@
package chess.view.DDDrender.opengl;
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_COMPONENT;
import static org.lwjgl.opengl.GL11.GL_RGB;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL11.glBindTexture;
import static org.lwjgl.opengl.GL11.glClear;
import static org.lwjgl.opengl.GL11.glTexImage2D;
import static org.lwjgl.opengl.GL11.glViewport;
import java.io.Closeable;
import java.io.IOException;
import org.lwjgl.opengl.GL30;
public class FrameBuffer implements Closeable {
private int fbo;
private int renderTexture;
private int depthBuffer;
private int width;
private int height;
public FrameBuffer(int width, int height) {
this.width = width;
this.height = height;
// The frame buffer
this.fbo = GL30.glGenFramebuffers();
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
// The texture
this.renderTexture = GL30.glGenTextures();
glBindTexture(GL_TEXTURE_2D, renderTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
GL30.glTexParameteri(GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST);
GL30.glTexParameteri(GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST);
// The depth buffer
this.depthBuffer = GL30.glGenRenderbuffers();
GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depthBuffer);
GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height);
GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_RENDERBUFFER,
depthBuffer);
// Set "renderedTexture" as our colour attachement #0
GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, renderTexture, 0);
// Set the list of draw buffers.
int[] drawBuffers = { GL30.GL_COLOR_ATTACHMENT0 };
GL30.glDrawBuffers(drawBuffers);
}
public void Bind() {
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, this.fbo);
glViewport(0, 0, this.width, this.height);
}
public void Unbind() {
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
}
public void Clear() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer
}
public void Resize(int width, int height) {
this.width = width;
this.height = height;
GL30.glBindTexture(GL_TEXTURE_2D, this.renderTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, this.width, this.height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, this.depthBuffer);
GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL_DEPTH_COMPONENT, this.width, this.height);
}
public void SetPixelScaling(boolean pixelated) {
glBindTexture(GL_TEXTURE_2D, renderTexture);
int method = pixelated ? GL30.GL_NEAREST : GL30.GL_LINEAR;
GL30.glTexParameteri(GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, method);
GL30.glTexParameteri(GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, method);
}
public float getAspectRatio() {
return (float) width / (float) height;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public int getRenderTexture() {
return renderTexture;
}
@Override
public void close() throws IOException {
GL30.glDeleteFramebuffers(this.fbo);
GL30.glDeleteRenderbuffers(this.depthBuffer);
GL30.glDeleteTextures(this.renderTexture);
}
}

View File

@@ -1,14 +1,17 @@
package chess.view.render;
package chess.view.DDDrender.opengl;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.lwjgl.opengl.GL30;
public class VertexArray {
private int id;
private ElementBuffer elementBuffer;
private List<VertexBuffer> vertexBuffers;
public class VertexArray implements Closeable {
private final int id;
private final ElementBuffer elementBuffer;
private final List<VertexBuffer> vertexBuffers;
public VertexArray(ElementBuffer elementBuffer) {
this.id = GL30.glGenVertexArrays();
@@ -19,10 +22,6 @@ public class VertexArray {
Unbind();
}
public void Destroy() {
GL30.glDeleteBuffers(this.id);
}
public int GetVertexCount() {
return this.elementBuffer.GetIndiciesCount();
}
@@ -44,4 +43,17 @@ public class VertexArray {
private void BindElementArrayBuffer() {
this.elementBuffer.Bind();
}
public List<VertexBuffer> getVertexBuffers() {
return vertexBuffers;
}
@Override
public void close() throws IOException {
GL30.glDeleteBuffers(this.id);
for (VertexBuffer vertexBuffer : vertexBuffers) {
vertexBuffer.close();
}
elementBuffer.close();
}
}

View File

@@ -1,4 +1,4 @@
package chess.view.render;
package chess.view.DDDrender.opengl;
public record VertexAttribPointer(int index, int size, int offset) {

View File

@@ -1,16 +1,18 @@
package chess.view.render;
package chess.view.DDDrender.opengl;
import static org.lwjgl.opengl.GL11.GL_FLOAT;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.lwjgl.opengl.GL30;
public class VertexBuffer {
private int id;
private int dataStride;
List<VertexAttribPointer> vertexAttribs;
public class VertexBuffer implements Closeable {
private final int id;
private final int dataStride;
private final List<VertexAttribPointer> vertexAttribs;
public VertexBuffer(float[] data, int stride) {
this.id = GL30.glGenBuffers();
@@ -22,8 +24,10 @@ public class VertexBuffer {
Unbind();
}
public void Destroy() {
GL30.glDeleteBuffers(id);
public void UpdateData(int offset, float[] data) {
Bind();
GL30.glBufferSubData(GL30.GL_ARRAY_BUFFER, offset, data);
Unbind();
}
public void Bind() {
@@ -46,4 +50,9 @@ public class VertexBuffer {
this.dataStride * 4, vertexAttribPointer.offset());
}
}
@Override
public void close() throws IOException {
GL30.glDeleteBuffers(id);
}
}

View File

@@ -0,0 +1,102 @@
package chess.view.DDDrender.shader;
import chess.view.DDDrender.Camera;
public class BoardShader extends ShaderProgram {
private static String vertexShader = """
#version 330
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform vec3 lightPosition = vec3(0, 10, 0);
flat out vec3 pass_color;
out vec3 toLightVector;
out vec3 toCameraVector;
out vec3 surfaceNormal;
void main(void){
const vec4 normal = vec4(0.0, 1.0, 0.0, 1.0);
gl_Position = projectionMatrix * viewMatrix * vec4(position, 1.0);
vec3 camPos = (inverse(viewMatrix) * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
toLightVector = lightPosition - position;
toCameraVector = camPos - position;
surfaceNormal = (normal).xyz;
pass_color = color;
}
""";
private static String fragmentShader = """
#version 330
flat in vec3 pass_color;
in vec3 toLightVector;
in vec3 toCameraVector;
in vec3 surfaceNormal;
layout(location = 0) out vec4 out_color;
void main(void){
const float shineDamper = 10.0;
const float reflectivity = 1.0;
float lightDistance = length(toLightVector);
const vec3 attenuation = vec3(0.2, 0.1, 0.0);
float attenuationFactor = attenuation.x + attenuation.y * lightDistance + attenuation.z * lightDistance * lightDistance;
vec3 unitNormal = normalize(surfaceNormal);
vec3 unitLightVector = normalize(toLightVector);
vec3 unitCamVector = normalize(toCameraVector);
vec3 lightDirection = -unitLightVector;
vec3 reflectedLightDirection = reflect(lightDirection, unitNormal);
float diffuse = max(0.2, dot(unitNormal, unitLightVector));
float specularFactor = max(0.0, dot(reflectedLightDirection, unitCamVector));
float specular = pow(specularFactor, shineDamper) * reflectivity;
float brightness = (diffuse + specular) / attenuationFactor;
out_color = brightness * vec4(pass_color, 1.0);
out_color.w = 1.0;
}
""";
private int location_ProjectionMatrix = 0;
private int location_ViewMatrix = 0;
public BoardShader() {
}
public void LoadShader() {
super.LoadProgram(vertexShader, fragmentShader);
}
@Override
protected void GetAllUniformLocation() {
location_ProjectionMatrix = GetUniformLocation("projectionMatrix");
location_ViewMatrix = GetUniformLocation("viewMatrix");
}
public void SetCamMatrix(Camera camera) {
LoadMat4(location_ProjectionMatrix, camera.getPerspectiveMatrix());
LoadMat4(location_ViewMatrix, camera.getViewMatrix());
}
}

View File

@@ -0,0 +1,116 @@
package chess.view.DDDrender.shader;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import chess.view.DDDrender.Camera;
public class PieceShader extends ShaderProgram {
private static String vertexShader = """
#version 330
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelTransform;
uniform vec3 lightPosition = vec3(0, 10, 0);
out vec3 toLightVector;
out vec3 toCameraVector;
out vec3 surfaceNormal;
void main(void){
vec4 modelPos = modelTransform * vec4(position, 1.0);
vec4 globalNormal = modelTransform * vec4(normal, 1.0);
gl_Position = projectionMatrix * viewMatrix * modelPos;
vec3 camPos = (inverse(viewMatrix) * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
toLightVector = lightPosition - modelPos.xyz;
toCameraVector = camPos - position;
surfaceNormal = normalize(globalNormal.xyz);
}
""";
private static String fragmentShader = """
#version 330
in vec3 toLightVector;
in vec3 toCameraVector;
in vec3 surfaceNormal;
uniform vec3 modelColor;
layout(location = 0) out vec4 out_color;
void main(void){
const float shineDamper = 10.0;
const float reflectivity = 1.0;
float lightDistance = length(toLightVector);
const vec3 attenuation = vec3(0.2, 0.1, 0.0);
float attenuationFactor = attenuation.x + attenuation.y * lightDistance + attenuation.z * lightDistance * lightDistance;
vec3 unitNormal = normalize(surfaceNormal);
vec3 unitLightVector = normalize(toLightVector);
vec3 unitCamVector = normalize(toCameraVector);
vec3 lightDirection = -unitLightVector;
vec3 reflectedLightDirection = reflect(lightDirection, unitNormal);
float diffuse = max(0.2, dot(unitNormal, unitLightVector));
float specularFactor = max(0.0, dot(reflectedLightDirection, unitCamVector));
float specular = pow(specularFactor, shineDamper) * reflectivity;
float brightness = (diffuse + specular) / attenuationFactor;
out_color = brightness * vec4(modelColor, 1.0);
out_color.w = 1.0;
}
""";
private int location_ProjectionMatrix = 0;
private int location_ViewMatrix = 0;
private int location_ModelTransform = 0;
private int location_ModelColor = 0;
public PieceShader() {
}
public void LoadShader() {
super.LoadProgram(vertexShader, fragmentShader);
}
@Override
protected void GetAllUniformLocation() {
location_ProjectionMatrix = GetUniformLocation("projectionMatrix");
location_ViewMatrix = GetUniformLocation("viewMatrix");
location_ModelTransform = GetUniformLocation("modelTransform");
location_ModelColor = GetUniformLocation("modelColor");
}
public void SetCamMatrix(Camera camera) {
LoadMat4(location_ProjectionMatrix, camera.getPerspectiveMatrix());
LoadMat4(location_ViewMatrix, camera.getViewMatrix());
}
public void setModelTransform(Matrix4f mat) {
LoadMat4(location_ModelTransform, mat);
}
public void setModelColor(Vector3f color) {
LoadVector(location_ModelColor, color);
}
}

View File

@@ -1,5 +1,7 @@
package chess.view.render.shader;
package chess.view.DDDrender.shader;
import java.io.Closeable;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
@@ -9,7 +11,7 @@ import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL30;
import org.lwjgl.system.MemoryStack;
public abstract class ShaderProgram {
public abstract class ShaderProgram implements Closeable {
private int programId;
private int vertexShaderId;
private int fragmentShaderId;
@@ -56,8 +58,8 @@ public abstract class ShaderProgram {
if (compileSuccesful.get() != 1) {
System.out.println("Shader did not compile !");
System.err.println(GL30.glGetShaderInfoLog(shaderId));
return -1;
}
return shaderId;
@@ -68,7 +70,7 @@ public abstract class ShaderProgram {
protected int GetUniformLocation(String uniformName) {
int location = GL30.glGetUniformLocation(programId, uniformName);
if (location == -1) {
System.out.println("Uniform value not found !");
System.out.println("Uniform value \"" + uniformName + "\" not found !");
}
return location;
}
@@ -91,4 +93,11 @@ public abstract class ShaderProgram {
GL30.glUniformMatrix4fv(location, false, buffer);
}
}
@Override
public void close() throws IOException {
GL30.glDeleteShader(vertexShaderId);
GL30.glDeleteShader(fragmentShaderId);
GL30.glDeleteProgram(programId);
}
}

View File

@@ -0,0 +1,54 @@
package chess.view.DDDrender.world;
import java.io.IOException;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import chess.model.Coordinate;
import chess.view.DDDrender.Renderer;
import chess.view.DDDrender.loader.BoardModelLoader;
import chess.view.DDDrender.opengl.VertexArray;
import chess.view.DDDrender.opengl.VertexBuffer;
public class BoardEntity extends Entity {
private final VertexArray vao;
private final VertexBuffer colorVbo;
private static final Vector3f WHITE = new Vector3f(1, 1, 1);
private static final Vector3f BLACK = new Vector3f(0, 0, 0);
public BoardEntity() {
this.vao = BoardModelLoader.GetBoardModel();
this.colorVbo = this.vao.getVertexBuffers().get(1);
}
public void setCellColor(Coordinate coord, Vector3f color) {
float[] data = { color.x, color.y, color.z, color.x, color.y, color.z, color.x, color.y, color.z, color.x,
color.y, color.z};
int cellNumber = (Coordinate.VALUE_MAX - 1 - coord.getX()) * Coordinate.VALUE_MAX + Coordinate.VALUE_MAX - 1 - coord.getY();
int offset = cellNumber * 4 * 4 * 3;
this.colorVbo.UpdateData(offset, data);
}
public void resetCellColor(Coordinate coord) {
setCellColor(coord, (coord.getX() + coord.getY()) % 2 == 0 ? WHITE : BLACK);
}
@Override
public void render(Renderer renderer) {
renderer.RenderVao(renderer.getBoardShader(), vao);
}
@Override
public void close() throws IOException {
vao.close();
}
@Override
public Matrix4f getTransform() {
return null;
}
}

View File

@@ -0,0 +1,15 @@
package chess.view.DDDrender.world;
import java.io.Closeable;
import org.joml.Matrix4f;
import chess.view.DDDrender.Renderer;
public abstract class Entity implements Closeable{
public abstract void render(Renderer renderer);
public abstract Matrix4f getTransform();
}

View File

@@ -0,0 +1,61 @@
package chess.view.DDDrender.world;
import java.io.IOException;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import chess.view.DDDrender.DDDModel;
import chess.view.DDDrender.Renderer;
public class ModelEntity extends Entity {
protected Vector3f position;
protected Vector3f color;
protected float rotation;
protected DDDModel model;
private Matrix4f transform;
public ModelEntity(DDDModel model, Vector3f position, Vector3f color, float rotation) {
this.position = position;
this.color = color;
this.rotation = rotation;
this.model = model;
updateTransform();
}
@Override
public void render(Renderer renderer) {
renderer.Render(model, color, transform);
}
public void setPosition(Vector3f position) {
this.position = position;
updateTransform();
}
public void setColor(Vector3f color) {
this.color = color;
}
public void setRotation(float rotation) {
this.rotation = rotation;
updateTransform();
}
@Override
public void close() throws IOException {
this.model.close();
}
private void updateTransform() {
this.transform = new Matrix4f().translate(this.position).rotate(this.rotation, new Vector3f(0, 1, 0));
}
@Override
public Matrix4f getTransform() {
return transform;
}
}

View File

@@ -0,0 +1,25 @@
package chess.view.DDDrender.world;
import java.io.IOException;
import org.joml.Vector3f;
import chess.model.Piece;
import chess.view.DDDrender.loader.Piece3DModel;
public class PieceEntity extends ModelEntity {
private static final Piece3DModel modelLoader = new Piece3DModel();
private final Piece piece;
public PieceEntity(Piece piece, Vector3f position, Vector3f color, float rotation) throws IOException {
super(modelLoader.getModel(piece), position, color, rotation);
this.piece = piece;
}
public Piece getPiece() {
return piece;
}
}

View File

@@ -0,0 +1,74 @@
package chess.view.DDDrender.world;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import chess.model.Coordinate;
import chess.model.Move;
import chess.model.Piece;
public class World implements Closeable{
/** Renderable entity list */
private final List<Entity> entites;
/** provides fast access to 3d pieces */
private final PieceEntity[] pieces;
public World() {
this.entites = new ArrayList<>();
this.pieces = new PieceEntity[Coordinate.VALUE_MAX * Coordinate.VALUE_MAX];
}
public PieceEntity getEntity(Piece piece) {
for (Entity entity : entites) {
if (entity instanceof PieceEntity p) {
if (p.getPiece() == piece)
return p;
}
}
return null;
}
public void addPiece(PieceEntity entity, Coordinate coordinate) {
addEntity(entity);
setPieceCoords(entity, coordinate);
}
private void setPieceCoords(PieceEntity entity, Coordinate coordinate) {
pieces[coordinate.toIndex()] = entity;
}
public void movePiece(PieceEntity entity, Move move) {
setPieceCoords(entity, move.getFinish());
setPieceCoords(null, move.getStart());
}
public void ejectPiece(Coordinate coordinate) {
PieceEntity entity = getPiece(coordinate);
if (entity != null)
this.entites.remove(entity);
setPieceCoords(null, coordinate);
}
public PieceEntity getPiece(Coordinate coordinate) {
return pieces[coordinate.toIndex()];
}
public void addEntity(Entity entity) {
this.entites.add(entity);
}
public List<Entity> getEntites() {
return entites;
}
@Override
public void close() throws IOException {
for (Entity entity : entites) {
entity.close();
}
}
}

View File

@@ -0,0 +1,63 @@
package chess.view.audio;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import common.AssetManager;
public class AudioFiles {
private static final String baseURL = "https://images.chesscomfiles.com/chess-themes/sounds/_WAV_/default/";
private static final String[] files = { "game-start", "game-end", "capture", "castle", "move-self",
"move-check", "promote", "illegal" };
private static final String filesExtension = ".wav";
private static final String saveDir = "audio/";
public static boolean initFiles() {
createSaveDir();
for (String file : files) {
if (!initFile(file + filesExtension))
return false;
}
return true;
}
private static boolean downloadFile(String fileName) {
System.out.println("[GameAudio] missing " + fileName + " Downloading ...");
try (BufferedInputStream in = new BufferedInputStream(new URI(baseURL + fileName).toURL().openStream());
FileOutputStream fileOutputStream = new FileOutputStream(saveDir + fileName)) {
byte dataBuffer[] = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) {
fileOutputStream.write(dataBuffer, 0, bytesRead);
}
return true;
} catch (IOException | URISyntaxException e) {
e.printStackTrace();
return false;
}
}
private static void createSaveDir() {
new File(saveDir).mkdir();
}
private static boolean initFile(String fileName) {
if (isFileDownloaded(saveDir + fileName))
return true;
return downloadFile(fileName);
}
private static boolean isFileDownloaded(String fileName) {
return AssetManager.getResource(fileName) != null;
}
public static InputStream getAudio(String audioName) {
return AssetManager.getResource(saveDir + audioName + filesExtension);
}
}

View File

@@ -0,0 +1,44 @@
package chess.view.audio;
import java.io.BufferedInputStream;
import java.io.InputStream;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
public class AudioPlayer {
public static void playSound(InputStream audio) {
new Thread(new Runnable() {
// The wrapper thread is unnecessary, unless it blocks on the
// Clip finishing; see comments.
public void run() {
try {
Clip clip = AudioSystem.getClip();
BufferedInputStream bufferedInputStream = new BufferedInputStream(audio);
AudioInputStream inputStream = AudioSystem.getAudioInputStream(bufferedInputStream);
clip.open(inputStream);
clip.start();
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}).start();
}
private static AudioInputStream convertToPCM(AudioInputStream audioInputStream) {
AudioFormat m_format = audioInputStream.getFormat();
if ((m_format.getEncoding() != AudioFormat.Encoding.PCM_SIGNED) &&
(m_format.getEncoding() != AudioFormat.Encoding.PCM_UNSIGNED)) {
AudioFormat targetFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
m_format.getSampleRate(), 16,
m_format.getChannels(), m_format.getChannels() * 2,
m_format.getSampleRate(), m_format.isBigEndian());
audioInputStream = AudioSystem.getAudioInputStream(targetFormat, audioInputStream);
}
return audioInputStream;
}
}

View File

@@ -0,0 +1,63 @@
package chess.view.audio;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.controller.event.GameAdapter;
import chess.model.Move;
public class GameAudio extends GameAdapter {
private final boolean functional;
public GameAudio() {
this.functional = AudioFiles.initFiles();
if(!this.functional){
System.err.println("[GameAudio] Failed to initialize audio files. Aborting ...");
}
}
private void playSound(String soundName) {
if (!this.functional)
return;
AudioPlayer.playSound(AudioFiles.getAudio(soundName));
}
@Override
public void onGameStart() {
playSound("game-start");
}
@Override
public void onGameEnd() {
playSound("game-end");
}
@Override
public void onMoveNotAllowed(Move move) {
playSound("illegal");
}
@Override
public void onMove(Move move, boolean captured) {
if (captured) {
playSound("capture");
return;
}
playSound("move-self");
}
@Override
public void onKingInCheck() {
playSound("move-check");
}
@Override
public void onPawnPromoted(PromoteType promotion) {
playSound("promote");
}
@Override
public void onCastling(boolean bigCastling, Move kingMove, Move rookMove) {
playSound("castle");
}
}

View File

@@ -2,9 +2,10 @@ package chess.view.consolerender;
import chess.controller.Command;
import chess.controller.CommandExecutor;
import chess.controller.CommandSender;
import chess.controller.commands.*;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.controller.event.GameListener;
import chess.controller.event.GameAdapter;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
@@ -16,30 +17,21 @@ import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Console implements GameListener {
public class Console extends GameAdapter implements CommandSender {
private final Scanner scanner = new Scanner(System.in);
private final CommandExecutor commandExecutor;
private final ConsolePieceName consolePieceName = new ConsolePieceName();
private boolean captureInput = false;
private boolean captureInput;
private final ExecutorService executor;
public Console(CommandExecutor commandExecutor) {
public Console(CommandExecutor commandExecutor, boolean captureInput) {
this.commandExecutor = commandExecutor;
this.executor = Executors.newSingleThreadExecutor();
this.captureInput = captureInput;
}
private Piece pieceAt(int x, int y) {
return pieceAt(new Coordinate(x, y));
}
private Command.CommandResult sendCommand(Command command) {
return this.commandExecutor.executeCommand(command);
}
private Piece pieceAt(Coordinate coordinate) {
GetPieceAtCommand command = new GetPieceAtCommand(coordinate);
sendCommand(command);
return command.getPiece();
public Console(CommandExecutor commandExecutor) {
this(commandExecutor, true);
}
public Coordinate stringToCoordinate(String coordinates) throws Exception {
@@ -61,7 +53,7 @@ public class Console implements GameListener {
}
@Override
public void onPlayerTurn(Color color) {
public void onPlayerTurn(Color color, boolean undone) {
if (!captureInput)
return;
System.out.println(Colors.RED + "Player turn: " + color + Colors.RESET);
@@ -90,7 +82,7 @@ public class Console implements GameListener {
}
private boolean playerPickedSurrender(Color color) {
sendCommand(new SurrenderCommand(color));
sendSurrender(color);
return true;
}
@@ -104,7 +96,7 @@ public class Console implements GameListener {
Coordinate start = stringToCoordinate(answer);
System.out.println("New position: ");
Coordinate end = stringToCoordinate(scanner.nextLine());
Command.CommandResult result = sendCommand(new MoveCommand(new Move(start, end)));
Command.CommandResult result = sendMove(new Move(start, end));
return switch (Objects.requireNonNull(result)) {
case Command.CommandResult.Moved, Command.CommandResult.ActionNeeded -> true;
@@ -121,12 +113,7 @@ public class Console implements GameListener {
try {
System.out.println("Piece to examine: ");
Coordinate piece = stringToCoordinate(scanner.nextLine());
GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(piece);
if (sendCommand(movesCommand) == Command.CommandResult.NotAllowed) {
System.out.println("Not allowed.");
return false;
}
List<Coordinate> allowedMoves = movesCommand.getDestinations();
List<Coordinate> allowedMoves = getPieceAllowedMoves(piece);
if (allowedMoves.isEmpty()) {
System.out.println("No moves allowed for this piece.");
return false;
@@ -196,7 +183,7 @@ public class Console implements GameListener {
default -> throw new Exception();
};
valid = true;
sendCommand(new PromoteCommand(newPiece));
sendPawnPromotion(newPiece);
} catch (Exception e) {
System.out.println("Invalid input!");
}
@@ -213,7 +200,7 @@ public class Console implements GameListener {
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
string.append(8 - i).append(" ");
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
Piece p = pieceAt(j, i);
Piece p = getPieceAt(new Coordinate(j, i));
if ((i + j) % 2 == 0) {
string.append(Colors.LIGHT_GRAY_BACKGROUND);
} else {
@@ -238,7 +225,7 @@ public class Console implements GameListener {
string.append(8 - i).append(" ");
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
Coordinate currentCell = new Coordinate(j, i);
Piece p = pieceAt(j, i);
Piece p = getPieceAt(new Coordinate(j, i));
if (moves.contains(currentCell)) {
string.append(Colors.YELLOW_BACKGROUND);
} else {
@@ -264,10 +251,6 @@ public class Console implements GameListener {
System.out.println(string);
}
@Override
public void onMove(Move move) {
}
@Override
public void onMoveNotAllowed(Move move) {
System.out.println("Move not allowed.");
@@ -279,9 +262,7 @@ public class Console implements GameListener {
}
private boolean onAskedCastling() {
GetAllowedCastlingsCommand cmd = new GetAllowedCastlingsCommand();
sendCommand(cmd);
return switch (cmd.getCastlingResult()) {
return switch (getAllowedCastlings()) {
case Small -> onSmallCastling();
case Big -> onBigCastling();
case Both -> onBothCastling();
@@ -328,4 +309,10 @@ public class Console implements GameListener {
this.captureInput = captureInput;
}
@Override
public CommandExecutor getCommandExecutor() {
return this.commandExecutor;
}
}

View File

@@ -1,128 +0,0 @@
package chess.view.render;
import org.joml.Matrix4f;
import org.joml.Vector3f;
public class Camera {
public static final float fov = 70.0f;
// should be changed to match screen
public static final float aspect = 1.0f;
public static final float zNear = 0.01f;
public static final float zFar = 100.0f;
private Vector3f pos;
private float yaw = 0.0f;
private float pitch = 0.0f;
public Camera() {
this.pos = new Vector3f(0, 2.0f, 0);
setRotation(0.0f, -3.14150f / 2.0f);
}
public void move(float x, float y) {
this.pos.x += x;
this.pos.y += y;
}
public void rotate(float yaw, float pitch) {
this.yaw += yaw;
this.pitch += pitch;
}
public Vector3f getPos() {
return pos;
}
public float getYaw() {
return yaw;
}
public float getPitch() {
return pitch;
}
public float getFov() {
return fov;
}
public void setX(float x) {
this.pos.x = x;
}
public void setY(float y) {
this.pos.y = y;
}
public void setZ(float z) {
this.pos.z = z;
}
public void setYaw(float yaw) {
this.yaw = yaw;
}
public void setPitch(float pitch) {
this.pitch = pitch;
}
public void reset() {
resetPosition();
resetRotation();
}
public void resetPosition() {
pos = new Vector3f(0.0f, 0.0f, 0.0f);
}
public void resetRotation() {
yaw = 0.0f;
pitch = 0.0f;
}
public void moveForward(float distance) {
pos.x += distance * (float) Math.cos(yaw);
pos.y += distance * (float) Math.sin(yaw);
}
public void moveRight(float distance) {
pos.x += distance * (float) Math.cos(yaw);
pos.y += distance * (float) Math.sin(yaw);
}
public void moveUp(float distance) {
pos.z += distance;
}
public void moveDown(float distance) {
pos.z -= distance;
}
public void addYaw(float angle) {
yaw += angle;
}
public void addPitch(float angle) {
pitch += angle;
}
public void setPosition(Vector3f pos) {
this.pos = pos;
}
public void setRotation(float yaw, float pitch) {
this.yaw = yaw;
this.pitch = pitch;
}
public Matrix4f getMatrix() {
Vector3f forward = new Vector3f(
(float) (Math.cos(yaw) * Math.cos(pitch)),
(float) (Math.sin(pitch)),
(float) (Math.sin(yaw) * Math.cos(pitch)));
return new Matrix4f()
.perspective((float) (Math.toRadians(fov)), aspect, zNear, zFar)
.lookAt(pos, forward, new Vector3f(0.0f, 1.0f, 0.0f));
}
}

View File

@@ -1,157 +0,0 @@
package chess.view.render;
import org.joml.Vector3f;
import org.lwjgl.opengl.*;
import chess.model.Coordinate;
import chess.view.render.shader.BoardShader;
import static org.lwjgl.opengl.GL30.*;
public class Renderer {
private BoardShader shader;
private VertexArray vao;
private static int BOARD_WIDTH = 8;
private static int BOARD_HEIGHT = 8;
private static int BOARD_SIZE = BOARD_WIDTH * BOARD_HEIGHT;
private static int SQUARE_VERTEX_COUNT = 4;
public Renderer() {
this.shader = new BoardShader();
}
public void Init() {
shader.LoadShader();
InitBoard();
}
private float[] GetBoardPositions() {
float[] positions = new float[BOARD_SIZE * SQUARE_VERTEX_COUNT * 3];
for (int i = 0; i < BOARD_WIDTH; i++) {
for (int j = 0; j < BOARD_HEIGHT; j++) {
float x = i / (float) BOARD_WIDTH;
float dx = (i + 1) / (float) BOARD_WIDTH;
float z = j / (float) BOARD_HEIGHT;
float dz = (j + 1) / (float) BOARD_HEIGHT;
float trueX = 2 * x - 1;
float trueZ = 2 * z - 1;
float trueDX = 2 * dx - 1;
float trueDZ = 2 * dz - 1;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3] = trueX;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 1] = 0.0f;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 2] = trueZ;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 3] = trueDX;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 4] = 0.0f;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 5] = trueZ;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 6] = trueX;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 7] = 0.0f;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 8] = trueDZ;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 9] = trueDX;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 10] = 0.0f;
positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 11] = trueDZ;
}
}
return positions;
}
private Coordinate GetCellFromColor(Vector3f color) {
int offset = 1;
if (color.x > 0.5) {
color = new Vector3f(1.0f, 1.0f, 1.0f).sub(color);
offset = 0;
}
int r = (int) (color.x * 255.0f);
int g = (int) (color.y * 255.0f);
int b = (int) (color.z * 255.0f);
int index = (r + g + b) * 2 + offset;
return Coordinate.fromIndex(index);
}
private Vector3f GetCellColor(int x, int y) {
float index = (y * BOARD_WIDTH + x) / 2.0f;
float r = (int) (index / 3) / 255.0f;
float g = (int) ((index + 1) / 3) / 255.0f;
float b = (int) ((index + 2) / 3) / 255.0f;
if ((x + y) % 2 != 0) {
System.out.println(GetCellFromColor(new Vector3f(1.0f - r - 1.0f / 255.0f, 1.0f - g - 1.0f / 255.0f, 1.0f - b - 1.0f / 255.0f)));
return new Vector3f(1.0f - r - 1.0f / 255.0f, 1.0f - g - 1.0f / 255.0f, 1.0f - b - 1.0f / 255.0f);
} else {
System.out.println(GetCellFromColor(new Vector3f(r, g, b)));
return new Vector3f(r, g, b);
}
}
private float[] GetBoardColors() {
float[] colors = new float[BOARD_SIZE * SQUARE_VERTEX_COUNT * 3];
for (int j = 0; j < BOARD_HEIGHT; j++) {
for (int i = 0; i < BOARD_WIDTH; i++) {
Vector3f color = GetCellColor(i, j);
int squareIndex = i * BOARD_WIDTH + j;
for (int k = 0; k < SQUARE_VERTEX_COUNT; k++) {
colors[squareIndex * SQUARE_VERTEX_COUNT * 3 + k * 3] = color.x;
colors[squareIndex * SQUARE_VERTEX_COUNT * 3 + k * 3 + 1] = color.y;
colors[squareIndex * SQUARE_VERTEX_COUNT * 3 + k * 3 + 2] = color.z;
}
}
}
return colors;
}
private int[] GetBoardIndicies() {
int[] indices = new int[BOARD_SIZE * 6];
for (int i = 0; i < BOARD_SIZE; i++) {
indices[i * 6] = i * 4;
indices[i * 6 + 1] = i * 4 + 1;
indices[i * 6 + 2] = i * 4 + 2;
indices[i * 6 + 3] = i * 4 + 1;
indices[i * 6 + 4] = i * 4 + 2;
indices[i * 6 + 5] = i * 4 + 3;
}
return indices;
}
private void InitBoard() {
ElementBuffer eBuffer = new ElementBuffer(GetBoardIndicies());
this.vao = new VertexArray(eBuffer);
VertexBuffer positionBuffer = new VertexBuffer(GetBoardPositions(), 3);
positionBuffer.AddVertexAttribPointer(0, 3, 0);
VertexBuffer colorBuffer = new VertexBuffer(GetBoardColors(), 3);
colorBuffer.AddVertexAttribPointer(1, 3, 0);
this.vao.Bind();
this.vao.BindVertexBuffer(positionBuffer);
this.vao.BindVertexBuffer(colorBuffer);
this.vao.Unbind();
}
public Coordinate GetSelectedCell() {
float pixels[] = new float[3];
GL30.glReadPixels(500, 500, 1, 1, GL_RGB, GL_FLOAT, pixels);
return GetCellFromColor(new Vector3f(pixels[0], pixels[1], pixels[2]));
}
public void Render(Camera cam) {
this.shader.Start();
this.shader.SetCamMatrix(cam.getMatrix());
RenderVao(vao);
}
public void RenderVao(VertexArray vertexArray) {
this.shader.Start();
vertexArray.Bind();
GL30.glDrawElements(GL30.GL_TRIANGLES, vertexArray.GetVertexCount(), GL_UNSIGNED_INT, 0);
vertexArray.Unbind();
}
}

View File

@@ -1,224 +0,0 @@
package chess.view.render;
import org.lwjgl.*;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.*;
import org.lwjgl.system.*;
import chess.controller.CommandExecutor;
import chess.controller.event.GameListener;
import chess.model.Color;
import chess.model.Coordinate;
import chess.model.Move;
import java.nio.*;
import static org.lwjgl.glfw.Callbacks.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.system.MemoryStack.*;
import static org.lwjgl.system.MemoryUtil.*;
public class Window implements GameListener{
// The window handle
private long window;
private final CommandExecutor commandExecutor;
private Renderer renderer;
private Camera cam;
public Window(CommandExecutor commandExecutor) {
this.renderer = new Renderer();
this.cam = new Camera();
this.commandExecutor = new CommandExecutor();
}
public void run() {
System.out.println("LWJGL " + Version.getVersion() + "!");
init();
loop();
// Free the window callbacks and destroy the window
glfwFreeCallbacks(window);
glfwDestroyWindow(window);
// Terminate GLFW and free the error callback
glfwTerminate();
glfwSetErrorCallback(null).free();
}
private void init() {
// Setup an error callback. The default implementation
// will print the error message in System.err.
GLFWErrorCallback.createPrint(System.err).set();
// Initialize GLFW. Most GLFW functions will not work before doing this.
if (!glfwInit())
throw new IllegalStateException("Unable to initialize GLFW");
// Configure GLFW
glfwDefaultWindowHints(); // optional, the current window hints are already the default
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // the window will stay hidden after creation
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // the window will be resizable
// Create the window
window = glfwCreateWindow(1000, 1000, "3DChess", NULL, NULL);
if (window == NULL)
throw new RuntimeException("Failed to create the GLFW window");
// Get the thread stack and push a new frame
try (MemoryStack stack = stackPush()) {
IntBuffer pWidth = stack.mallocInt(1); // int*
IntBuffer pHeight = stack.mallocInt(1); // int*
// Get the window size passed to glfwCreateWindow
glfwGetWindowSize(window, pWidth, pHeight);
// Get the resolution of the primary monitor
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
// Center the window
glfwSetWindowPos(
window,
(vidmode.width() - pWidth.get(0)) / 2,
(vidmode.height() - pHeight.get(0)) / 2);
} // the stack frame is popped automatically
// Make the OpenGL context current
glfwMakeContextCurrent(window);
// Enable v-sync
glfwSwapInterval(1);
// Make the window visible
glfwShowWindow(window);
}
private void render() {
cam.rotate(0.01f, 0.01f);
renderer.Render(cam);
}
private void loop() {
// This line is critical for LWJGL's interoperation with GLFW's
// OpenGL context, or any context that is managed externally.
// LWJGL detects the context that is current in the current thread,
// creates the GLCapabilities instance and makes the OpenGL
// bindings available for use.
GL.createCapabilities();
renderer.Init();
// Set the clear color
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
// Run the rendering loop until the user has attempted to close
// the window or has pressed the ESCAPE key.
while (!glfwWindowShouldClose(window)) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer
render();
System.out.println(this.renderer.GetSelectedCell());
glfwSwapBuffers(window); // swap the color buffers
// Poll for window events. The key callback above will only be
// invoked during this call.
glfwPollEvents();
try (MemoryStack stack = stackPush()) {
IntBuffer pWidth = stack.mallocInt(1); // int*
IntBuffer pHeight = stack.mallocInt(1); // int*
// Get the window size passed to glfwCreateWindow
glfwGetWindowSize(window, pWidth, pHeight);
glViewport(0, 0, pWidth.get(), pHeight.get());
} // the stack frame is popped automatically
}
}
@Override
public void onBoardUpdate() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onBoardUpdate'");
}
@Override
public void onDraw() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onDraw'");
}
@Override
public void onGameEnd() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onGameEnd'");
}
@Override
public void onGameStart() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onGameStart'");
}
@Override
public void onKingInCheck() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onKingInCheck'");
}
@Override
public void onKingInMat() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onKingInMat'");
}
@Override
public void onMove(Move move) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onMove'");
}
@Override
public void onMoveNotAllowed(Move move) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onMoveNotAllowed'");
}
@Override
public void onPatSituation() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onPatSituation'");
}
@Override
public void onPlayerTurn(Color color) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onPlayerTurn'");
}
@Override
public void onPromotePawn(Coordinate pieceCoords) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onPromotePawn'");
}
@Override
public void onSurrender(Color coward) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onSurrender'");
}
@Override
public void onWin(Color winner) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'onWin'");
}
}

View File

@@ -1,53 +0,0 @@
package chess.view.render.shader;
import org.joml.Matrix4f;
public class BoardShader extends ShaderProgram {
private static String vertexShader = """
#version 330
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
uniform mat4 camMatrix;
flat out vec3 pass_color;
void main(void){
gl_Position = camMatrix * vec4(position, 1.0);
pass_color = color;
}
""";
private static String fragmentShader = """
#version 330
flat in vec3 pass_color;
out vec4 out_color;
void main(void){
out_color = vec4(pass_color, 1.0);
}
""";
private int location_CamMatrix = 0;
public BoardShader() {
}
public void LoadShader() {
super.LoadProgram(vertexShader, fragmentShader);
}
@Override
protected void GetAllUniformLocation() {
location_CamMatrix = GetUniformLocation("camMatrix");
}
public void SetCamMatrix(Matrix4f mat) {
LoadMat4(location_CamMatrix, mat);
}
}

View File

@@ -17,7 +17,7 @@ import chess.model.pieces.Knight;
import chess.model.pieces.Pawn;
import chess.model.pieces.Queen;
import chess.model.pieces.Rook;
import chess.view.AssetManager;
import common.AssetManager;
public class PieceIcon implements PieceVisitor<String> {

View File

@@ -17,24 +17,14 @@ import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import chess.controller.Command;
import chess.controller.Command.CommandResult;
import chess.controller.CommandExecutor;
import chess.controller.commands.CastlingCommand;
import chess.controller.commands.GetAllowedCastlingsCommand;
import chess.controller.commands.GetAllowedMovesPieceCommand;
import chess.controller.commands.GetPieceAtCommand;
import chess.controller.commands.MoveCommand;
import chess.controller.commands.PromoteCommand;
import chess.controller.CommandSender;
import chess.controller.commands.PromoteCommand.PromoteType;
import chess.controller.commands.UndoCommand;
import chess.controller.commands.GetAllowedCastlingsCommand.CastlingResult;
import chess.controller.event.GameListener;
import chess.model.Coordinate;
import chess.model.Move;
import chess.model.Piece;
public class Window extends JFrame implements GameListener {
public class Window extends JFrame implements GameListener, CommandSender {
private final CommandExecutor commandExecutor;
@@ -65,25 +55,22 @@ public class Window extends JFrame implements GameListener {
});
}
private CommandResult sendCommand(Command command) {
return this.commandExecutor.executeCommand(command);
}
private Color getCellColor(int x, int y) {
return ((x + y) % 2 == 1) ? Color.DARK_GRAY : Color.LIGHT_GRAY;
}
@SuppressWarnings("unused")
private void buildButtons(JPanel bottom) {
castlingButton.addActionListener((event) -> {
sendCommand(new CastlingCommand(false));
sendCastling();
});
bigCastlingButton.addActionListener((event) -> {
sendCommand(new CastlingCommand(true));
sendBigCastling();
});
undoButton.addActionListener((event) -> {
sendCommand(new UndoCommand());
sendUndo();
});
bottom.add(castlingButton);
@@ -127,23 +114,13 @@ public class Window extends JFrame implements GameListener {
updateBoard();
}
private boolean isCellEmpty(int x, int y) {
return pieceAt(x, y) == null;
}
private Piece pieceAt(int x, int y) {
GetPieceAtCommand command = new GetPieceAtCommand(new Coordinate(x, y));
sendCommand(command);
return command.getPiece();
}
private void updateBoard() {
PieceIcon pieceIcon = new PieceIcon();
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
JLabel cell = this.cells[x][y];
try {
cell.setIcon(pieceIcon.getIcon(pieceAt(x, y)));
cell.setIcon(pieceIcon.getIcon(getPieceAt(x, y)));
} catch (IOException e) {
e.printStackTrace();
}
@@ -152,11 +129,7 @@ public class Window extends JFrame implements GameListener {
}
private boolean previewMoves(int x, int y) {
GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(new Coordinate(x, y));
if (sendCommand(movesCommand) == CommandResult.NotAllowed)
return false;
List<Coordinate> allowedMoves = movesCommand.getDestinations();
List<Coordinate> allowedMoves = getPieceAllowedMoves(new Coordinate(x, y));
if (allowedMoves.isEmpty())
return false;
@@ -198,22 +171,18 @@ public class Window extends JFrame implements GameListener {
}
if (!this.lastClick.equals(new Coordinate(x, y))) {
Move move = new Move(lastClick, new Coordinate(x, y));
sendCommand(new MoveCommand(move));
sendMove(move);
}
this.lastClick = null;
}
private void updateButtons() {
GetAllowedCastlingsCommand cmd = new GetAllowedCastlingsCommand();
sendCommand(cmd);
this.castlingButton.setEnabled(
cmd.getCastlingResult() == CastlingResult.Small || cmd.getCastlingResult() == CastlingResult.Both);
this.bigCastlingButton.setEnabled(
cmd.getCastlingResult() == CastlingResult.Big || cmd.getCastlingResult() == CastlingResult.Both);
this.castlingButton.setEnabled(canDoCastling());
this.bigCastlingButton.setEnabled(canDoBigCastling());
}
@Override
public void onPlayerTurn(chess.model.Color color) {
public void onPlayerTurn(chess.model.Color color, boolean undone) {
this.displayText.setText("Current turn: " + color);
updateButtons();
}
@@ -296,7 +265,7 @@ public class Window extends JFrame implements GameListener {
}
if (choosedType != null)
sendCommand(new PromoteCommand(choosedType));
sendPawnPromotion(choosedType);
});
}
@@ -306,7 +275,7 @@ public class Window extends JFrame implements GameListener {
}
@Override
public void onMove(Move move) {}
public void onMove(Move move, boolean captured) {}
@Override
public void onMoveNotAllowed(Move move) {
@@ -318,4 +287,15 @@ public class Window extends JFrame implements GameListener {
JOptionPane.showMessageDialog(this, "Same position was repeated three times. It's a draw!");
}
@Override
public void onCastling(boolean bigCastling, Move kingMove, Move rookMove) {}
@Override
public void onPawnPromoted(PromoteType promotion) {}
@Override
public CommandExecutor getCommandExecutor() {
return this.commandExecutor;
}
}

View File

@@ -1,9 +1,11 @@
package chess.view;
package common;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class AssetManager {
@@ -22,6 +24,13 @@ public class AssetManager {
return ClassLoader.getSystemResourceAsStream(name);
}
public static String getResourceAsString(String path) {
StringBuilder builder = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(AssetManager.getResource(path)));
reader.lines().forEach((line) -> builder.append(line + "\n"));
return builder.toString();
}
private static InputStream getFileInputStream(String path) {
File f = new File(path);
if (f.exists()) {

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More