Moves flip dots. all tests pass. can play reversi local.

This commit is contained in:
Ticho Hidding
2025-10-11 00:51:46 +02:00
parent 5dda85248e
commit c1f7a093ef
6 changed files with 102 additions and 48 deletions

View File

@@ -104,7 +104,6 @@ public abstract class GameCanvas {
graphics.setFill(color); graphics.setFill(color);
graphics.fillOval(x, y, width, height); graphics.fillOval(x, y, width, height);
IO.println("gothere");
} }
public void resize(int width, int height) { public void resize(int width, int height) {

View File

@@ -8,7 +8,6 @@ import org.toop.game.Game;
import java.util.function.Consumer; import java.util.function.Consumer;
public class ReversiCanvas extends GameCanvas{ public class ReversiCanvas extends GameCanvas{
private Game.Move[] mostRecentLegalMoves;
public ReversiCanvas(Color color, int width, int height, Consumer<Integer> onCellClicked) { public ReversiCanvas(Color color, int width, int height, Consumer<Integer> onCellClicked) {
super(color, width, height, 8, 8, 10, true, onCellClicked); super(color, width, height, 8, 8, 10, true, onCellClicked);
drawStartingDots(); drawStartingDots();
@@ -20,18 +19,8 @@ public class ReversiCanvas extends GameCanvas{
drawDot(Color.WHITE,27); drawDot(Color.WHITE,27);
} }
public void drawLegalMoves(Game.Move[] moves){ public void drawLegalMoves(Game.Move[] moves){
mostRecentLegalMoves = moves;
for(Game.Move move : moves){ for(Game.Move move : moves){
IO.println("Legal Moves:" + move.position());
drawDot(new Color(1f,0,0,0.25f),move.position()); drawDot(new Color(1f,0,0,0.25f),move.position());
} }
} }
public void removeLegalMoves(){
if (mostRecentLegalMoves != null){
for(Game.Move move : mostRecentLegalMoves){
drawDot(Color.GRAY,move.position()); //todo get current background color or make this redraw the entire board
}
}
mostRecentLegalMoves = null;
}
} }

View File

@@ -7,6 +7,7 @@ import org.toop.app.layer.*;
import org.toop.app.layer.containers.HorizontalContainer; import org.toop.app.layer.containers.HorizontalContainer;
import org.toop.app.layer.containers.VerticalContainer; import org.toop.app.layer.containers.VerticalContainer;
import org.toop.app.layer.layers.MainLayer; import org.toop.app.layer.layers.MainLayer;
import org.toop.game.Game;
import org.toop.game.reversi.Reversi; import org.toop.game.reversi.Reversi;
import org.toop.game.reversi.ReversiAI; import org.toop.game.reversi.ReversiAI;
import org.toop.local.AppContext; import org.toop.local.AppContext;
@@ -19,7 +20,9 @@ public class ReversiLayer extends Layer{
super("bg-secondary"); //make reversiboard background dark green super("bg-secondary"); //make reversiboard background dark green
canvas = new ReversiCanvas(Color.GREEN,(App.getHeight() / 100) * 75, (App.getHeight() / 100) * 75, (cell) -> { canvas = new ReversiCanvas(Color.GREEN,(App.getHeight() / 100) * 75, (App.getHeight() / 100) * 75, (cell) -> {
IO.println("clicked reversi cell: "+cell); reversi.play(new Game.Move(cell,reversi.getCurrentPlayer()));
reload();
canvas.drawLegalMoves(reversi.getLegalMoves());
}); });
reversi = new Reversi() ; reversi = new Reversi() ;
reversiAI = new ReversiAI(); reversiAI = new ReversiAI();

View File

@@ -6,6 +6,7 @@ import org.toop.game.tictactoe.TicTacToe;
import java.awt.*; import java.awt.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@@ -13,7 +14,7 @@ import java.util.Set;
public final class Reversi extends TurnBasedGame { public final class Reversi extends TurnBasedGame {
private int movesTaken; private int movesTaken;
public static final char FIRST_MOVE = 'B'; public static final char FIRST_MOVE = 'B';
private Set<Point> filledCells = new HashSet<Point>(); private Set<Point> filledCells = new HashSet<>();
public Reversi() { public Reversi() {
super(8, 8, 2); super(8, 8, 2);
@@ -48,8 +49,10 @@ public final class Reversi extends TurnBasedGame {
char[][] boardGrid = makeBoardAGrid(); char[][] boardGrid = makeBoardAGrid();
char currentPlayer = (currentTurn==0) ? 'B' : 'W'; char currentPlayer = (currentTurn==0) ? 'B' : 'W';
Set<Point> adjCell = getAdjacentCells(boardGrid); Set<Point> adjCell = getAdjacentCells(boardGrid);
for (Point point : adjCell){ for (Point point : adjCell){
int score = getScoreForPotentialMove(point,boardGrid,currentPlayer); Move[] moves = getFlipsForPotentialMove(point,boardGrid,currentPlayer);
int score = moves.length;
if (score > 0){ if (score > 0){
legalMoves.add(new Move(point.x + point.y * 8, currentPlayer)); legalMoves.add(new Move(point.x + point.y * 8, currentPlayer));
} }
@@ -63,11 +66,11 @@ public final class Reversi extends TurnBasedGame {
for (int deltaColumn = -1; deltaColumn <= 1; deltaColumn++){ //check adjacent cells for (int deltaColumn = -1; deltaColumn <= 1; deltaColumn++){ //check adjacent cells
for (int deltaRow = -1; deltaRow <= 1; deltaRow++){ //orthogonally and diagonally for (int deltaRow = -1; deltaRow <= 1; deltaRow++){ //orthogonally and diagonally
int newX = point.x + deltaColumn, newY = point.y + deltaRow; int newX = point.x + deltaColumn, newY = point.y + deltaRow;
if (deltaColumn == 0 || deltaRow == 0 //continue if out of bounds if (deltaColumn == 0 && deltaRow == 0 //continue if out of bounds
|| !isOnBoard(newX, newY)) { || !isOnBoard(newX, newY)) {
continue; continue;
} }
if (boardGrid[newX][newY] == Game.EMPTY) { //check if the cell is empty if (boardGrid[newY][newX] == Game.EMPTY) { //check if the cell is empty
possibleCells.add(new Point(newX, newY)); //and then add it to the set of possible moves possibleCells.add(new Point(newX, newY)); //and then add it to the set of possible moves
} }
} }
@@ -76,66 +79,75 @@ public final class Reversi extends TurnBasedGame {
return possibleCells; return possibleCells;
} }
public int getScoreForPotentialMove(Point point, char[][] boardGrid, char currentPlayer) { public Move[] getFlipsForPotentialMove(Point point, char[][] boardGrid, char currentPlayer) {
int score = 0; final ArrayList<Move> movesToFlip = new ArrayList<>();
for (int deltaColumn = -1; deltaColumn <= 1; deltaColumn++) { for (int deltaColumn = -1; deltaColumn <= 1; deltaColumn++) {
for (int deltaRow = -1; deltaRow <= 1; deltaRow++) { for (int deltaRow = -1; deltaRow <= 1; deltaRow++) {
if (deltaColumn == 0 && deltaRow == 0){ if (deltaColumn == 0 && deltaRow == 0){
continue; continue;
} }
score += getScoreInDirection(point,boardGrid,currentPlayer,deltaColumn,deltaRow); Move[] moves = getFlipsInDirection(point,boardGrid,currentPlayer,deltaColumn,deltaRow);
if (moves != null) {
movesToFlip.addAll(Arrays.asList(moves));
} }
} }
return score; }
return movesToFlip.toArray(new Move[0]);
} }
private int getScoreInDirection(Point point, char[][] boardGrid, char currentPlayer, int dirX, int dirY) { private Move[] getFlipsInDirection(Point point, char[][] boardGrid, char currentPlayer, int dirX, int dirY) {
char opponent = getOpponent(currentPlayer); char opponent = getOpponent(currentPlayer);
final ArrayList<Move> movesToFlip = new ArrayList<>();
int x = point.x + dirX; int x = point.x + dirX;
int y = point.y + dirY; int y = point.y + dirY;
int count = 0;
if (!isOnBoard(x, y) || boardGrid[y][x] != opponent) { if (!isOnBoard(x, y) || boardGrid[y][x] != opponent) {
return 0; return null;
} }
while (isOnBoard(x, y) && boardGrid[y][x] == opponent) { while (isOnBoard(x, y) && boardGrid[y][x] == opponent) {
count++;
movesToFlip.add(new Move(x+y*8, currentPlayer));
x += dirX; x += dirX;
y += dirY; y += dirY;
} }
if (isOnBoard(x, y) && boardGrid[y][x] == currentPlayer) { if (isOnBoard(x, y) && boardGrid[y][x] == currentPlayer) {
return count; return movesToFlip.toArray(new Move[0]);
} }
return null;
return 0;
} }
private boolean isOnBoard(int x, int y) { private boolean isOnBoard(int x, int y) {
return x >= 0 && x < 8 && y >= 0 && y < 8; return x >= 0 && x < 8 && y >= 0 && y < 8;
} }
private char getOpponent(char currentPlayer){
if (currentPlayer == 'B') {
return 'W';
}
else {
return 'B';
}
}
public char[][] makeBoardAGrid() { public char[][] makeBoardAGrid() {
char[][] boardGrid = new char[8][8]; char[][] boardGrid = new char[8][8];
for (int i = 0; i < 64; i++) { for (int i = 0; i < 64; i++) {
boardGrid[i / 8][i % 8] = board[i]; boardGrid[i / 8][i % 8] = board[i]; //boardGrid[y / row] [x / column]
} }
return boardGrid; return boardGrid;
} }
@Override @Override
public State play(Move move) { public State play(Move move) {
filledCells.add(new Point(move.position() / 8, move.position() % 8)); Move[] legalMoves = getLegalMoves();
boolean moveIsLegal = false;
for (Move legalMove : legalMoves) {
if (move.equals(legalMove)) {
moveIsLegal = true;
break;
}
}
if (moveIsLegal) {
Move[] moves = getFlipsForPotentialMove(new Point(move.position()%8,move.position()/8), makeBoardAGrid(), move.value());
board[move.position()] = move.value();
for (Move m : moves) {
board[m.position()] = m.value();
}
updateFilledCellsSet();
nextTurn(); nextTurn();
return State.NORMAL;
}
return null; return null;
} }
@@ -152,6 +164,15 @@ public final class Reversi extends TurnBasedGame {
} }
} }
private char getOpponent(char currentPlayer){
if (currentPlayer == 'B') {
return 'W';
}
else {
return 'B';
}
}
public Game.Score getScore(){ public Game.Score getScore(){
int player1Score = 0, player2Score = 0; int player1Score = 0, player2Score = 0;
for (int count = 0; count < 63; count++) { for (int count = 0; count < 63; count++) {

View File

@@ -6,6 +6,6 @@ import org.toop.game.Game;
public final class ReversiAI extends AI<Reversi> { public final class ReversiAI extends AI<Reversi> {
@Override @Override
public Game.Move findBestMove(Reversi game, int depth) { public Game.Move findBestMove(Reversi game, int depth) {
return null; return game.getLegalMoves()[0];
} }
} }

View File

@@ -2,7 +2,7 @@ package org.toop.game.tictactoe;
import org.toop.game.Game; import org.toop.game.Game;
import java.util.Set; import java.util.*;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -30,46 +30,88 @@ class ReversiTest {
assertEquals('B',game.board[35]); assertEquals('B',game.board[35]);
assertEquals('W',game.board[36]); assertEquals('W',game.board[36]);
} }
@Test @Test
void testGetLegalMovesAtStart() { void testGetLegalMovesAtStart() {
Game.Move[] moves = game.getLegalMoves(); Game.Move[] moves = game.getLegalMoves();
List<Game.Move> expectedMoves = List.of(
new Game.Move(19,'B'),
new Game.Move(26,'B'),
new Game.Move(37,'B'),
new Game.Move(44,'B')
);
assertNotNull(moves); assertNotNull(moves);
assertTrue(moves.length > 0); assertTrue(moves.length > 0);
assertEquals(new Game.Move(19,'B'),moves[0]); assertMovesMatchIgnoreOrder(expectedMoves, Arrays.asList(moves));
assertEquals(new Game.Move(26,'B'),moves[0]);
assertEquals(new Game.Move(37,'B'),moves[0]);
assertEquals(new Game.Move(44,'B'),moves[0]);
} }
private void assertMovesMatchIgnoreOrder(List<Game.Move> expected, List<Game.Move> actual) {
assertEquals(expected.size(), actual.size());
for (int i = 0; i < expected.size(); i++) {
assertTrue(actual.contains(expected.get(i)));
assertTrue(expected.contains(actual.get(i)));
}
}
@Test @Test
void testMakeValidMoveFlipsPieces() { void testMakeValidMoveFlipsPieces() {
game.play(new Game.Move(19, 'B')); game.play(new Game.Move(19, 'B'));
assertEquals('B', game.board[19]); assertEquals('B', game.board[19]);
assertEquals('B', game.board[27], "Piece should have flipped to B"); assertEquals('B', game.board[27], "Piece should have flipped to B");
} }
@Test @Test
void testMakeInvalidMoveDoesNothing() { void testMakeInvalidMoveDoesNothing() {
char[] before = game.board.clone(); char[] before = game.board.clone();
game.play(new Game.Move(0, 'B')); game.play(new Game.Move(0, 'B'));
assertArrayEquals(before, game.board, "Board should not change on invalid move"); assertArrayEquals(before, game.board, "Board should not change on invalid move");
} }
@Test @Test
void testTurnSwitchesAfterValidMove() { void testTurnSwitchesAfterValidMove() {
char current = game.getCurrentPlayer(); char current = game.getCurrentPlayer();
game.play(game.getLegalMoves()[0]); game.play(game.getLegalMoves()[0]);
assertNotEquals(current, game.getCurrentPlayer(), "Player turn should switch after a valid move"); assertNotEquals(current, game.getCurrentPlayer(), "Player turn should switch after a valid move");
} }
@Test @Test
void testCountScoreCorrectly() { void testCountScoreCorrectlyAtStart() {
long start = System.nanoTime();
Game.Score score = game.getScore(); Game.Score score = game.getScore();
assertEquals(2, score.player1Score()); // Black assertEquals(2, score.player1Score()); // Black
assertEquals(2, score.player2Score()); // White assertEquals(2, score.player2Score()); // White
long end = System.nanoTime();
IO.println((end-start));
} }
@Test
void testCountScoreCorrectlyAtEnd() {
for (int i = 0; i < 10; i++){
game = new Reversi();
Game.Move[] legalMoves = game.getLegalMoves();
while(legalMoves.length > 0) {
game.play(legalMoves[(int)(Math.random()*legalMoves.length)]);
legalMoves = game.getLegalMoves();
}
Game.Score score = game.getScore();
IO.println(score.player1Score());
IO.println(score.player2Score());
char[][] grid = game.makeBoardAGrid();
for (char[] chars : grid) {
IO.println(Arrays.toString(chars));
}
}
}
@Test @Test
void testAISelectsLegalMove() { void testAISelectsLegalMove() {
Game.Move move = ai.findBestMove(game,4); Game.Move move = ai.findBestMove(game,4);
assertNotNull(move); assertNotNull(move);
assertTrue(containsMove(game.getLegalMoves(),move), "AI should always choose a legal move"); assertTrue(containsMove(game.getLegalMoves(),move), "AI should always choose a legal move");
} }
private boolean containsMove(Game.Move[] moves, Game.Move move) { private boolean containsMove(Game.Move[] moves, Game.Move move) {
for (Game.Move m : moves) { for (Game.Move m : moves) {
if (m.equals(move)) return true; if (m.equals(move)) return true;