I am trying to code an AI to play TicTacToe using the minimax algorithm. I understand the algorithm and have researched how to use it in terms of java and TicTacToe. I have an an opponent (AI) that calculates and plays the best move after the player has clicked a tile. I am having a problem where if the player clicks the centre tile then all 8 remaining tiles are filled with the opponent's token. This is also followed with an ArrayOutOfBounds Exception where the best column should be return.
This is the first time that I have tried implementing an algorithm or any kind of AI into a java application.
Here is my code so far:
TicTacToe.java
package com.cmarshall10450.TicTacToe;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Chris on 02/08/2015.
*/
public class TicTacToe extends Application {
public static final int ROWS = 3;
public static final int COLS = 3;
enum Seed{
COMPUTER("O"), PLAYER("X"), EMPTY("");
String token;
Seed(String token){
this.token = token;
}
}
private Pane root = new Pane();
private boolean playable = true;
private Seed playerSeed = Seed.PLAYER;
private Tile[][] board = new Tile[3][3];
private List<Combo> combos = new ArrayList<>();
private AIPlayer opponent = new AIPlayerMinimax(board);
private Parent createContent(){
root.setPrefSize(600, 600);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
Tile tile = new Tile(j*200, i*200);
root.getChildren().add(tile);
board[j][i] = tile;
}
}
//Horizontal
for (int y = 0; y < 3; y++) {
combos.add(new Combo(board[0][y], board[1][y], board[2][y]));
}
//Vertical
for (int x = 0; x < 3; x++) {
combos.add(new Combo(board[x][0], board[x][1], board[x][2]));
}
//Diagonal
combos.add(new Combo(board[0][0], board[1][1], board[2][2]));
combos.add(new Combo(board[2][0], board[1][1], board[0][2]));
return root;
}
private void checkState(){
for (Combo combo : combos){
if(combo.isComplete()){
playable = false;
playWinAnimation(combo);
break;
}
}
}
private void playComputerMove(){
opponent.setBoard(board);
playerSeed = Seed.PLAYER;
int bestRow = opponent.move()[1];
int bestCol = opponent.move()[2];
board[bestRow][bestCol].drawPlayerToken(Seed.COMPUTER);
}
private void playWinAnimation(Combo combo){
Line line = new Line();
line.setStartX(combo.tiles[0].getCenterX());
line.setStartY(combo.tiles[0].getCenterY());
line.setEndX(combo.tiles[0].getCenterX());
line.setEndY(combo.tiles[0].getCenterY());
root.getChildren().add(line);
Timeline timeline = new Timeline();
timeline.getKeyFrames().addAll(new KeyFrame(Duration.seconds(1),
new KeyValue(line.endXProperty(), combo.tiles[2].getCenterX()),
new KeyValue(line.endYProperty(), combo.tiles[2].getCenterY())
));
timeline.play();
}
private class Combo {
private Tile[] tiles;
public Combo(Tile... tiles){
this.tiles = tiles;
}
public boolean isComplete(){
if(tiles[0].getValue().isEmpty()){
return false;
}
return tiles[0].getValue().equals(tiles[1].getValue())
&& tiles[0].getValue().equals(tiles[2].getValue());
}
}
public class Tile extends StackPane{
private Text text = new Text(Seed.EMPTY.token);
public Tile(int translateX, int translateY){
Rectangle border = new Rectangle(200, 200);
border.setFill(null);
border.setStroke(Color.BLACK);
text.setFont(Font.font(72));
setTranslateX(translateX);
setTranslateY(translateY);
setAlignment(Pos.CENTER);
getChildren().addAll(border, text);
setOnMouseClicked(event -> {
if(!playable){
return;
}
if(event.getButton() == MouseButton.PRIMARY && playerSeed == Seed.PLAYER){
drawPlayerToken(Seed.PLAYER);
playerSeed = Seed.COMPUTER;
if(isHintModeEnabled()){
//TODO: compare move made by player to best move possible for player
}
checkState();
playComputerMove();
}
});
}
public String getValue(){
return text.getText();
}
public void drawPlayerToken(Seed playerSeed){
if(playerSeed == Seed.PLAYER){
text.setText(Seed.PLAYER.token);
}
else{
text.setText(Seed.COMPUTER.token);
}
}
public Double getCenterX(){
return getTranslateX() + 100;
}
public Double getCenterY(){
return getTranslateY() + 100;
}
}
private boolean isHintModeEnabled(){
return true;
}
#Override
public void start(Stage window) throws Exception {
window.setScene(new Scene(createContent()));
window.show();
}
public static void main(String[] args){
launch(args);
}
}
AIPlayer.java
package com.cmarshall10450.TicTacToe;
import com.cmarshall10450.TicTacToe.TicTacToe.Tile;
import com.cmarshall10450.TicTacToe.TicTacToe.Seed;
/**
* Abstract superclass for all AI players with different strategies.
* To construct an AI player:
* 1. Construct an instance (of its subclass) with the game Board
* 2. Call setSeed() to set the computer's seed
* 3. Call move() which returns the next move in an int[2] array of {row, col}.
*
* The implementation subclasses need to override abstract method move().
* They shall not modify Tile[][], i.e., no side effect expected.
* Assume that next move is available, i.e., not game-over yet.
*/
public abstract class AIPlayer {
protected int rows = TicTacToe.ROWS; // number of rows
protected int cols = TicTacToe.COLS; // number of columns
protected Tile[][] board; // the board's ROWS-by-COLS array of Cells
protected Seed aiSeed = Seed.COMPUTER; // computer's seed
protected Seed playerSeed = Seed.PLAYER; // opponent's seed
/** Constructor with reference to game board */
public AIPlayer(Tile[][] board) {
this.board = board;
}
/** Set/change the seed used by computer and opponent */
public void setSeed(Seed seed) {
this.aiSeed = seed;
playerSeed = (aiSeed == Seed.COMPUTER) ? Seed.COMPUTER : Seed.PLAYER;
}
public void setBoard(Tile[][] board) {
this.board = board;
}
/** Abstract method to get next move. Return int[2] of {row, col} */
abstract int[] move(); // to be implemented by subclasses
}
AIPlayerMinimax.java
package com.cmarshall10450.TicTacToe;
import java.util.ArrayList;
import java.util.List;
import com.cmarshall10450.TicTacToe.TicTacToe.Tile;
import com.cmarshall10450.TicTacToe.TicTacToe.Seed;
/** AIPlayer using Minimax algorithm */
public class AIPlayerMinimax extends AIPlayer {
private int[] winningPatterns = {
0b111000000, 0b000111000, 0b000000111, // rows
0b100100100, 0b010010010, 0b001001001, // cols
0b100010001, 0b001010100 // diagonals
};
/** Constructor with the given game board */
public AIPlayerMinimax(Tile[][] board) {
super(board);
}
/** Get next best move for computer. Return int[2] of {row, col} */
#Override
int[] move() {
int[] result = minimax(2, aiSeed); // depth, max turn
return new int[] {result[1], result[2]}; // row, col
}
/** Recursive minimax at level of depth for either maximizing or minimizing player.
Return int[3] of {score, row, col} */
private int[] minimax(int depth, Seed player) {
// Generate possible next moves in a List of int[2] of {row, col}.
List<int[]> nextMoves = generateMoves();
// aiSeed is maximizing; while playerSeed is minimizing
int bestScore = (player == aiSeed) ? Integer.MIN_VALUE : Integer.MAX_VALUE;
int currentScore;
int bestRow = -1;
int bestCol = -1;
if (nextMoves.isEmpty() || depth == 0) {
// Gameover or depth reached, evaluate score
bestScore = evaluate();
}
else {
for (int[] move : nextMoves) {
// Try this move for the current "player"
board[move[0]][move[1]].drawPlayerToken(player);
if (player == aiSeed) { // aiSeed (computer) is maximizing player
currentScore = minimax(depth - 1, playerSeed)[0];
if (currentScore > bestScore) {
bestScore = currentScore;
bestRow = move[0];
bestCol = move[1];
}
} else { // playerSeed is minimizing player
currentScore = minimax(depth - 1, aiSeed)[0];
if (currentScore < bestScore) {
bestScore = currentScore;
bestRow = move[0];
bestCol = move[1];
}
}
// Undo move
board[move[0]][move[1]].drawPlayerToken(Seed.EMPTY);
}
}
return new int[] {bestScore, bestRow, bestCol};
}
/** Find all valid next moves.
Return List of moves in int[2] of {row, col} or empty list if gameover */
private List<int[]> generateMoves() {
List<int[]> nextMoves = new ArrayList<>(); // allocate List
// If gameover, i.e., no next move
if (hasWon(aiSeed) || hasWon(playerSeed)) {
return nextMoves; // return empty list
}
// Search for empty board and add to the List
for (int row = 0; row < rows; ++row) {
for (int col = 0; col < cols; ++col) {
if (board[row][col].getValue() == Seed.EMPTY.token) {
nextMoves.add(new int[] {row, col});
}
}
}
return nextMoves;
}
/** The heuristic evaluation function for the current board
#Return +100, +10, +1 for EACH 3-, 2-, 1-in-a-line for computer.
-100, -10, -1 for EACH 3-, 2-, 1-in-a-line for opponent.
0 otherwise */
private int evaluate() {
int score = 0;
// Evaluate score for each of the 8 lines (3 rows, 3 columns, 2 diagonals)
score += evaluateLine(0, 0, 0, 1, 0, 2); // row 0
score += evaluateLine(1, 0, 1, 1, 1, 2); // row 1
score += evaluateLine(2, 0, 2, 1, 2, 2); // row 2
score += evaluateLine(0, 0, 1, 0, 2, 0); // col 0
score += evaluateLine(0, 1, 1, 1, 2, 1); // col 1
score += evaluateLine(0, 2, 1, 2, 2, 2); // col 2
score += evaluateLine(0, 0, 1, 1, 2, 2); // diagonal
score += evaluateLine(0, 2, 1, 1, 2, 0); // alternate diagonal
return score;
}
/** The heuristic evaluation function for the given line of 3 board
#Return +100, +10, +1 for 3-, 2-, 1-in-a-line for computer.
-100, -10, -1 for 3-, 2-, 1-in-a-line for opponent.
0 otherwise */
private int evaluateLine(int row1, int col1, int row2, int col2, int row3, int col3) {
int score = 0;
// First cell
if (board[row1][col1].getValue() == aiSeed.token) {
score = 1;
} else if (board[row1][col1].getValue() == playerSeed.token) {
score = -1;
}
// Second cell
if (board[row2][col2].getValue() == aiSeed.token) {
if (score == 1) { // cell1 is aiSeed
score = 10;
} else if (score == -1) { // cell1 is playerSeed
return 0;
} else { // cell1 is empty
score = 1;
}
} else if (board[row2][col2].getValue() == playerSeed.token) {
if (score == -1) { // cell1 is playerSeed
score = -10;
} else if (score == 1) { // cell1 is aiSeed
return 0;
} else { // cell1 is empty
score = -1;
}
}
// Third cell
if (board[row3][col3].getValue() == aiSeed.token) {
if (score > 0) { // cell1 and/or cell2 is aiSeed
score *= 10;
} else if (score < 0) { // cell1 and/or cell2 is playerSeed
return 0;
} else { // cell1 and cell2 are empty
score = 1;
}
} else if (board[row3][col3].getValue() == playerSeed.token) {
if (score < 0) { // cell1 and/or cell2 is playerSeed
score *= -10;
} else if (score > 1) { // cell1 and/or cell2 is aiSeed
return 0;
} else { // cell1 and cell2 are empty
score = -1;
}
}
return score;
}
/** Returns true if thePlayer wins */
private boolean hasWon(Seed player) {
int pattern = 0b000000000; // 9-bit pattern for the 9 board
for (int row = 0; row < rows; ++row) {
for (int col = 0; col < cols; ++col) {
if (board[row][col].getValue() == player.token) {
pattern |= (1 << (row * cols + col));
}
}
}
for (int winningPattern : winningPatterns) {
if ((pattern & winningPattern) == winningPattern) return true;
}
return false;
}
}
NullPointerException Stack Trace
Exception in thread "JavaFX Application Thread" java.lang.ArrayIndexOutOfBoundsException: -1
at com.cmarshall10450.TicTacToe.TicTacToe.playComputerMove(TicTacToe.java:100)
at com.cmarshall10450.TicTacToe.TicTacToe.access$500(TicTacToe.java:28)
at com.cmarshall10450.TicTacToe.TicTacToe$Tile.lambda$new$0(TicTacToe.java:170)
at com.cmarshall10450.TicTacToe.TicTacToe$Tile$$Lambda$70/1842446135.handle(Unknown Source)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$ClickGenerator.postProcess(Scene.java:3471)
at javafx.scene.Scene$ClickGenerator.access$8100(Scene.java:3399)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3767)
at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3486)
at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2495)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:350)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:275)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$350(GlassViewEventHandler.java:385)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$$Lambda$98/1651003963.get(Unknown Source)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:404)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:384)
at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
at com.sun.glass.ui.View.notifyMouse(View.java:927)
Any help or advice that you could give me would be greatly appreciated.
Related
public class ChutesAndLadders2d {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[][] numbersOnBoard = new int [6][6];
boardSetUpA (numbersOnBoard);
printTwoD(numbersOnBoard);
}
public static void boardSetUpA (int[][]twoD) {
//Square with even size
//even rows
for (int row = 0;row<twoD.length; row ++) {
if (row %2 ==0) {
int num = twoD.length*(twoD.length-row);
for (int col = 0; col<twoD[row].length; col ++ ) {
twoD[row][col] = num;
num--;
}
}//
else {
int num = twoD.length*(twoD.length-(row + 1))+ 1;
for (int col = 0; col<twoD[row].length; col ++ ) {
twoD[row][col] = num;
num++;
}
}
}//for row
}//
public static void printTwoD(int [][] array){
for (int row = 0; row < array.length; row++){
for (int column = 0; column < array[row].length; column++){
System.out.print(array[row][column] + "\t");
}
System.out.println();
}
}
public static void boardDetails(String[][]board) {
for (int row = 0;row<board.length; row++){
for (int col = 0;col<board[row].length; col++){
if( col+2 == row||col+1 == row*2 ){
board[row][col] = "Lad"; // Append value
}
else if (col*2 == row|| row*2 == col){
board[row][col] = "Cht";// Append value
}
else {
board[row][col] = " ";
}
}
board[board.length-1][0] = "Start";
if (board.length%2 ==0) {
board[0][0] = "End";}
else {
board[0][board.length-1]="End";
}
}
}
public static void printBoard (int[][]twoD, String[][]strTwoD) {
//Printing
for (int row = 0;row<twoD.length;row++) {
for (int col = 0;col<twoD[row].length;col++) {
System.out.print(twoD[row][col] + " "+strTwoD[row][col]+"\t\t");
}
System.out.println("\n");
}
}
}
This is the starter code I have for setting up the snakes and ladders game. I also tried to set the chutes/snakes and ladders on the board but it is not printing. How should I fix it and how do I develop this code to have three methods: update the moves of a player once he reaches a snake, a ladder, and once he rolls his die, from one place to another?
Is it possible to implement a Shutes & Ladders game using a 2D Array? For sure! Does that make sense in an object-oriented language such as Java? I dont know ....
What do you need for that?
A square board with e.g. 36 playing fields.
Connections between two playing fields. (shutes and ladders)
Pawns and a dice.
A renderer that outputs the playing field (as text or graphics).
A program that allows input and connects everything to a functioning game.
Here is an example that works with a List instead of an Array. That can certainly be changed if it is necessary for your purposes.
I hope this is of some help to you.
P.S .: After the start, the board is displayed with field numbers. Shutes are shown as red lines. Ladders as green lines. Keys 1-6 on the keyboard simulate rolling the dice..
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyAdapter;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import javax.swing.*;
public class ChutesAndLadders2d {
public static void main(String[] args) {
JFrame frame = new JFrame("Chutes and Ladders 2D");
Game game = new ChutesAndLadders2d().new Game();
game.setPreferredSize(new Dimension(400, 400));
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setContentPane(game);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
#SuppressWarnings("serial")
class Game extends JPanel{
private final Font defaultFont = new Font("Arial", Font.PLAIN, 16);
private final BasicStroke stroke = new BasicStroke(4f);
private static final int SCALE = 64;
// board and pawns
private final Board board = new Board(6);
private final List<Pawn> pawns = new ArrayList<>();
public Game(){
setFocusable(true); // receive Keyboard-Events
addKeyListener(new KeyAdapter(){
#Override
public void keyTyped(KeyEvent e) {
char c = e.getKeyChar();
if(c >= '1' && c <= '6'){
int steps = Integer.parseInt(Character.toString(c));
Pawn pawn = pawns.get(0);
pawn.move(steps);
Field field = board.get(pawn.fieldIndex);
if(field.targetKind() != Kind.NONE){
pawn.move(field.getTarget().index - field.index);
}
repaint();
}
}
});
board.connect(5, 12); // Ladder 5 -> 12
board.connect(8, 4); // Shute 8 -> 4
board.connect(15, 32); // Ladder 15 -> 32
board.connect(35, 17); // Shute 35 -> 17
board.connect(23, 30); // Ladder 23 -> 30
pawns.add(new Pawn(Color.BLUE, board.size() - 1));
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
setFont(defaultFont);
for(Field field : board){
Point p = field.getLocation();
g2d.drawRect(p.x * SCALE, p.y * SCALE, SCALE, SCALE);
g2d.drawString(field.text, p.x * SCALE + 24, p.y * SCALE + 40);
}
for(Field field : board){
if(field.targetKind() != Kind.NONE){
g2d.setColor(field.targetKind() == Kind.LADDER ? Color.GREEN : Color.RED);
g2d.setStroke(stroke);
Point source = field.getLocation();
Point target = field.getTarget().getLocation();
g2d.drawLine(source.x * SCALE + 40, source.y * SCALE + 24, target.x * SCALE + 40, target.y * SCALE + 24);
}
}
for(Pawn pawn : pawns){
Point loc = board.get(pawn.fieldIndex).getLocation();
g2d.setColor(pawn.color);
g2d.fillOval(loc.x * SCALE + 32, loc.y * SCALE + 32, 16, 16);
}
}
}
class Board implements Iterable<Field>{
private final List<Field> fields = new ArrayList<>();
public Board(int size){
for(int index = 0; index < size * size; index++)
fields.add(new Field(index, size));
}
public Field get(int index){
return fields.get(index);
}
public void connect(int startFieldnumber, int targetFieldnumber){
get(startFieldnumber - 1).setTarget(get(targetFieldnumber - 1));
}
#Override
public Iterator<Field> iterator() {
return fields.iterator();
}
public int size(){
return fields.size();
}
}
class Field{
final int index;
final String text;
final int size;
private Field target;
public Field(int index, int size){
this.index = index;
this.size = size;
text = "" + (index + 1);
}
public void setTarget(Field target){
if(target == this) return;
this.target = target;
}
public Field getTarget(){
return target;
}
public Kind targetKind(){
if(target == null) return Kind.NONE;
return index < target.index ? Kind.LADDER : Kind.SHUTE;
}
public Point getLocation(){
int x = index % size;
int y = index / size;
if(y % 2 != 0) x = size - x - 1;
return new Point(x, size - y - 1);
}
}
class Pawn{
int fieldIndex = 0;
int maxIndex;
Color color;
public Pawn(Color color, int maxIndex){
this.color = color;
this.maxIndex = maxIndex;
}
public void move(int steps){
fieldIndex += steps;
if(fieldIndex < 0) fieldIndex = 0;
if(fieldIndex > maxIndex) fieldIndex = maxIndex;
}
}
enum Kind{
NONE, SHUTE, LADDER
}
}
I finished writing a program that creates a maze recursively but have not been able to figure out how to have it draw the maze after each step. Any changes to the maze happen before the next recursive call. The maze is prerendered onto a JPanel as a grid of squares, and I have attempted to have the program render each step, by using JPanel.repaint before the next recursive call (I have comments within my generate method where I have previously tried to repaint. No matter what I try, the maze just simply renders the finished product (maze with all of the paths, walls, etc) all at once at the end. Attached is my recursive generate method.
private static boolean generate(int x, int y) {
System.out.println("xcord: " + x + ", ycord: " + y);
//panel.repaint(); when i have repaint here, it renders the entire maze at the end
a[x][y].visited = true;
if (unvisitedCells == 0) { // if you have visited all of the cells, maze is done generating
System.out.println("done");
return true;
}
int movesTried = 0; // keeps track of which directions have been tried
int currentMove = (int) (Math.random() * 4); // try moving a random direction first (0 = north, 1 = east, etc.)
while (movesTried < 4) { // continue as long as all four moves havent been tried
// north move
if (a[x][y].northCell != null && a[x][y].northCell.visited != true && currentMove == 0) {
a[x][y].northCell.visited = true;
a[x][y].northWall = false;
a[x][y].northCell.southWall = false;
unvisitedCells -= 1;
// tried repainting here, but had no effect
if (generate(x, y - 1)) {
return true; // move successful
}
}
// east move
if (a[x][y].eastCell != null && a[x][y].eastCell.visited != true && currentMove == 1) {
a[x][y].eastCell.visited = true;
a[x][y].eastWall = false;
a[x][y].eastCell.westWall = false;
unvisitedCells -= 1;
// tried repainting here, but had no effect
if (generate(x + 1, y)) {
return true; // move successful
}
}
// south move
if (a[x][y].southCell != null && a[x][y].southCell.visited != true && currentMove == 2) {
a[x][y].southCell.visited = true;
a[x][y].southWall = false;
a[x][y].southCell.northWall = false;
unvisitedCells -= 1;
// tried repainting here, but had no effect
if (generate(x, y + 1)) {
return true; // move successful
}
}
// west move
if (a[x][y].westCell != null && a[x][y].westCell.visited != true && currentMove == 3) {
a[x][y].westCell.visited = true;
a[x][y].westWall = false;
a[x][y].westCell.eastWall = false;
unvisitedCells -= 1;
// tried repainting here, but had no effect
if (generate(x - 1, y)) {
return true; // move successful
}
}
movesTried++; // another move has been tried
if (currentMove == 3 && movesTried < 4) {
currentMove = 0; // wraps back to north move if maze started at a move greater than 0, and you
// have more moves to try
} else {
currentMove++;
}
}
// at this point, all 4 moves have been tried, and there are no possible moves
// from the current maze cell
return false;
}
Each cell is rendered individually onto a JPanel, using information kept in a MazeCell class.
public class MazeCell {
public boolean northWall = true;
public boolean eastWall = true;
public boolean southWall = true;
public boolean westWall = true;
public MazeCell northCell = null;
public MazeCell eastCell = null;
public MazeCell southCell = null;
public MazeCell westCell = null;
public boolean visited = false;
}
Here I have set up a JPanel which draws each cell individually, based on whether there is a wall in each of 4 directions.
panel = new JPanel() {
private static final long serialVersionUID = 1L;
public void paintComponent(Graphics g) {
super.paintComponent(g);
a[0][0].northWall = false;
a[mazeSize - 1][mazeSize - 1].southWall = false;
for (int y = 0; y < mazeSize; y++) {
for (int x = 0; x < mazeSize; x++) {
if (a[x][y].northWall) {
g.drawLine(100 + (x * 25), 100 + (y * 25), 100 + (x * 25) + 25, 100 + (y * 25));
}
if (a[x][y].eastWall) {
g.drawLine(100 + (x * 25) + 25, 100 + (y * 25), 100 + (x * 25) + 25, 100 + (y * 25) + 25);
}
if (a[x][y].southWall) {
g.drawLine(100 + (x * 25), 100 + (y * 25) + 25, 100 + (x * 25) + 25, 100 + (y * 25) + 25);
}
if (a[x][y].westWall) {
g.drawLine(100 + (x * 25), 100 + (y * 25), 100 + (x * 25), 100 + (y * 25) + 25);
}
}
}
}
};
Warp the long process (generate(int x, int y)) with a SwingWorker, and let it update the GUI. Here is an example:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
public class RecursiveGuiUpdate extends JFrame {
private final int SIZE = 4;
JLabel[][] grid = new JLabel[SIZE][SIZE];
RecursiveGuiUpdate() {
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
add(getGrid(), BorderLayout.NORTH);
JButton paint = new JButton("Paint");
paint.addActionListener(a -> updateGui());
add(paint, BorderLayout.SOUTH);
pack();
setVisible(true);
}
private void updateGui() {
new Task().execute();
}
private Component getGrid() {
JPanel panel = new JPanel(new GridLayout(SIZE, SIZE));
for(int i=0; i<=(SIZE-1); i++) {
for(int j=0; j<=(SIZE-1); j++) {
JLabel l = new JLabel(i+"-"+j, JLabel.CENTER);
l.setOpaque(true);
panel.add(l);
grid[i][j] = l;
}
}
return panel;
}
class Task extends SwingWorker<Void,Void> {
#Override
public Void doInBackground() {
updateGui(0, 0);
return null;
}
#Override
public void done() { }
//recursively set labels background
void updateGui(int i, int j) {
System.out.println(i+"-"+j);
//set random, background color
grid[i][j].setBackground(new Color((int)(Math.random() * 0x1000000)));
try {
Thread.sleep(500); //simulate long process
} catch (InterruptedException ex) { ex.printStackTrace();}
if((i==(SIZE-1))&&(j==(SIZE-1))) { return; }
if(i<(SIZE-1)) {
updateGui(++i, j);
}else {
i=0;
updateGui(i, ++j);
}
}
}
public static void main(String[] args) {
new RecursiveGuiUpdate();
}
}
If needed, you can override process(java.util.List) to get and process interim results.
If you need help with adapting such solution to your code, please post another question with mcve.
I am creating a 2D game which the zombie moves with WASD keys and is supposed to collide with the walls and not enter them, as well as collide with the brains and removes them. Every type of code I have used does not create collision. I am using a zombie sprite sheet i found on google as well as 2 backgroundless images for walls and brains.
After I figure out collision, I then then to implement a autorun sequence to where it bounces around like a screensaver and does the same thing just automatically until all brains are collected.
The EZ is just a library that is utilized by UH Manoa, that can be found here: EZ Graphics
Main
import java.awt.Color;
import java.io.FileReader;
import java.util.Scanner;
public class ZombieMain {
static EZImage[] walls = new EZImage[500];
static EZImage[] sideWalls = new EZImage[500];
static EZImage[] brains = new EZImage[50];
static int wallsCount = 0;
static int sideWallsCount = 0;
static int brainsCount = 0;
/*public static void addWall(EZImage wall) {
walls[wallsCount] = wall;
wallsCount++;
}
public static void addCoin(EZImage brain) {
brains[brainsCount] = brain;
brainsCount++;
}*/
/*public static void CollisingCoin(EZImage me) {
int x = me.getXCenter();
int y = me.getYCenter();
for (int i = 0; i < brainsCount; i++) {
if ((brains[i].isPointInElement(me.getXCenter() - 30, me.getYCenter() - 30))
|| (brains[i].isPointInElement(me.getXCenter() + 30, me.getYCenter() - 30))
|| (brains[i].isPointInElement(me.getXCenter() - 30, me.getYCenter() + 30))
|| (brains[i].isPointInElement(me.getXCenter() + 30, me.getYCenter() + 30))) {
brains[i].translateTo(-20, -20);
System.out.println("You ate a brain!");
}
}
}*/
public static void main(String[] args) throws java.io.IOException {
//initialize scanner
Scanner fScanner = new Scanner(new FileReader("boundaries.txt"));
int w = fScanner.nextInt();
int h = fScanner.nextInt();
String inputText = fScanner.nextLine();
//create backdrop
EZ.initialize(w*33,h*32);
EZ.setBackgroundColor(new Color(0, 0,0));
Zombie me = new Zombie("zombieSheet.png", 650, 450, 65, 63, 10);
//set reading parameters and establish results of case readings
int row = 0;
while(fScanner.hasNext()) {
inputText = fScanner.nextLine();
for (int column = 0; column < inputText.length(); column++){
char ch = inputText.charAt(column);
switch(ch){
case 'W':
walls[wallsCount] = EZ.addImage("barbwire.jpg", column*32, row*32);
wallsCount++;
break;
case 'M':
sideWalls[wallsCount] = EZ.addImage("barb.jpg", column*32, row*32);
wallsCount++;
break;
case 'B':
brains[brainsCount] = EZ.addImage("brains.png", column*32, row*32);
brainsCount++;
break;
default:
// Do nothing
break;
}
//printed count of walls, side walls, and brains
System.out.println("W = " + wallsCount);
System.out.println("M = " + sideWallsCount);
System.out.println("B = " + brainsCount);
}
row++;
}
fScanner.close();
while (true) {
// check if going to collide with wall
// we want to check this before we actually move
// otherwise, we get "stuck" in a situation where we can't move
// if no collision, we can move
/*if (EZInteraction.isKeyDown('a')) {
if (!isCollisingWall(me, -2, 0)) {
me.translateBy(-2, 0);
}
} else if (EZInteraction.isKeyDown('d')) {
if (!isCollisingWall(me, 2, 0)) {
me.translateBy(2, 0);
}
} else if (EZInteraction.isKeyDown('w')) {
if (!isCollisingWall(me, 0, -2)) {
me.translateBy(0, -2);
}
} else if (EZInteraction.isKeyDown('s')) {
if (!isCollisingWall(me, 0, 2)) {
me.translateBy(0, 2);
}
}*/
me.go();
EZ.refreshScreen();
}
}
}
Sprite
public class Zombie {
EZImage zombieSheet;
int x = 0; // Position of Sprite
int y = 0;
int zombieWidth; // Width of each sprite
int zombieHeight; // Height of each sprite
int direction = 0; // Direction character is walking in
int walkSequence = 0; // Walk sequence counter
int cycleSteps; // Number of steps before cycling to next animation step
int counter = 0; // Cycle counter
Zombie(String imgFile, int startX, int startY, int width, int height, int steps) {
x = startX; // position of the sprite character on the screen
y = startY;
zombieWidth = width; // Width of the sprite character
zombieHeight = height; // Height of the sprite character
cycleSteps = steps; // How many pixel movement steps to move before changing the sprite graphic
zombieSheet = EZ.addImage(imgFile, x, y);
setImagePosition();
}
private void setImagePosition() {
// Move the entire sprite sheet
zombieSheet.translateTo(x, y);
// Show only a portion of the sprite sheet.
// Portion is determined by setFocus which takes 4 parameters:
// The 1st two numbers is the top left hand corner of the focus region.
// The 2nd two numbers is the bottom right hand corner of the focus region.
zombieSheet.setFocus(walkSequence * zombieWidth, direction, walkSequence * zombieWidth + zombieWidth, direction + zombieHeight);
}
public void moveDown(int stepSize) {
y = y + stepSize;
direction = 0;
if ((counter % cycleSteps) == 0) {
walkSequence++;
if (walkSequence > 6)
walkSequence = 0;
}
counter++;
setImagePosition();
}
public void moveLeft(int stepSize) {
x = x - stepSize;
direction = zombieHeight * 2;
if ((counter % cycleSteps) == 0) {
walkSequence--;
if (walkSequence < 0)
walkSequence = 6;
}
counter++;
setImagePosition();
}
public void moveRight(int stepSize) {
x = x + stepSize;
direction = zombieHeight;
if ((counter % cycleSteps) == 0) {
walkSequence++;
if (walkSequence > 6)
walkSequence = 0;
}
counter++;
setImagePosition();
}
public void moveUp(int stepSize) {
y = y - stepSize;
direction = zombieHeight * 3;
if ((counter % cycleSteps) == 0) {
walkSequence--;
if (walkSequence < 0)
walkSequence = 6;
}
setImagePosition();
counter++;
}
// Keyboard controls for moving the character.
public void go() {
if (EZInteraction.isKeyDown('w')) {
moveUp(2);
} else if (EZInteraction.isKeyDown('a')) {
moveLeft(2);
} else if (EZInteraction.isKeyDown('s')) {
moveDown(2);
} else if (EZInteraction.isKeyDown('d')) {
moveRight(2);
}
}
public void translateBy(int i, int j) {
// TODO Auto-generated method stub
}
public int getXCenter() {
// TODO Auto-generated method stub
return x;
}
public int getYCenter() {
// TODO Auto-generated method stub
return y;
}
public int getWidth() {
// TODO Auto-generated method stub
return 0;
}
public int getHeight() {
// TODO Auto-generated method stub
return 0;
}
}
EZElement provides a getBounds property, which returns a java.awt.Shape object; why is this important? Because the Java 2D Graphics API already provides some hit detection.
From this, we then need to determine the player shape's intersection with any other shapes. To do this, we need to wrap both shapes in a Area and use it to make the final determinations.
Area meArea = new Area(me.getBounds());
Area checkArea = new Area(elementToCheck.getBounds());
checkArea(meArea);
if (!checkArea.isEmpty()) {
//... We have collision
}
Obviously, this should all be wrapped up in some kind of method to handle the core functionality, but you could have a helper method which simply took two EZElements and return true/false if the collide
For brevity and testing, I stripped back your example, but the basic idea should continue to work
import java.awt.Color;
import java.awt.Shape;
import java.awt.geom.Area;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Test {
private List<EZImage> brains = new ArrayList<>(25);
private Zombie me;
public static void main(String[] args) throws java.io.IOException {
new Test();
}
public Test() {
int w = 10;
int h = 10;
//create backdrop
EZ.initialize(w * 33, h * 32);
EZ.setBackgroundColor(new Color(0, 0, 0));
me = new Zombie("Zombie.png", 0, 0);
brains.add(EZ.addImage("Brains.png", (w * 33) / 2, (h * 32 / 2)));
while (true) {
detectCollision();
// check if going to collide with wall
// we want to check this before we actually move
// otherwise, we get "stuck" in a situation where we can't move
// if no collision, we can move
/*if (EZInteraction.isKeyDown('a')) {
if (!isCollisingWall(me, -2, 0)) {
me.translateBy(-2, 0);
}
} else if (EZInteraction.isKeyDown('d')) {
if (!isCollisingWall(me, 2, 0)) {
me.translateBy(2, 0);
}
} else if (EZInteraction.isKeyDown('w')) {
if (!isCollisingWall(me, 0, -2)) {
me.translateBy(0, -2);
}
} else if (EZInteraction.isKeyDown('s')) {
if (!isCollisingWall(me, 0, 2)) {
me.translateBy(0, 2);
}
}*/
me.go();
EZ.refreshScreen();
}
}
public boolean doesCollide(EZElement element, EZElement with) {
Area a = new Area(element.getBounds());
Area b = new Area(with.getBounds());
a.intersect(b);
return !a.isEmpty();
}
public void detectCollision() {
Iterator<EZImage> obstacles = brains.iterator();
while (obstacles.hasNext()) {
EZElement next = obstacles.next();
if (doesCollide(me.zombieSheet, next)) {
System.out.println("Me = " + me.getBounds().getBounds());
System.out.println("next = " + next.getBounds().getBounds());
EZ.removeEZElement(next);
obstacles.remove();
}
}
}
public class Zombie {
EZImage zombieSheet;
int x = 0; // Position of Sprite
int y = 0;
Zombie(String imgFile, int startX, int startY) {
x = startX; // position of the sprite character on the screen
y = startY;
zombieSheet = EZ.addImage(imgFile, x, y);
setImagePosition();
}
public Shape getBounds() {
return zombieSheet.getBounds();
}
private void setImagePosition() {
// Move the entire sprite sheet
zombieSheet.translateTo(x, y);
}
public void moveDown(int stepSize) {
y = y + stepSize;
setImagePosition();
}
public void moveLeft(int stepSize) {
x = x - stepSize;
setImagePosition();
}
public void moveRight(int stepSize) {
x = x + stepSize;
setImagePosition();
}
public void moveUp(int stepSize) {
y = y - stepSize;
setImagePosition();
}
// Keyboard controls for moving the character.
public void go() {
if (EZInteraction.isKeyDown('w')) {
moveUp(2);
} else if (EZInteraction.isKeyDown('a')) {
moveLeft(2);
} else if (EZInteraction.isKeyDown('s')) {
moveDown(2);
} else if (EZInteraction.isKeyDown('d')) {
moveRight(2);
}
}
}
}
I would recommend that you give each entity (and block/tile) a collision box, then test if a specific entity's bounding box collided with another entity's bounding box, then make it so that the entities can't move in that direction until there isn't a bounding box in a direction, if that made any since.
Do the same for testing for the brains, though I recommend making an ArrayList of brains, and removing specific ones if that brain had been touched.
I am developing a Tic Tac Toe version that uses different AI and ML techniques in java but I'm having some problem with slowing down the simulation.
Basically I would like to see the game as if it was played by two normal player while now as soon as I run it I get the final game state.
Here is my code:
Board.java
package com.nicolagheza.tictactoe;
public class Board {
// package access
Cell[][] cells; // 2D array of ROWS-by-COLS Cell instances
/** Constructor to initialize the game board */
public Board() {
cells = new Cell[GameMain.ROWS][GameMain.COLS]; // allocate the array
for (int row = 0; row < GameMain.ROWS; row++) {
for (int col = 0; col < GameMain.COLS; col++) {
cells[row][col] = new Cell(row, col); // allocate element of array
}
}
}
/** Initialize (or re-initialize) the game board */
public void init() {
for (int row = 0; row < GameMain.ROWS; row++) {
for (int col = 0; col < GameMain.COLS; col++) {
cells[row][col].clear(); // clear the cell content
}
}
}
/** Return true if it is a draw (i.e., no more EMPTY cell) */
public boolean isDraw() {
for (int row = 0; row < GameMain.ROWS; row++) {
for (int col = 0; col < GameMain.COLS; col++) {
if (cells[row][col].content == Seed.EMPTY) {
return false; // an empty seed found, not a draw, exit
}
}
}
return true; // no empty cell, it's a draw
}
/** Return true if the player with "seed" has won after placing at (seedRow, seedCol) */
public boolean hasWon(Seed seed, int seedRow, int seedCol) {
return (cells[seedRow][0].content == seed // 3-in-the-row
&& cells[seedRow][1].content == seed
&& cells[seedRow][2].content == seed
|| cells[0][seedCol].content == seed // 3-in-the-column
&& cells[1][seedCol].content == seed
&& cells[2][seedCol].content == seed
|| seedRow == seedCol // 3-in-the-diagonal
&& cells[0][0].content == seed
&& cells[1][1].content == seed
&& cells[2][2].content == seed
|| seedRow + seedCol == 2 // 3-in-the-opposite-diagonal
&& cells[0][2].content == seed
&& cells[1][1].content == seed
&& cells[2][0].content == seed);
}
}
GameState.java
package com.nicolagheza.tictactoe;
public enum GameState {
PLAYING, DRAW, CROSS_WON, NOUGHT_WON
}
GameMain.java
package com.nicolagheza.tictactoe;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class GameMain extends JPanel{
// Named-constants for the game board
public static final int ROWS = 3; // ROWS by COLS cells
public static final int COLS = 3;
public static final String TITLE = "Tic Tac Toe";
// Name-constants for the various dimensions used for graphics drawing
public static final int CELL_SIZE = 100; // cell width and height (square)
public static final int CANVAS_WIDTH = CELL_SIZE * COLS; // the drawing canvas
public static final int CANVAS_HEIGHT = CELL_SIZE * ROWS;
public static final int GRID_WIDTH = 8; // Grid-line's width
public static final int GRID_WIDHT_HALF = GRID_WIDTH / 2; // Grid-line's half-width
// Symbols (cross/nought) are displayed inside a cell, with padding from border
public static final int CELL_PADDING = CELL_SIZE / 6;
public static final int SYMBOL_SIZE = CELL_SIZE - CELL_PADDING * 2;
public static final int SYMBOL_STROKE_WIDTH = 8; // pen's stroke width
private Board board; // the game board
private BoardView boardView;
private AIPlayer aiPlayer1;
private AIPlayer aiPlayer2;
private GameState currentState; // the current state of the game
private Seed currentPlayer; // the current player
private JLabel statusBar; // for displaying status message
/** Constructor to setup the UI and game components */
public GameMain() {
// This JPanel fires MouseEvent
this.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
int mouseX = e.getX();
int mouseY = e.getY();
// Get the row and column clicked
int rowSelected = mouseY / CELL_SIZE;
int colSelected = mouseX / CELL_SIZE;
if (currentState == GameState.PLAYING) {
if (rowSelected >= 0 && rowSelected < ROWS
&& colSelected >= 0 && colSelected < COLS
&& board.cells[rowSelected][colSelected].content == Seed.EMPTY) {
board.cells[rowSelected][colSelected].content = currentPlayer; // move
updateGame(currentPlayer, rowSelected, colSelected); // update currentState
}
} else { // game over
initGame();
}
// Refresh the drawing canvas
repaint();
currentPlayer = (currentPlayer == Seed.CROSS) ? Seed.NOUGHT : Seed.CROSS;
}
});
// Setup the status bar (JLabel) to display status message
statusBar = new JLabel(" ");
statusBar.setFont(new Font(Font.DIALOG_INPUT, Font.BOLD, 14));
statusBar.setBorder(BorderFactory.createEmptyBorder(2, 5, 4, 5));
statusBar.setOpaque(true);
statusBar.setBackground(Color.LIGHT_GRAY);
setLayout(new BorderLayout());
add(statusBar, BorderLayout.SOUTH);
setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT + 30));
board = new Board(); // allocate the game-board
boardView = new BoardView(board.cells);
initGame();
initAI();
}
private void initAI() {
aiPlayer1 = new AIPlayerRuleBased(board);
aiPlayer1.setSeed(Seed.CROSS);
aiPlayer2 = new AIPlayerTableLookup(board);
aiPlayer2.setSeed(Seed.NOUGHT);
}
/** Initialize the game-board contents and the current-state */
public void initGame() {
for (int row = 0; row < ROWS; ++row) {
for (int col = 0; col < COLS; ++col) {
board.cells[row][col].content = Seed.EMPTY; // all cells empty
}
}
currentState = GameState.PLAYING; // ready to play
currentPlayer = Seed.CROSS; // cross plays first
}
public void makeAIMove(AIPlayer player) {
int[] move = player.move();
if (move != null) {
System.out.println("Player " + player.mySeed + " row: " + move[0] + " col: " + move[1]);
board.cells[move[0]][move[1]].content = player.mySeed;
updateGame(currentPlayer, move[0], move[1]);
repaint();
currentPlayer = (currentPlayer == Seed.CROSS) ? Seed.NOUGHT : Seed.CROSS;
}
}
/** Update the currentState after the player with "theSeed" has placed on (row, col) */
public void updateGame(Seed theSeed, int row, int col) {
if(board.hasWon(theSeed, row, col)) { // check for win
currentState = (theSeed == Seed.CROSS) ? GameState.CROSS_WON : GameState.NOUGHT_WON;
} else if (board.isDraw()) { // check for draw
currentState = GameState.DRAW;
}
// Otherwise, no change to current state (PLAYING).
}
/** Custom painting codes on this JPanel */
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g); // fill background
setBackground(Color.WHITE); // set its background color
boardView.paint(g); // ask the game board to paint itself
// Print status-ba message
if (currentState == GameState.PLAYING) {
statusBar.setForeground(Color.BLACK);
if (currentPlayer == Seed.CROSS) {
statusBar.setText("X's Turn");
} else {
statusBar.setText("O's Turn");
}
} else if (currentState == GameState.DRAW) {
statusBar.setForeground(Color.RED);
statusBar.setText("It's a Draw! Click to play again.");
} else if (currentState == GameState.CROSS_WON) {
statusBar.setForeground(Color.RED);
statusBar.setText("'X' Won! Click to play again.");
} else if (currentState == GameState.NOUGHT_WON) {
statusBar.setForeground(Color.RED);
statusBar.setText("'O' Won! Click to play again.");
}
}
public void getNextState() {
if (currentPlayer == aiPlayer1.mySeed) {
makeAIMove(aiPlayer1);
}
if (currentPlayer == aiPlayer2.mySeed) {
makeAIMove(aiPlayer2);
}
}
/** The entry "main" method */
public static void main(String args[]) {
// Run GUI construction codes in Event-Dispatching thread for thread safety
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame(TITLE);
// Set the content-pane of the JFrame to an instance of main JPanel
GameMain game = new GameMain();
frame.setContentPane(game);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
while (game.currentState == GameState.PLAYING) {
game.getNextState();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
}
}
AIPlayer.java
package com.nicolagheza.tictactoe;
public abstract class AIPlayer {
protected int ROWS = GameMain.ROWS; // number of rows
protected int COLS = GameMain.COLS; // number of cols
protected Cell[][] cells; // the board's ROWs-by-COLs array of Cells
protected Seed mySeed; // computer's seed
protected Seed oppSeed; // opponent's seed
/** Constructor with reference to game board */
public AIPlayer(Board board) {
cells = board.cells;
}
/** Set/change the seed used by computer and opponent */
public void setSeed(Seed seed) {
this.mySeed = seed;
oppSeed = (mySeed == Seed.CROSS) ? Seed.NOUGHT : Seed.CROSS;
}
public abstract int[] move();
}
AIPlayerRuleBased.java
package com.nicolagheza.tictactoe;
import java.util.ArrayList;
import java.util.List;
public class AIPlayerRuleBased extends AIPlayer {
/**
* Constructor with reference to game board
*
* #param board
*/
public AIPlayerRuleBased(Board board) {
super(board);
}
private List<int[]> generatePossibleMoves() {
List<int[]> nextMoves = new ArrayList<int[]>();
for (int row = 0; row < ROWS; row++) {
for (int col = 0; col < COLS; col++) {
if (cells[row][col].content == Seed.EMPTY)
nextMoves.add(new int[] {row, col});
}
}
return nextMoves;
}
#Override
public int[] move() {
List<int[]> nextPossibleMoves = generatePossibleMoves();
// Rule 1: If I have a winning move, take it.
for (int[] nextMove : nextPossibleMoves) {
// Try this move
cells[nextMove[0]][nextMove[1]].content = mySeed;
if (hasWon(mySeed)) {
cells[nextMove[0]][nextMove[1]].content = Seed.EMPTY; // Undo move
return nextMove;
}
cells[nextMove[0]][nextMove[1]].content = Seed.EMPTY; // Undo move
}
// Rule 2: If the opponent has a winning move, block it
for (int[] nextMove: nextPossibleMoves) {
// Try this move
cells[nextMove[0]][nextMove[1]].content = oppSeed;
if (hasWon(oppSeed)) {
cells[nextMove[0]][nextMove[1]].content = Seed.EMPTY; // Undo move
return nextMove;
}
cells[nextMove[0]][nextMove[1]].content = Seed.EMPTY; // Undo move
}
// Moves {row, col} in order of preferences. {0,0} at top-left corner
int[][] preferredMoves = {
{1,1}, {0,0}, {0,2}, {2,0}, {2,2},
{0,1}, {1,0}, {1,2}, {2,1}};
for (int[] move : preferredMoves) {
if (cells[move[0]][move[1]].content == Seed.EMPTY) {
return move;
}
}
assert false : "No empty cell?!";
return null;
}
private int[] winningPatterns = {
0b111000000, 0b000111000, 0b000000111, // rows
0b100100100, 0b010010010, 0b001001001, // cols
0b100010001, 0b001010100 // diagonals
};
/** Returns true if thePlayer wins */
private boolean hasWon(Seed thePlayer) {
int pattern = 0b000000000; // 9-bit pattern for the 9 cells
for (int row = 0; row < ROWS; ++row) {
for (int col = 0; col < COLS; ++col) {
if (cells[row][col].content == thePlayer) {
pattern |= (1 << (row * COLS + col));
}
}
}
for (int winningPattern : winningPatterns) {
if ((pattern & winningPattern) == winningPattern) return true;
}
return false;
}
}
I tried with thread.sleep but it wont work.
(Posted on behalf of the OP).
I solved my issue using Javax.swing.Timer. Here is how I did it in case someone else is interested:
new javax.swing.Timer(TIMER_DELAY, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (game.currentState != GameState.PLAYING)
return;
game.getNextState();
}
}).start();
I'm a newbie in programming and I need some lights and help.I'm developing a game in which two players have to play with tokens (say red and blue) by placing them in cells (75x75 grid). The goal is to "capture" opponent's tokens by surrounding them. (See the image, which is the actual game output, the surrounding is drawn by hand)
To do so, I need to make the tokens "listen" to neighborhood, meaning other cells in the grid. A token has to check for itself in the grid(what is its position in the grid) and check of there is another token close to it, checks it color (blue or red) then,a in certain conditions, trigger the capturing mechanism
[![Game board with tokens][1]][1]
What I have done, technically:
Created the grid ( Grid/board is a 2 dimensional array of Token objects.)
The token (which is an enumeration: EMPTY, BLUE_TOKEN, RED_TOKEN.
A currentPlayer is also a Token .
When it's the user turn, they select an empty cell at which they point. They place the currentPlayer into the grid/board at cell rowSelected, colSelected then repaint the canvas with the newly added cell in the grid/board.
Now i'm stuck on how to make the tokens listen the next cell surrounding them in order to see if there is an opponent or an ally.
PS: I've posted the same here, got downgraded because i didn't respect rules (ignorant I was)
Here is my code:
import javax.swing.JFrame;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
public class Phagocyte extends JFrame {
public static final int ROWS = 75;
public static final int COLS = 75;
public static final int CELL_SIZE = 18;
public static final int CANVAS_WIDTH = CELL_SIZE * COLS;
public static final int CANVAS_HEIGHT = CELL_SIZE * ROWS;
public static final int GRID_WIDTH = 8;
public static final int GRID_WIDHT_HALF = GRID_WIDTH / 2;
// Symbols (Blue token/Red Token) are displayed inside a cell with padding from borders
public static final int CELL_PADDING = CELL_SIZE / 5;
public static final int SYMBOL_SIZE = CELL_SIZE - CELL_PADDING * 2;
public static final int SYMBOL_STROKE_WIDTH = 3;
//This represent the various states of the game
public enum GameState {
PLAYING, DRAW, BLUE_TOKEN_WON, RED_TOKEN_WON //Haven't created a scenario yet
}
private GameState currentState; // The current state of the game
//This represent the Tokens and cell contents
public enum Token {
EMPTY, BLUE_TOKEN, RED_TOKEN
}
private Token currentPlayer; // The current player (whether red or blue)
private Token[][] board ; // This is the game board of cells (ROWS by COLS)
private DrawCanvas canvas;
private JLabel statusBar;
/**The components of the the game and the GUI are setup here */
public Phagocyte() {
canvas = new DrawCanvas();
canvas.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));
canvas.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
int mouseX = e.getX();
int mouseY = e.getY();
// Here to get the row and column that is clicked
int rowSelected = mouseY / CELL_SIZE;
int colSelected = mouseX / CELL_SIZE;
if (currentState == GameState.PLAYING) {
if (rowSelected >= 0 && rowSelected < ROWS && colSelected >= 0
&& colSelected < COLS && board[rowSelected][colSelected] == TOKEN.EMPTY) {
board[rowSelected][colSelected] = currentPlayer;
updateGame(currentPlayer, rowSelected, colSelected);
// Here's to switch player
currentPlayer = (currentPlayer == Token.BLUE_TOKEN) ? Token.RED_TOKEN : Token.BLUE_TOKEN;
}
} else {
initGame();
}
// Drawing canvas are refresh
repaint();
}
});
// Setup the status bar (JLabel) to display status message
statusBar = new JLabel(" ");
statusBar.setFont(new Font(Font.DIALOG_INPUT, Font.BOLD, 15));
statusBar.setBorder(BorderFactory.createEmptyBorder(2, 5, 4, 5));
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
cp.add(canvas, BorderLayout.CENTER);
cp.add(statusBar, BorderLayout.NORTH);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setTitle("Phagocyte by esQmo");
setVisible(true);
board = new Token[ROWS][COLS];
initGame();
}
/** The game board contents and the status are initialised here*/
public void initGame() {
for (int row = 0; row < ROWS; ++row) {
for (int col = 0; col < COLS; ++col) {
board[row][col] = Token.EMPTY;
}
}
currentState = GameState.PLAYING;
currentPlayer = Token.RED_TOKEN;
}
/*this part need some improvements since hasWon and isDraw are not yet definied*/
public void updateGame(Token theToken, int rowSelected, int colSelected) {
if (hasWon(theToken, rowSelected, colSelected)) {
currentState = (theToken == Token.RED_TOKEN) ? GameState.RED_TOKEN_WON : GameState.BLUE_TOKEN_WON;
} else if (isDraw()) {
currentState = GameState.DRAW;
}
}
/** This is supposed to return true if it is a draw (no more empty cell for exemple) */
/** need to be improved **/
/* public boolean isDraw() {
for (int row = 0; row < ROWS; ++row) {
for (int col = 0; col < COLS; ++col) {
if (board[row][col] == Token.EMPTY) {
return false;
}
}
}
return true;
} */
/**Need to implement a Win scenario as well **/
public boolean hasWon(Token theToken, int rowSelected, int colSelected){
//
}
class DrawCanvas extends JPanel {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
setBackground(Color.WHITE);
//Grid lines
g.setColor(Color.LIGHT_GRAY);
for (int row = 1; row < ROWS; ++row) {
g.fillRoundRect(0, CELL_SIZE * row - GRID_WIDHT_HALF,
CANVAS_WIDTH-1, GRID_WIDTH, GRID_WIDTH, GRID_WIDTH);
}
for (int col = 1; col < COLS; ++col) {
g.fillRoundRect(CELL_SIZE * col - GRID_WIDHT_HALF, 0,
GRID_WIDTH, CANVAS_HEIGHT-1, GRID_WIDTH, GRID_WIDTH);
}
// This draw the Tokkens in the cells if they are not empty
Graphics2D g2d = (Graphics2D)g;
g2d.setStroke(new BasicStroke(SYMBOL_STROKE_WIDTH, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND));
for (int row = 0; row < ROWS; ++row) {
for (int col = 0; col < COLS; ++col) {
int x1 = col * CELL_SIZE + CELL_PADDING;
int y1 = row * CELL_SIZE + CELL_PADDING;
if (board[row][col] == Token.RED_TOKEN) {
int x2 = (col + 1) * CELL_SIZE - CELL_PADDING;
int y2 = (row + 1) * CELL_SIZE - CELL_PADDING;
g2d.setColor(Color.RED);
g2d.drawOval(x1, y1, SYMBOL_SIZE, SYMBOL_SIZE);
g2d.fillOval(x1, y1, SYMBOL_SIZE, SYMBOL_SIZE);
} else
if (board[row][col] == Token.BLUE_TOKEN) {
g2d.setColor(Color.BLUE);
g2d.drawOval(x1, y1, SYMBOL_SIZE, SYMBOL_SIZE);
g2d.fillOval(x2, y1, SYMBOL_SIZE, SYMBOL_SIZE);
}
}
}
// Print status-bar message
if (currentState == GameState.PLAYING) {
statusBar.setForeground(Color.BLACK);
if (currentPlayer == Token.RED_TOKEN) {
statusBar.setText("Red, it's your move");
statusBar.setForeground(Color.RED);
} else {
statusBar.setText("Blue, it's your move");
statusBar.setForeground(Color.BLUE);
statusBar.addMouseMotionListener(null);
}
} else if (currentState == GameState.DRAW) {
statusBar.setForeground(Color.RED);
statusBar.setText("It's a draw!");
} else if (currentState == GameState.RED_TOKEN_WON) {
statusBar.setForeground(Color.RED);
statusBar.setText("Red wow!");
} else if (currentState == GameState.BLUE_TOKEN_WON) {
statusBar.setForeground(Color.BLUE);
statusBar.setText("Blue Won! ");
}
}
}
public static void main(String[] args){
SwingUtilities.invokeLater(() -> {
Phagocyte phagocyte = new Phagocyte();
});
}
}
I'm unable to post image, due to my reputation :'(