mirror of
https://github.com/2OOP/pism.git
synced 2026-02-04 10:54:51 +00:00
Merge remote-tracking branch 'origin/UI' into UI
# Conflicts: # app/src/main/java/org/toop/app/App.java # app/src/main/java/org/toop/app/menu/MainMenu.java # app/src/main/java/org/toop/app/menu/game/GameMenu.java
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -47,6 +47,7 @@ shelf/
|
|||||||
*.iml
|
*.iml
|
||||||
*.ipr
|
*.ipr
|
||||||
*.iws
|
*.iws
|
||||||
|
misc.xml
|
||||||
|
|
||||||
##############################
|
##############################
|
||||||
## Eclipse
|
## Eclipse
|
||||||
@@ -102,3 +103,8 @@ newgamesver-release-V1.jar
|
|||||||
server.properties
|
server.properties
|
||||||
gameserver.log.*
|
gameserver.log.*
|
||||||
gameserver.log
|
gameserver.log
|
||||||
|
|
||||||
|
##############################
|
||||||
|
## JPackage
|
||||||
|
##############################
|
||||||
|
jpackage-input
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.toop.app;
|
package org.toop.app;
|
||||||
|
|
||||||
|
import org.toop.app.canvas.TicTacToeCanvas;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import org.toop.app.menu.MainMenu;
|
import org.toop.app.menu.MainMenu;
|
||||||
import org.toop.app.menu.Menu;
|
import org.toop.app.menu.Menu;
|
||||||
@@ -57,7 +58,6 @@ public final class App extends Application {
|
|||||||
|
|
||||||
pane = new StackPane(background, box);
|
pane = new StackPane(background, box);
|
||||||
pane.getStylesheets().add(ResourceManager.get(CssAsset.class, "quit.css").getUrl());
|
pane.getStylesheets().add(ResourceManager.get(CssAsset.class, "quit.css").getUrl());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ public final class App extends Application {
|
|||||||
final StackPane root = new StackPane(new MainMenu().getPane());
|
final StackPane root = new StackPane(new MainMenu().getPane());
|
||||||
|
|
||||||
final Scene scene = new Scene(root);
|
final Scene scene = new Scene(root);
|
||||||
scene.getStylesheets().add(((CssAsset) ResourceManager.get("app.css")).getUrl());
|
scene.getStylesheets().add(ResourceManager.get(CssAsset.class, "app.css").getUrl());
|
||||||
|
|
||||||
stage.setTitle(loc.getString("windowTitle",currentLocale));
|
stage.setTitle(loc.getString("windowTitle",currentLocale));
|
||||||
stage.setMinWidth(1080);
|
stage.setMinWidth(1080);
|
||||||
@@ -95,10 +95,16 @@ public final class App extends Application {
|
|||||||
App.width = (int)stage.getWidth();
|
App.width = (int)stage.getWidth();
|
||||||
App.height = (int)stage.getHeight();
|
App.height = (int)stage.getHeight();
|
||||||
|
|
||||||
|
App.isQuitting = false;
|
||||||
|
|
||||||
new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent();
|
new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent();
|
||||||
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(0.1)).asyncPostEvent();
|
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(0.1)).asyncPostEvent();
|
||||||
|
|
||||||
App.isQuitting = false;
|
// Todo: Temp Obviously
|
||||||
|
// Replace with game of life
|
||||||
|
final TicTacToeCanvas canvas = new TicTacToeCanvas();
|
||||||
|
root.getChildren().addLast(canvas.getCanvas());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
new EventFlow()
|
new EventFlow()
|
||||||
.listen(this::handleChangeLanguage);
|
.listen(this::handleChangeLanguage);
|
||||||
@@ -117,6 +123,8 @@ public final class App extends Application {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static void quitPopup() {
|
public static void quitPopup() {
|
||||||
isQuitting = true;
|
isQuitting = true;
|
||||||
push(new QuitMenu());
|
push(new QuitMenu());
|
||||||
|
|||||||
77
app/src/main/java/org/toop/app/canvas/GameCanvas.java
Normal file
77
app/src/main/java/org/toop/app/canvas/GameCanvas.java
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package org.toop.app.canvas;
|
||||||
|
|
||||||
|
import javafx.scene.canvas.Canvas;
|
||||||
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
|
import javafx.scene.input.MouseButton;
|
||||||
|
|
||||||
|
public abstract class GameCanvas {
|
||||||
|
protected record Cell(float x, float y, float width, float height) {}
|
||||||
|
|
||||||
|
protected final int width;
|
||||||
|
protected final int height;
|
||||||
|
|
||||||
|
protected final Canvas canvas;
|
||||||
|
protected final GraphicsContext graphics;
|
||||||
|
|
||||||
|
protected final int rows;
|
||||||
|
protected final int columns;
|
||||||
|
|
||||||
|
protected final int gapSize;
|
||||||
|
|
||||||
|
protected final Cell[] cells;
|
||||||
|
|
||||||
|
protected GameCanvas(int width, int height, int rows, int columns, int gapSize) {
|
||||||
|
final Canvas canvas = new Canvas(width, height);
|
||||||
|
final GraphicsContext graphics = canvas.getGraphicsContext2D();
|
||||||
|
|
||||||
|
final Cell[] cells = new Cell[rows * columns];
|
||||||
|
|
||||||
|
final float cellWidth = ((float)width - (rows - 1) * gapSize) / rows;
|
||||||
|
final float cellHeight = ((float)height - (columns - 1) * gapSize) / columns;
|
||||||
|
|
||||||
|
for (int y = 0; y < columns; y++) {
|
||||||
|
final float startY = y * cellHeight + y * gapSize;
|
||||||
|
|
||||||
|
for (int x = 0; x < rows; x++) {
|
||||||
|
final float startX = x * cellWidth + x * gapSize;
|
||||||
|
cells[y * rows + x] = new Cell(startX, startY, cellWidth, cellHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.setOnMouseClicked(event -> {
|
||||||
|
final MouseButton button = event.getButton();
|
||||||
|
|
||||||
|
if (button != MouseButton.PRIMARY && button != MouseButton.SECONDARY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int column = (int)((event.getX() / width) * rows);
|
||||||
|
final int row = (int)((event.getY() / height) * columns);
|
||||||
|
|
||||||
|
event.consume();
|
||||||
|
onCellClicked(row * rows + column, button == MouseButton.PRIMARY);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
|
||||||
|
this.canvas = canvas;
|
||||||
|
this.graphics = graphics;
|
||||||
|
|
||||||
|
this.rows = rows;
|
||||||
|
this.columns = columns;
|
||||||
|
|
||||||
|
this.gapSize = gapSize;
|
||||||
|
|
||||||
|
this.cells = cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void clearCell(int cell) {
|
||||||
|
assert cell >= 0 && cell < cells.length;
|
||||||
|
graphics.clearRect(cells[cell].x(), cells[cell].y(), cells[cell].width(), cells[cell].height());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void onCellClicked(int cell, boolean primary);
|
||||||
|
|
||||||
|
public Canvas getCanvas() { return canvas; }
|
||||||
|
}
|
||||||
81
app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java
Normal file
81
app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package org.toop.app.canvas;
|
||||||
|
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import org.toop.app.App;
|
||||||
|
import org.toop.game.Game;
|
||||||
|
import org.toop.game.tictactoe.TicTacToe;
|
||||||
|
|
||||||
|
public class TicTacToeCanvas extends GameCanvas {
|
||||||
|
private final TicTacToe game;
|
||||||
|
|
||||||
|
public TicTacToeCanvas() {
|
||||||
|
super(App.getHeight(), App.getHeight(), 3, 3, 10);
|
||||||
|
game = new TicTacToe();
|
||||||
|
|
||||||
|
graphics.setFill(Color.CYAN);
|
||||||
|
|
||||||
|
for (int x = 1; x < rows; x++) {
|
||||||
|
graphics.fillRect(cells[x].x() - gapSize, 0, gapSize, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int y = 1; y < columns; y++) {
|
||||||
|
graphics.fillRect(0, cells[y * rows].y() - gapSize, width, gapSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void placeX(int cell) {
|
||||||
|
graphics.setStroke(Color.ORANGERED);
|
||||||
|
graphics.setLineWidth(gapSize);
|
||||||
|
|
||||||
|
final float x = cells[cell].x() + gapSize;
|
||||||
|
final float y = cells[cell].y() + gapSize;
|
||||||
|
|
||||||
|
final float width = cells[cell].width() - gapSize * 2;
|
||||||
|
final float height = cells[cell].height() - gapSize * 2;
|
||||||
|
|
||||||
|
graphics.strokeLine(x, y, x + width, y + height);
|
||||||
|
graphics.strokeLine(x + width, y, x, y + height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void placeO(int cell) {
|
||||||
|
graphics.setStroke(Color.DEEPSKYBLUE);
|
||||||
|
graphics.setLineWidth(gapSize);
|
||||||
|
|
||||||
|
final float x = cells[cell].x() + gapSize;
|
||||||
|
final float y = cells[cell].y() + gapSize;
|
||||||
|
|
||||||
|
final float width = cells[cell].width() - gapSize * 2;
|
||||||
|
final float height = cells[cell].height() - gapSize * 2;
|
||||||
|
|
||||||
|
graphics.strokeOval(x, y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCellClicked(int cell, boolean primary) {
|
||||||
|
for (final Game.Move move : game.getLegalMoves()) {
|
||||||
|
if (move.position() == cell) {
|
||||||
|
if (move.value() == 'X') {
|
||||||
|
placeX(cell);
|
||||||
|
} else {
|
||||||
|
placeO(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Game.State state = game.play(move);
|
||||||
|
|
||||||
|
if (state == Game.State.WIN) {
|
||||||
|
graphics.setFill(Color.GREEN);
|
||||||
|
graphics.fillRect(cells[4].x(), cells[4].y(), cells[4].width(), cells[4].height());
|
||||||
|
|
||||||
|
for (int i = 0; i < game.board.length; i++) {
|
||||||
|
if (game.board[i] != move.value()) {
|
||||||
|
clearCell(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (state == Game.State.DRAW) {
|
||||||
|
graphics.setFill(Color.DARKORANGE);
|
||||||
|
graphics.fillRect(cells[4].x(), cells[4].y(), cells[4].width(), cells[4].height());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ public final class MainMenu extends Menu {
|
|||||||
public MainMenu() {
|
public MainMenu() {
|
||||||
final Region background = createBackground();
|
final Region background = createBackground();
|
||||||
|
|
||||||
tictactoe = createButton(loc.getString("mainMenuSelectTicTacToe",currentLocale), () -> { App.activate(new TicTacToeMenu(new TicTacToe("player 1", true, "player 2", true))); });
|
tictactoe = createButton(loc.getString("mainMenuSelectTicTacToe",currentLocale), () -> { App.activate(new GameSelectMenu(GameType.TICTACTOE)); });
|
||||||
reversi = createButton(loc.getString("mainMenuSelectReversi",currentLocale), () -> { App.activate(new GameSelectMenu(GameType.REVERSI)); });
|
reversi = createButton(loc.getString("mainMenuSelectReversi",currentLocale), () -> { App.activate(new GameSelectMenu(GameType.REVERSI)); });
|
||||||
|
|
||||||
final VBox gamesBox = new VBox(10, tictactoe, reversi);
|
final VBox gamesBox = new VBox(10, tictactoe, reversi);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.toop.app.menu;
|
package org.toop.app.menu;
|
||||||
|
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.layout.Pane;
|
import javafx.scene.layout.Pane;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
|||||||
@@ -1,133 +0,0 @@
|
|||||||
package org.toop.app.menu.game;
|
|
||||||
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import org.toop.game.Game;
|
|
||||||
import org.toop.game.Player;
|
|
||||||
import org.toop.game.tictactoe.TicTacToe;
|
|
||||||
import org.toop.game.tictactoe.TicTacToeAI;
|
|
||||||
|
|
||||||
import javax.management.RuntimeErrorException;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
|
|
||||||
public final class TicTacToeMenu extends GameMenu {
|
|
||||||
private final TicTacToe game;
|
|
||||||
private final TicTacToeAI ai;
|
|
||||||
|
|
||||||
// private final ExecutorService executor = Executors.newFixedThreadPool(1);
|
|
||||||
private final BlockingQueue<Game.Move> moveQueue = new LinkedBlockingQueue<>();
|
|
||||||
|
|
||||||
public TicTacToeMenu(TicTacToe game) {
|
|
||||||
super(3, 3, 10);
|
|
||||||
|
|
||||||
graphics.setFill(Color.CYAN);
|
|
||||||
|
|
||||||
for (int x = 1; x < rows; x++) {
|
|
||||||
graphics.fillRect(cells[x].x - gapSize, 0, gapSize, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int y = 1; y < columns; y++) {
|
|
||||||
graphics.fillRect(0, cells[y * rows].y - gapSize, size, gapSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.game = game;
|
|
||||||
ai = new TicTacToeAI();
|
|
||||||
|
|
||||||
canvas.setOnMouseClicked(event -> {
|
|
||||||
for (int i = 0; i < cells.length; i++) {
|
|
||||||
if (cells[i].check((float) event.getX(), (float) event.getY())) {
|
|
||||||
final Game.Move move = new Game.Move(i, game.getCurrentPlayer().values()[0]);
|
|
||||||
play(move);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
new Thread(this::gameThread).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void play(Game.Move move) {
|
|
||||||
final Game.Move[] legalMoves = game.getLegalMoves();
|
|
||||||
|
|
||||||
boolean isLegal = false;
|
|
||||||
|
|
||||||
for (final Game.Move legalMove : legalMoves) {
|
|
||||||
if (legalMove.position() == move.position() && legalMove.value() == move.value()) {
|
|
||||||
isLegal = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isLegal) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try { moveQueue.put(move); }
|
|
||||||
catch (InterruptedException _) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void placeX(int cell) {
|
|
||||||
graphics.setStroke(Color.ORANGERED);
|
|
||||||
graphics.setLineWidth(gapSize);
|
|
||||||
|
|
||||||
final float x = cells[cell].x + gapSize;
|
|
||||||
final float y = cells[cell].y + gapSize;
|
|
||||||
|
|
||||||
final float width = cells[cell].width - gapSize * 2;
|
|
||||||
final float height = cells[cell].height - gapSize * 2;
|
|
||||||
|
|
||||||
graphics.strokeLine(x, y, x + width, y + height);
|
|
||||||
graphics.strokeLine(x + width, y, x, y + height);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void placeO(int cell) {
|
|
||||||
graphics.setStroke(Color.DEEPSKYBLUE);
|
|
||||||
graphics.setLineWidth(gapSize);
|
|
||||||
|
|
||||||
final float x = cells[cell].x + gapSize;
|
|
||||||
final float y = cells[cell].y + gapSize;
|
|
||||||
|
|
||||||
final float width = cells[cell].width - gapSize * 2;
|
|
||||||
final float height = cells[cell].height - gapSize * 2;
|
|
||||||
|
|
||||||
graphics.strokeOval(x, y, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void gameThread() {
|
|
||||||
boolean running = true;
|
|
||||||
|
|
||||||
while(running) {
|
|
||||||
final Player currentPlayer = game.getCurrentPlayer();
|
|
||||||
|
|
||||||
try {
|
|
||||||
Game.Move move;
|
|
||||||
|
|
||||||
if (!currentPlayer.isAI()) {
|
|
||||||
try { move = moveQueue.take(); }
|
|
||||||
catch (InterruptedException _) { return; }
|
|
||||||
} else {
|
|
||||||
move = ai.findBestMove(game, 9);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert move != null;
|
|
||||||
final Game.State state = game.play(move);
|
|
||||||
|
|
||||||
if (move.value() == 'X') {
|
|
||||||
placeX(move.position());
|
|
||||||
} else {
|
|
||||||
placeO(move.position());
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case NORMAL: break;
|
|
||||||
|
|
||||||
case DRAW:
|
|
||||||
case LOSE:
|
|
||||||
case WIN:
|
|
||||||
running = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,6 +11,7 @@ public class AppContext {
|
|||||||
currentLocale = locale;
|
currentLocale = locale;
|
||||||
new EventFlow().addPostEvent(new LocalizationEvents.LanguageHasChanged(locale.getLanguage())).asyncPostEvent();
|
new EventFlow().addPostEvent(new LocalizationEvents.LanguageHasChanged(locale.getLanguage())).asyncPostEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Locale getLocale() {
|
public static Locale getLocale() {
|
||||||
return currentLocale;
|
return currentLocale;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
windowTitle=ISY Games Selector
|
windowTitle=ISY Games Selector
|
||||||
|
|
||||||
# Main Menu buttons
|
# Main Menu buttons
|
||||||
mainMenuSelectTicTacToe=Tic Tac Toe
|
mainMenuSelectTicTacToe=Tic Tac Toe\u5426
|
||||||
mainMenuSelectReversi=Reversi
|
mainMenuSelectReversi=Reversi\u5426
|
||||||
mainMenuSelectSudoku=Sudoku
|
mainMenuSelectSudoku=Sudoku
|
||||||
mainMenuSelectBattleship=Battleship
|
mainMenuSelectBattleship=Battleship
|
||||||
mainMenuSelectOther=Other
|
mainMenuSelectOther=Other
|
||||||
|
|||||||
@@ -4,52 +4,31 @@ import java.util.Arrays;
|
|||||||
|
|
||||||
public abstract class Game {
|
public abstract class Game {
|
||||||
public enum State {
|
public enum State {
|
||||||
NORMAL, DRAW, LOSE, WIN,
|
NORMAL, DRAW, WIN,
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Move(int position, char value) {}
|
public record Move(int position, char value) {}
|
||||||
|
|
||||||
public static final char EMPTY = (char)0;
|
public static final char EMPTY = (char)0;
|
||||||
|
|
||||||
protected final int rowSize;
|
public final int rowSize;
|
||||||
protected final int columnSize;
|
public final int columnSize;
|
||||||
protected final char[] board;
|
public final char[] board;
|
||||||
|
|
||||||
protected final Player[] players;
|
protected Game(int rowSize, int columnSize) {
|
||||||
protected int currentPlayer;
|
|
||||||
|
|
||||||
protected Game(int rowSize, int columnSize, Player... players) {
|
|
||||||
assert rowSize > 0 && columnSize > 0;
|
assert rowSize > 0 && columnSize > 0;
|
||||||
assert players.length >= 1;
|
|
||||||
|
|
||||||
this.rowSize = rowSize;
|
this.rowSize = rowSize;
|
||||||
this.columnSize = columnSize;
|
this.columnSize = columnSize;
|
||||||
|
|
||||||
board = new char[rowSize * columnSize];
|
board = new char[rowSize * columnSize];
|
||||||
Arrays.fill(board, EMPTY);
|
Arrays.fill(board, EMPTY);
|
||||||
|
|
||||||
this.players = players;
|
|
||||||
currentPlayer = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Game(Game other) {
|
protected Game(Game other) {
|
||||||
rowSize = other.rowSize;
|
rowSize = other.rowSize;
|
||||||
columnSize = other.columnSize;
|
columnSize = other.columnSize;
|
||||||
board = Arrays.copyOf(other.board, other.board.length);
|
board = Arrays.copyOf(other.board, other.board.length);
|
||||||
|
|
||||||
players = Arrays.copyOf(other.players, other.players.length);
|
|
||||||
currentPlayer = other.currentPlayer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRowSize() { return rowSize; }
|
|
||||||
public int getColumnSize() { return columnSize; }
|
|
||||||
public char[] getBoard() { return board; }
|
|
||||||
|
|
||||||
public Player[] getPlayers() { return players; }
|
|
||||||
public Player getCurrentPlayer() { return players[currentPlayer]; }
|
|
||||||
|
|
||||||
protected void nextPlayer() {
|
|
||||||
currentPlayer = (currentPlayer + 1) % players.length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Move[] getLegalMoves();
|
public abstract Move[] getLegalMoves();
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
package org.toop.game;
|
|
||||||
|
|
||||||
public record Player(String name, boolean isAI, char... values) {}
|
|
||||||
26
game/src/main/java/org/toop/game/TurnBasedGame.java
Normal file
26
game/src/main/java/org/toop/game/TurnBasedGame.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package org.toop.game;
|
||||||
|
|
||||||
|
public abstract class TurnBasedGame extends Game {
|
||||||
|
public final int turns;
|
||||||
|
|
||||||
|
protected int currentTurn;
|
||||||
|
|
||||||
|
protected TurnBasedGame(int rowSize, int columnSize, int turns) {
|
||||||
|
assert turns >= 2;
|
||||||
|
|
||||||
|
super(rowSize, columnSize);
|
||||||
|
this.turns = turns;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TurnBasedGame(TurnBasedGame other) {
|
||||||
|
super(other);
|
||||||
|
turns = other.turns;
|
||||||
|
currentTurn = other.currentTurn;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void nextTurn() {
|
||||||
|
currentTurn = (currentTurn + 1) % turns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentTurn() { return currentTurn; }
|
||||||
|
}
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
package org.toop.game.tictactoe;
|
package org.toop.game.tictactoe;
|
||||||
|
|
||||||
import org.toop.game.Game;
|
import org.toop.game.TurnBasedGame;
|
||||||
import org.toop.game.Player;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public final class TicTacToe extends Game {
|
public final class TicTacToe extends TurnBasedGame {
|
||||||
private int movesLeft;
|
private int movesLeft;
|
||||||
|
|
||||||
public TicTacToe(String player1, boolean isPlayer1AI, String player2, boolean isPlayer2AI) {
|
public TicTacToe() {
|
||||||
super(3, 3, new Player(player1, isPlayer1AI, 'X'), new Player(player2, isPlayer2AI, 'O'));
|
super(3, 3, 2);
|
||||||
movesLeft = board.length;
|
movesLeft = board.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,11 +19,12 @@ public final class TicTacToe extends Game {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Move[] getLegalMoves() {
|
public Move[] getLegalMoves() {
|
||||||
final ArrayList<Move> legalMoves = new ArrayList<>();
|
final ArrayList<Move> legalMoves = new ArrayList<>();
|
||||||
|
final char currentValue = getCurrentValue();
|
||||||
|
|
||||||
for (int i = 0; i < board.length; i++) {
|
for (int i = 0; i < board.length; i++) {
|
||||||
if (board[i] == EMPTY) {
|
if (board[i] == EMPTY) {
|
||||||
legalMoves.add(new Move(i, getCurrentPlayer().values()[0]));
|
legalMoves.add(new Move(i, currentValue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ public final class TicTacToe extends Game {
|
|||||||
public State play(Move move) {
|
public State play(Move move) {
|
||||||
assert move != null;
|
assert move != null;
|
||||||
assert move.position() >= 0 && move.position() < board.length;
|
assert move.position() >= 0 && move.position() < board.length;
|
||||||
assert move.value() == getCurrentPlayer().values()[0];
|
assert move.value() == getCurrentValue();
|
||||||
|
|
||||||
board[move.position()] = move.value();
|
board[move.position()] = move.value();
|
||||||
movesLeft--;
|
movesLeft--;
|
||||||
@@ -44,12 +44,12 @@ public final class TicTacToe extends Game {
|
|||||||
return State.WIN;
|
return State.WIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextPlayer();
|
nextTurn();
|
||||||
|
|
||||||
if (movesLeft <= 2) {
|
if (movesLeft <= 2) {
|
||||||
if (checkDraw(new TicTacToe(this))) {
|
if (movesLeft <= 0 || checkForEarlyDraw(this)) {
|
||||||
return State.DRAW;
|
return State.DRAW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return State.NORMAL;
|
return State.NORMAL;
|
||||||
@@ -60,18 +60,14 @@ public final class TicTacToe extends Game {
|
|||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
final int index = i * 3;
|
final int index = i * 3;
|
||||||
|
|
||||||
if (board[index] != EMPTY
|
if (board[index] != EMPTY && board[index] == board[index + 1] && board[index] == board[index + 2]) {
|
||||||
&& board[index] == board[index + 1]
|
|
||||||
&& board[index] == board[index + 2]) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vertical
|
// Vertical
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
if (board[i] != EMPTY
|
if (board[i] != EMPTY && board[i] == board[i + 3] && board[i] == board[i + 6]) {
|
||||||
&& board[i] == board[i + 3]
|
|
||||||
&& board[i] == board[i + 6]) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,25 +81,19 @@ public final class TicTacToe extends Game {
|
|||||||
return board[2] != EMPTY && board[2] == board[4] && board[2] == board[6];
|
return board[2] != EMPTY && board[2] == board[4] && board[2] == board[6];
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean checkDraw(TicTacToe game) {
|
private boolean checkForEarlyDraw(TicTacToe game) {
|
||||||
if (game.checkForWin()) {
|
for (final Move move : game.getLegalMoves()) {
|
||||||
return false;
|
final TicTacToe copy = new TicTacToe(game);
|
||||||
}
|
|
||||||
if (game.movesLeft == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// try every move on a legal copy
|
|
||||||
for (Move move : game.getLegalMoves()) {
|
|
||||||
TicTacToe copy = new TicTacToe(game);
|
|
||||||
State result = copy.play(move);
|
|
||||||
|
|
||||||
if (result == State.WIN) {
|
if (copy.play(move) == State.WIN || !checkForEarlyDraw(copy)) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!checkDraw(copy)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private char getCurrentValue() {
|
||||||
|
return currentTurn == 0? 'X' : 'O';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
//package org.toop.game;
|
|
||||||
//
|
|
||||||
//import org.junit.jupiter.api.BeforeEach;
|
|
||||||
//import org.junit.jupiter.api.Test;
|
|
||||||
//
|
|
||||||
//import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
//
|
|
||||||
//class PlayerTest {
|
|
||||||
// private Player playerA;
|
|
||||||
// private Player playerB;
|
|
||||||
// private Player playerC;
|
|
||||||
//
|
|
||||||
// @BeforeEach
|
|
||||||
// void setup() {
|
|
||||||
// playerA = new Player("test A", 'x', 'Z', 'i');
|
|
||||||
// playerB = new Player("test B", 'O', (char)12, (char)-34, 's');
|
|
||||||
// playerC = new Player("test C", (char)9, '9', (char)-9, '0', 'X', 'O');
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// void testNameGetter_returnsTrueForValidName() {
|
|
||||||
// assertEquals("test A", playerA.name());
|
|
||||||
// assertEquals("test B", playerB.name());
|
|
||||||
// assertEquals("test C", playerC.name());
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// void testValuesGetter_returnsTrueForValidValues() {
|
|
||||||
// final char[] valuesA = playerA.values();
|
|
||||||
// assertEquals('x', valuesA[0]);
|
|
||||||
// assertEquals('Z', valuesA[1]);
|
|
||||||
// assertEquals('i', valuesA[2]);
|
|
||||||
//
|
|
||||||
// final char[] valuesB = playerB.values();
|
|
||||||
// assertEquals('O', valuesB[0]);
|
|
||||||
// assertEquals(12, valuesB[1]);
|
|
||||||
// assertEquals((char)-34, valuesB[2]);
|
|
||||||
// assertEquals('s', valuesB[3]);
|
|
||||||
//
|
|
||||||
// final char[] valuesC = playerC.values();
|
|
||||||
// assertEquals((char)9, valuesC[0]);
|
|
||||||
// assertEquals('9', valuesC[1]);
|
|
||||||
// assertEquals((char)-9, valuesC[2]);
|
|
||||||
// assertEquals('0', valuesC[3]);
|
|
||||||
// assertEquals('X', valuesC[4]);
|
|
||||||
// assertEquals('O', valuesC[5]);
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
@@ -1,83 +1,83 @@
|
|||||||
//package org.toop.game.tictactoe;
|
package org.toop.game.tictactoe;
|
||||||
//
|
|
||||||
//import org.toop.game.Game;
|
import org.toop.game.Game;
|
||||||
//
|
|
||||||
//import java.util.Set;
|
import java.util.Set;
|
||||||
//
|
|
||||||
//import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
//import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
//
|
|
||||||
//import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
//
|
|
||||||
//class TicTacToeAITest {
|
class TicTacToeAITest {
|
||||||
// private TicTacToe game;
|
private TicTacToe game;
|
||||||
// private TicTacToeAI ai;
|
private TicTacToeAI ai;
|
||||||
//
|
|
||||||
// @BeforeEach
|
@BeforeEach
|
||||||
// void setup() {
|
void setup() {
|
||||||
// game = new TicTacToe("AI", "AI");
|
game = new TicTacToe();
|
||||||
// ai = new TicTacToeAI();
|
ai = new TicTacToeAI();
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// @Test
|
@Test
|
||||||
// void testBestMove_returnWinningMoveWithDepth1() {
|
void testBestMove_returnWinningMoveWithDepth1() {
|
||||||
// // X X -
|
// X X -
|
||||||
// // O O -
|
// O O -
|
||||||
// // - - -
|
// - - -
|
||||||
// game.play(new Game.Move(0, 'X'));
|
game.play(new Game.Move(0, 'X'));
|
||||||
// game.play(new Game.Move(3, 'O'));
|
game.play(new Game.Move(3, 'O'));
|
||||||
// game.play(new Game.Move(1, 'X'));
|
game.play(new Game.Move(1, 'X'));
|
||||||
// game.play(new Game.Move(4, 'O'));
|
game.play(new Game.Move(4, 'O'));
|
||||||
//
|
|
||||||
// final Game.Move move = ai.findBestMove(game, 1);
|
final Game.Move move = ai.findBestMove(game, 1);
|
||||||
//
|
|
||||||
// assertNotNull(move);
|
assertNotNull(move);
|
||||||
// assertEquals('X', move.value());
|
assertEquals('X', move.value());
|
||||||
// assertEquals(2, move.position());
|
assertEquals(2, move.position());
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// @Test
|
@Test
|
||||||
// void testBestMove_blockOpponentWinDepth1() {
|
void testBestMove_blockOpponentWinDepth1() {
|
||||||
// // - - -
|
// - - -
|
||||||
// // O - -
|
// O - -
|
||||||
// // X X -
|
// X X -
|
||||||
// game.play(new Game.Move(6, 'X'));
|
game.play(new Game.Move(6, 'X'));
|
||||||
// game.play(new Game.Move(3, 'O'));
|
game.play(new Game.Move(3, 'O'));
|
||||||
// game.play(new Game.Move(7, 'X'));
|
game.play(new Game.Move(7, 'X'));
|
||||||
//
|
|
||||||
// final Game.Move move = ai.findBestMove(game, 1);
|
final Game.Move move = ai.findBestMove(game, 1);
|
||||||
//
|
|
||||||
// assertNotNull(move);
|
assertNotNull(move);
|
||||||
// assertEquals('O', move.value());
|
assertEquals('O', move.value());
|
||||||
// assertEquals(8, move.position());
|
assertEquals(8, move.position());
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// @Test
|
@Test
|
||||||
// void testBestMove_preferCornerOnEmpty() {
|
void testBestMove_preferCornerOnEmpty() {
|
||||||
// final Game.Move move = ai.findBestMove(game, 0);
|
final Game.Move move = ai.findBestMove(game, 0);
|
||||||
//
|
|
||||||
// assertNotNull(move);
|
assertNotNull(move);
|
||||||
// assertEquals('X', move.value());
|
assertEquals('X', move.value());
|
||||||
// assertTrue(Set.of(0, 2, 6, 8).contains(move.position()));
|
assertTrue(Set.of(0, 2, 6, 8).contains(move.position()));
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// @Test
|
@Test
|
||||||
// void testBestMove_findBestMoveDraw() {
|
void testBestMove_findBestMoveDraw() {
|
||||||
// // O X -
|
// O X -
|
||||||
// // - O X
|
// - O X
|
||||||
// // X O X
|
// X O X
|
||||||
// game.play(new Game.Move(1, 'X'));
|
game.play(new Game.Move(1, 'X'));
|
||||||
// game.play(new Game.Move(0, 'O'));
|
game.play(new Game.Move(0, 'O'));
|
||||||
// game.play(new Game.Move(5, 'X'));
|
game.play(new Game.Move(5, 'X'));
|
||||||
// game.play(new Game.Move(4, 'O'));
|
game.play(new Game.Move(4, 'O'));
|
||||||
// game.play(new Game.Move(6, 'X'));
|
game.play(new Game.Move(6, 'X'));
|
||||||
// game.play(new Game.Move(7, 'O'));
|
game.play(new Game.Move(7, 'O'));
|
||||||
// game.play(new Game.Move(8, 'X'));
|
game.play(new Game.Move(8, 'X'));
|
||||||
//
|
|
||||||
// final Game.Move move = ai.findBestMove(game, game.getLegalMoves().length);
|
final Game.Move move = ai.findBestMove(game, game.getLegalMoves().length);
|
||||||
//
|
|
||||||
// assertNotNull(move);
|
assertNotNull(move);
|
||||||
// assertEquals('O', move.value());
|
assertEquals('O', move.value());
|
||||||
// assertEquals(2, move.position());
|
assertEquals(2, move.position());
|
||||||
// }
|
}
|
||||||
//}
|
}
|
||||||
Reference in New Issue
Block a user