I recently started a minesweeper project in Java. I planned to first develop the game mechanics while using the Console as output, but now I want to develop a GUI using the code I have. The problem is I have very little experience in GUI design.
One of my previous experiences in JavaFX was designing a Chess game. For the chess board, I created a 2D ImageView grid in the fxml. The images each ImageView would display would change as the pieces moved.
For my minesweeper game, I've implemented a variably sized board so I can't just create another ImageView 2D array again. I'm wondering if there is something in JavaFX that I don't know about that could help me approach this issue.
I would just like to know a good way to display the board of tiles itself. All the other GUI aspects of the game, I could figure out later.
The best thing that I could think of right now with what I know about JavaFX is that I can create a 2D ImageView array with the max size of the board. I could set all the height and width of the unused tiles to 0. However, even though this is the best idea that I have, I feel that it is a very bad way to approach the problem.
Here are the fields of the Board object
public class Board {
public final static int EASY_SIZE = 8;
public final static int MED_SIZE = 12;
public final static int HARD_SIZE = 15;
public final static int MIN_SIZE = 5;
public final static int MAX_SIZE = 26;
public int size;
protected int mine_count;
public boolean ended = false;
protected Tile[][] board;
The board output to the Console. The board.display() function:
public void display() {
System.out.println();
System.out.println(mine_count + " remaining mines");
for (int i = 0; i< 10 && i < size; i++) {
System.out.print(" _");
}
for(int i = 10; i < size; i++) {
System.out.print(" __");
}
System.out.println();
// For each row
for (int r = 0; r < size; r++) {
// For each col
for (int c = 0; c < size && c < 10; c++) {
// if the tile is flagged
if (board[r][c].flag == true) {
System.out.print("|F");
}
// else if the tile has not been clicked
else if (board[r][c].clicked == false) {
System.out.print("|#");
}
// else if mine clicked
else if (board[r][c].mine == true) {
System.out.print("|*");
}
// else if zero adj_mines
else if (board[r][c].adj_mines == 0) {
System.out.print("|_");
}
// else show number
else {
System.out.print("|" + board[r][c].adj_mines);
}
}
for (int c = 10; c < size; c++) {
// if the tile is flagged
if (board[r][c].flag == true) {
System.out.print("|F_");
}
// else if the tile has not been clicked
else if (board[r][c].clicked == false) {
System.out.print("|#_");
}
// else if mine clicked
else if (board[r][c].mine == true) {
System.out.print("|*_");
}
// else if zero adj_mines
else if (board[r][c].adj_mines == 0) {
System.out.print("|__");
}
// else show number
else {
System.out.print("|" + board[r][c].adj_mines + "_");
}
}
System.out.println("| " + r);
}
for (int i = 0; i < size; i++) {
System.out.print(" " + i);
}
System.out.println();
}
...
Since my question is regarding what sort of JavaFX layout I could use to approach the problem, I started looking more into my options. In my previous experiences in JavaFX, I designed all of the GUI elements in an FXML file, but I realize that since I'm not certain about the size of the board on bootup, I should not be doing this.
Instead, I'm planning to use a GridPane layout and I will change my Tile class to extend Button to set an EventHandler on left and right mouse clicks. If anyone has insight on why this might not work, I would love to hear it, but otherwise, I'll report back if this works on not!
Related
I'm making battleships and I've currently come across an issue where my ships overlap. I've tried to incorporate an if statement that will judge whether it can be placed. Here's an example of me placing two ships of length 3 down.
public static void PlaceCruiser(String[][] board) {
ThreadLocalRandom random = ThreadLocalRandom.current();
int timesplaced = 0;
int size = 3;
while (timesplaced < 2) {
int randomcruisercheck =(int)(Math.random()*2);
if (randomcruisercheck == 0) {
int column = random.nextInt(0,9);
int row = random.nextInt(0,7);
if (row + 2 < 11 && board[row][column] == "." && board[row + 1][column] == "." && board[row + 2][column] == ".") {
for(int i = 0; i<size; i++)
{
board[row+i][column] = "#";
}
System.out.println(board[row][column]);
}
timesplaced++;
}
else if (randomcruisercheck == 1) {
int column = random.nextInt(0,9);
int row = random.nextInt(0,7);
if (column + 2 < 11 && board[row][column] == "." && board[row][column + 1] == "." && board[row][column + 2] == ".") {
for (int i = 0; i<size; i++)
{
board[row][column + i] = "#";
}
System.out.println(board[row][column]);
}
timesplaced++;
}
}
}
Basically, I use "#" to represent a ship in a 10x10 2D array of ".". I feel like the if statement about if the row or column plus 1 then plus 2 is a dot i.e a free space, a ship will be generated but this does not seem to be the case. Can anyone help me out?
Your code works well, you only need to take care of the indexes and initialize the board:
public class Main {
public static String[][] board;
public static void main(String[] args) {
PlaceCruiser pc = new PlaceCruiser();
board = new String[10][10];
// Initialize the board
for (int i=0;i<10;i++) {
for (int j=0;j<10;j++) {
board[i][j]=".";
}
}
pc.placeCruiser(board);
// Show thew board
for (int i=0;i<10;i++) {
for (int j=0;j<10;j++) {
System.out.print(board[i][j]);
}
System.out.println();
}
}
}
Result:
..........
..###.....
..........
..........
....#.....
....#.....
....#.....
..........
..........
..........
Also check that your initial position is not already "#".
Stylistical remarks:
if you use ThreadLocalRandom for generating position, you should also use it for other randomness (in other words: (int)(Math.random()*2) could rather be random.nextBoolean(), because actually a boolean could decide if ship should be horizontal or vertical)
nextInt(0,x) is just a longer variant of nextInt(x).
Actual bugs:
due to a presumably copy-paste issue, column (0-"9") and row (0-"7") are generated in the same way in both cases, making it possible to index out of the array when placing a vertical ship
which you seem to have noticed, but fixed it with that row + 2 < 11 check which has two problems in itself:
when row+2 ends up being 10 (which is <11), that is an invalid index (valid indices are 0...9)
as row stays between 0 and "7", there will not be horizontal ships in the last few rows
nextInt(a,b) generates numbers a...b-1, so it will not generate b itself
as the other answer points out string comparison with == generally and usually does not work, use equals()
Generally I would suggest having a single check+placement function, which can deal with an entire rectangle (given position+size). Also, I switched to array of characters, that simplifies both comparisons and printing.
boolean tryPlace(int x,int y,int width,int height) {
for(int i=0;i<height;i++) {
for(int j=0;j<width;j++) {
if(board[y+i][x+j]!='.') {
return false; // ship can not be placed
}
}
}
// if we reach here, ship can be placed
for(int i=0;i<height;i++) {
for(int j=0;j<width;j++) {
board[y+i][x+j]='#';
}
}
return true; // ship placed successfully
}
This routine could be called to place a pair of 3-long ships this way:
board=new char[10][10];
for(int i=0;i<10;i++)
for(int j=0;j<10;j++)
board[i][j]='.';
int size=3;
int amount=2;
while(amount>0) {
if(random.nextBoolean()) {
// horizontal
if(tryPlace(random.nextInt(10-size+1),random.nextInt(10),size,1)){
amount--; // one placed
}
} else {
// vertical
if(tryPlace(random.nextInt(10),random.nextInt(10-size+1),1,size)){
amount--; // one placed
}
}
}
// and a 4x2 mothership
while(!(random.nextBoolean()
?tryPlace(random.nextInt(7),random.nextInt(9),4,2)
:tryPlace(random.nextInt(9),random.nextInt(7),2,4)
));
for(int i=0;i<10;i++)
System.out.println(board[i]); // char[] has special overload for print/ln()
Test: https://ideone.com/DjYqjB
However, when I was a kid we had a rule that ships could not match, there had to be empty space (or a border of the board) around them. If you need that, tryPlace() could check a larger block, and put the ship into the middle of it. Also, a usual trick of implementing board games is that you can keep a larger array in the memory than what you will actually display. So instead of fighting with "check if field is empty or it is outside the board", it is simpler to have a 12x12 board, and place ships into the middle 10x10 portion of it:
boolean tryPlaceWithBorder(int x,int y,int width,int height) {
for(int i=0;i<height;i++)
for(int j=0;j<width;j++)
if(board[y+i][x+j]!='.')
return false; // ship can not be placed
// if we reach here, ship can be placed
for(int i=1;i<height-1;i++)
for(int j=1;j<width-1;j++)
board[y+i][x+j]='#';
return true; // ship placed successfully
}
and modified usage:
board=new char[12][12];
for(int i=0;i<12;i++)
for(int j=0;j<12;j++)
board[i][j]='.';
int size=3;
int amount=2;
while(amount>0) {
if(random.nextBoolean()) {
// horizontal
if(tryPlaceWithBorder(random.nextInt(12-size-1),random.nextInt(10),size+2,3))
amount--; // one placed
} else {
// vertical
if(tryPlaceWithBorder(random.nextInt(10),random.nextInt(12-size-1),3,size+2)){
amount--; // one placed
}
}
}
// and a 4x2 mothership
while(!(random.nextBoolean()
?tryPlaceWithBorder(random.nextInt(7),random.nextInt(9),6,4)
:tryPlaceWithBorder(random.nextInt(9),random.nextInt(7),4,6)
));
for(int i=1;i<11;i++)
System.out.println(String.valueOf(board[i],1,10));
Test: https://ideone.com/LXAD7T
I am teaching myself java using the cs106a course from Stanford.
Currently I am on chapter 10 of the book "The Art and Science of Java".
The problem is to write a 3x3 Magic Square.
The exercise:
You have to write a 3x3 array
Each side of the array(Magic Square) has to equal 15
The problem:
The program I wrote works, the assignment is complete, this question is for self learning. As a beginner I would like to improve the method SumOfSides() and make it smaller and more efficient. I tried iterating the array in this method but still have issues. Is there a way to make it more efficient?
public void run() {
//set the font
setFont("Helvetica-40");
//fill the array
fillArray();
//sum up all sides
SumOfSides();
//check if all of the sides in the magic square array equal 15:
checkSides(mSqr);
//I used this for debugging purposes only:
//showSides();
}
//for debugging purposes:
public void showSides() {
println(sumRight0);
println(sumRight1);
println(sumRight2);
println(sumBottom0);
println(sumBottom1);
println(sumBottom2);
println(sumDiagonalUp);
println(sumDiagonalDown);
}
public void SumOfSides() {
sumRight0 = mSqr[0][0] + mSqr[0][1] + mSqr[0][2];
sumRight1 = mSqr[1][0] + mSqr[1][1] + mSqr[1][2];
sumRight2 = mSqr[2][0] + mSqr[2][1] + mSqr[2][2];
sumBottom0 =mSqr[0][0] + mSqr[1][0] + mSqr[2][0];
sumBottom1 =mSqr[0][1] + mSqr[1][1] + mSqr[2][1];
sumBottom2 =mSqr[0][2] + mSqr[1][2] + mSqr[2][2];
sumDiagonalUp = mSqr[2][0] + mSqr[1][1]+ mSqr[0][2];
sumDiagonalDown = mSqr[0][0] + mSqr[1][1] + mSqr[2][2];
}
/*This predicate method checks if the sides
of the array add up to 15: */
public boolean checkSides(int[][] myArray) {
if (sumRight0 ==15 && sumRight1 ==15&& sumRight2==15 && sumBottom0==15&& sumBottom1==15&&
sumBottom2==15&& sumDiagonalUp==15&&sumDiagonalDown==15) {
println("True, this is a Magic Square");
return true;
} else {
println("False, the sides do not equal 15");
return false;
}
}
public void fillArray() {
int num =0;
for(int row=0; row <3; row++) {
for (int col=0; col<3; col++) {
num=readInt("");
mSqr[row][col]=num;
}
}
/*Test array values here to see
* if they were entered correctly.
*/
//println(mSqr[1][2]); //should be 6
//println(mSqr[2][0]); //should be 7
}
//instance variables:
int[][] mSqr= new int[3][3];
List<List<Integer>> new1 = new ArrayList<>();
private int sumRight0;
private int sumRight1;
private int sumRight2;
private int sumBottom0;
private int sumBottom1;
private int sumBottom2;
private int sumDiagonalUp;
private int sumDiagonalDown;
}
Perhaps the only thing is readability. You could take the values and move them into more readable variables:
int topLeft = mSqr[0][0];
int topMid = mSqr[0][1];
...
int sumLeft = topLeft + midLeft + bottomLeft;
int sumRight = topRight = midRight + bottomRight;
...
To address your concern of making it smaller, I would argue that converting the sums into loops, as you mentioned, is certainly not worth it in the case that you are doing 6 sums of 3 values each. Furthermore, each term of each sum is common to either one or two other sums, which does not provide much overlap. If you were performing larger sums (larger in number of terms in the sum, not the total value), then perhaps it would be worth it on a readability/SLOC argument.
Suppose you did want to do a loop still though, you could do something like
sumLeft = 0;
sumRight = 0;
sumTop = 0;
sumBottom = 0;
sumDiagonalUp = 0;
sumDiagonalDown = 0;
for(int i = 0; i < mSqr.length; i++) {
for(int j = 0; j < mSqr[i].length; j++) {
if (i == 0) {
sumLeft += mSqr[i][j];
}
if (i == mSqr.length - 1) {
sumRight += mSqr[i][j];
}
if (j == 0) {
sumTop += mSqr[i][j];
}
if (j == mSqr[i].length) {
sumBottom += mSqr[i][j];
}
if (i == j) {
sumDiagonalDown += mSqr[i][j];
}
if (i + j == mSqr.length - 1) {
sumDiagonalUp += mSqr[i][j];
}
}
}
The loops only provide benefit on large magic squares.
Also, I am confused by your description contrasted with your implementation. It seems you are summing each row and column, and the two diagonals of the square, as opposed to the 4 sides and the diagonals.
I need help rearranging a program I have to use classes constructors methods...so on. It's a game of battleships. I'm not too clear on what classes and constructors and methods I'd need.
import java.util.Scanner;
public class BattleshipLab {
Scanner input = new Scanner(System.in);
public static final boolean DEBUG = false;
public static void breakln() {
System.out.println("_____________________________________");
System.out.println("");
}
public static void createBoard(String[][] board) {
for (String[] board1 : board) {
for (int c = 0; c < board[0].length; c++) {
board1[c] = "~";
}
}
}
public static void showBoard(String[][] board) {
breakln();
for (String[] board1 : board) {
if (DEBUG == true) {
for (int c = 0; c < board[0].length; c++) {
System.out.print(" " + board1[c]);
}
System.out.println("");
} else {
for (int c = 0; c < board[0].length; c++) {
if (board1[c].equals("S")) {
System.out.print(" " + "~");
} else {
System.out.print(" " + board1[c]);
}
}
System.out.println("");
}
}
breakln();
}
public static void createShip(String[][] board, int size) {
if (Math.random() < 0.5) {
int col = (int) (Math.random() * 5);
int row = (int) (Math.random() * 7);
for (int i = 0; i < size; i++) {
board[row][col + i] = "S";
}
} else {
int col = (int) (Math.random() * 7);
int row = (int) (Math.random() * 5);
for (int i = 0; i < size; i++) {
board[row + i][col] = "S";
}
}
}
public static int userFire(String[][] board, int hits, int torps) {
Scanner input = new Scanner(System.in);
int row, col;
System.out.println("You have: " + torps + " torpedos left!");
System.out.println("Select a row to fire in: ");
row = input.nextInt();
while (row > 8 || row < 1) // Error checking for row
{
System.out.println("Enter a valid row (1 -> 8)");
row = input.nextInt();
}
System.out.println("Select a column to fire in: ");
col = input.nextInt();
while (col > 8 || col < 1) // Error checking for column
{
System.out.println("Enter a valid col (1 -> 8)");
col = input.nextInt();
}
if (board[row - 1][col - 1].equals("S")) {
hits++;
System.out.println("~~~~~~~ HIT ~~~~~~~");
board[row - 1][col - 1] = "!";
} else {
System.out.println("~~~~~~~ MISS ~~~~~~~");
board[row - 1][col - 1] = "M";
}
return hits;
}
public static void finall(int hits, int torps) {
if (hits < 4) {
System.out.println("Sorry, but you lost because you didn't sink the ship.");
}
if (torps < 1) {
System.out.println("You have lost all your torpedos");
} else if (hits >= 4) {
System.out.println("You have beaten the game battleship, Thanks for playing!");
}
System.out.println("Good game, well played!");
}
public static void main(String[] arg) {
String[][] board = new String[8][8];
createBoard(board);
createShip(board, 4);
int torps = 15;
int hits = 0;
/// Starting real stuff
while (torps > 0 && hits < 4) {
showBoard(board);
hits = userFire(board, hits, torps);
torps--;
}
finall(hits, torps);
}
}
Welcome to Stack Overflow, Noah!
First a couple of remarks:
This kind of question is usually considered as unclear what you are asking and/or too broad, so expect downvotes and a closing of your question. You should really read up on how to ask a good question.
If you are struggling with your code design, it is often a good idea to take a pen and some paper and draft a rough outline there. For your specific task of turning an existing program into OOP, a class diagram would be a really good starting point.
Now that that's out of the way, I'll give you a bit of a notch in the right direction, seeing that you are new to Stack Overflow.
In your code, we can find two interesting methods:
createBoard
createShip
Notice how they both start with create? This is a very, very strong hint that it would probably a good idea to write classes for those two objects at least:
Board
Ship
When it comes to methods, your current code gives us some pointers again. While I'm not sure about firing torpedos and the like, it looks as if you might want to at least turn showBoard() into a method show of your Board class.
Now, what other classes and methods you need really depends on the rules of that game (I'm not familiar with it), what features you want it to have and, of course, how you want to implement it. Possible other classes could be Torpedo but maybe those will just be a primitive type member of your Ship class. Another candidate could be something like Tile, the basic building block of your Board?
You'll have to take it from here, really. The Java Lessons on Classes and Objects will be useful.
I hope this gets you started.
Okay so when you're doing OOP, before you begin to code you should analyze your problem and break it down. What parts make up the game, what are their functions and attributes, how do these parts interact with each other? I'll help you start, consider making board and cell classes.
In OOP you like Java, you need to create a new instance of the object(class) that you define. So let's say you created a class:
class Shape {
int length, width;
String color ="";
}
To use Shape in main you create a new instance of it
class Driver {
public static void main (String [] args){
//First you specify the type as your object name, then name the variable and lastly, you do new object.
Shape myShape = new Shape ();
}
}
Okay, I am building a game in my free time for fun. This is not for work or school. I graduated. What I didn't do is graduate in game programming so I am learning it as I go. I have developed a couple of games, but this one is probably my most advanced.
I have about 5 2d arrays. One to hold the Bitmaps, one to hold X locations, one to hold Y locations, one to hold the state of the element (they are birds, status is for flying state and hit state), and one identifying what the element is. Each array works together to give me a game board with birds flapping their wings. I can touch the game board, I get the location back. This part all works.
What I am trying to do is create a way to detect if elements nearby are the same element as the flagged element (the touched item). When this happens I basically want to flag all of these elements as "Hit". This is not just getting the 8 blocks around the element, this is getting every item touching the flagged item and identifying them as "Hit". You probably have played games like this, maybe. They are puzzle like games (mines cooler because the birds poop on you and it kills your character :) ). I created another 2D array to tack hit state (in this latest iteration of my attempt to solve the problem, so I don't double count a hit, and I am now using it to flag items I don't count with a -1.
My arrays are all [8][8] arrays. They don't change, they will always be that way. This should make it easier. So as an example:
sample from game
The circled red birds in the image should have been flagged as hit as well.
On to the code:
public void calculateGunDestruction(){
int currentRow = targetBirdRow;
int currentCol = targetBirdCol;
int hitbird = whichBird[targetBirdRow][targetBirdCol];
boolean[] pathState = new boolean[5];
countedArray = new int[8][8];
for(int j = 0; j < countedArray.length;j++){
for(int jj = 0; jj< countedArray[j].length;jj++){
countedArray[j][jj] = 0;
}
}
for(int i = currentRow;i < whichBird.length; i++) {
for (int ii = currentCol; ii < whichBird[i].length; ii++) {
pathState = pathMatch(i,ii,hitbird);
if(!pathState[0] && !pathState[1] && !pathState[2] && !pathState[3]){
break;
}
}
if(!pathState[0] && !pathState[1] && !pathState[2] && !pathState[3]){
break;
}
}
for(int i = currentRow;i > -1; i--) {
for (int ii = currentCol; ii < whichBird[i].length; ii++) {
pathState = pathMatch(i,ii,hitbird);
if(!pathState[0] && !pathState[1] && !pathState[2] && !pathState[3]){
break;
}
}
if(!pathState[0] && !pathState[1] && !pathState[2] && !pathState[3]){
break;
}
}
for(int i = currentRow;i < whichBird.length; i++) {
for (int ii = currentCol; ii > - 1; ii--) {
pathState = pathMatch(i,ii,hitbird);
if(!pathState[0] && !pathState[1] && !pathState[2] && !pathState[3]){
break;
}
}
if(!pathState[0] && !pathState[1] && !pathState[2] && !pathState[3]){
break;
}
}
for(int i = currentRow;i > -1; i--) {
for (int ii = currentCol; ii > - 1; ii--) {
pathState = pathMatch(i,ii,hitbird);
if(!pathState[0] && !pathState[1] && !pathState[2] && !pathState[3]){
break;
}
}
if(!pathState[0] && !pathState[1] && !pathState[2] && !pathState[3]){
break;
}
}
}
public boolean[] pathMatch(int i, int ii, int hitbird){
boolean pathTop = false;
boolean pathBottom = false;
boolean pathLeft = false;
boolean pathRight = false;
if(findMatch(i,ii,hitbird)) {
if(countedArray[i][ii] == 0) {
countedArray[i][ii] = 1;
destroyedBirds(i, ii);
}
if((i - 1) > -1) {
if (countedArray[i - 1][ii] == 0) {
if (findMatch(i - 1, ii, hitbird)) {
countedArray[i - 1][ii] = 1;
destroyedBirds(i - 1, ii);
pathLeft = true;
} else {countedArray[i - 1][ii] = -1;pathLeft = false;}
} else {pathLeft = false;}
} else {pathLeft = false;}
if((i + 1) < 8) {
if (countedArray[i + 1][ii] == 0) {
if (findMatch(i + 1, ii, hitbird)) {
countedArray[i + 1][ii] = 1;
destroyedBirds(i + 1, ii);
pathRight = true;
} else {countedArray[i + 1][ii] = -1;pathRight = false;}
} else {pathRight = false;}
}else {pathRight = false;}
if((ii - 1) > -1) {
if (countedArray[i][ii - 1] == 0) {
if (findMatch(i, ii - 1, hitbird)) {
countedArray[i][ii - 1] = 1;
destroyedBirds(i, ii - 1);
pathTop = true;
} else {countedArray[i][ii - 1] = -1;pathTop = false;}
} else {pathTop = false;}
} else {pathTop = false;}
if((ii + 1) < 8) {
if (countedArray[i][ii + 1] == 0) {
if (findMatch(i, ii + 1, hitbird)) {
countedArray[i][ii + 1] = 1;
destroyedBirds(i, ii + 1);
pathBottom = true;
} else {countedArray[i][ii + 1] = -1;pathBottom = false;}
} else {pathBottom = false;}
}else {pathBottom = false;}
}else{
countedArray[i][ii] = -1;
}
return new boolean[]{pathTop,pathBottom,pathLeft,pathRight};
}
public boolean findMatch(int row,int col,int hitbird){
if(hitbird == whichBird[row][col]){
return true;
}
return false;
}
public void destroyedBirds(int row,int col){
destroyedBirdsRow.add(0, row);
destroyedBirdsCol.add(0, col);
}
I have tried several different ways, this is just the latest as of this writing. Some were closer than others, but none quite got it. These seems like it should be a real simple thing to do, but I am not a game programmer professionally, game programming is something I am trying to learn.
The code above basically breaks the 2D array out into quadrants from the point at which you touch. I added the control "countedArray" with this attempt because I knew there was no real way to stop double counting with this method without some sort of way to track it. It goes left/down, left,up, right/down, right/up from the touch point.
the touch points near the top:
int currentRow = targetBirdRow;
int currentCol = targetBirdCol;
int hitbird = whichBird[targetBirdRow][targetBirdCol];
hit bird holds the identifier of the touched bird or element. This is not a terribly elegant solution, but it also doesn't work. At this point elegance is thrown out the window, I am about to write a long nasty looking piece of code that no one can read checking each item individual with no looping and hope that works. So cobbled solutions are okay.
For reference sake - the area the above code is called from looks like this:
if(playerObj.getWeaponSelected() == wGun) {
calculateGunDestruction();//CALLING FUNCTION
for (int i = 0; i < destroyedBirdsRow.size(); i++) {
state[destroyedBirdsRow.get(i)][destroyedBirdsCol.get(i)] = hit;
}
destroyedBirdsRow.clear();
destroyedBirdsCol.clear();
Full Game Image
I have edited to add a full game image. Questions on why it is over complicated prompted me to explain a bit. (And yes, there may be better ways to do it - I am certainly no expert on this). The idea is, as the birds are hit, to display a brief hit image, make the hit bird disappear, and then move birds in from the left, adding a new bird(s) to replace the hit birds. That structure all works - if not perhaps over complicated in design. Single hits work, bomb hits work (when you shoot a bomb that randomly appears on the board as a new bird). I use the position arrays to guide the animation of the birds that need to move in to fill the gap left by the hit birds. I wouldn't be surprised to learn of a better way, and I am here and doing this to learn so feel free to comment outside of the original scope of the question. It may help others trying to learn, and me; and I think that really is the most important point of a site like this.
As far as the part I am having difficulty getting to work. When I shoot the gun, the idea was to have it peg all birds of the same color/species that are touching the hit bird, mark them as hit, and make them disappear. And yes, the code I currently have for that section is probably the most complicated version I wrote. I started over at least a dozen times now. The simplest version involved two groups of loops and less outside methods (it was half the size) - it worked about as well as this one, but tended to flag extra birds. This one doesn't seem to flag extra birds, but doesn't get them all.
I have built a few small games in the past - but this one is by far the most complex one I have attempted and its looking pretty good so far - except for the whole grouped kill thing.
Possibly your downfall with this is over complication in design. I am struggling to understand why you need 5 8x8 arrays to store a 2D game board.
Your solution for marking hit tiles and those around it should be something like this for simplicity:
public static final int NOTHING=0,BIRD_FLYING=1,BIRD_HIT=2,
TILE_LAND=0,TILE_WATER=1;
private int[][] gameBoard=new int[8][8]; // Store terrain etc.
private int[][] birds=new int[8][8]; // Store entities on the map
private int tileSize=32; // Store the size of the tile in pixels
private Image landImg,seaImg,flyingImg,hitImg; // Store the tile images (or use 1 tilesheet and segment it in the draw method)
private void hitTile(int x,int y)
{
for(int i=-1;i<=1;i++)
{
for(int j=-1;j<=1;j++)
{
birds[hitX+i,hitY+j]=BIRD_HIT;
}
}
}
private void drawTheTiles()
{
for(int i=0;i<8;i++)
{
for(int j=0;j<8;j++)
{
// Draw background tiles first
switch(gameBoard[i][j])
{
default:
case TILE_LAND:
g.drawImage(landImg,i*tileSize,j*tileSize,this);
break;
case TILE_SEA:
g.drawImage(seaImg,i*tileSize,j*tileSize,this);
break;
}
// Draw entities on tiles last
switch(gameBoard[i][j])
{
default:
case NOTHING:
// Don't draw anything
break;
case BIRD_FLYING:
g.drawImage(flyingImg,i*tileSize,j*tileSize,this);
break;
case BIRD_HIT:
g.drawImage(hitImg,i*tileSize,j*tileSize,this);
break;
}
}
}
}
Of course, you'll need to add in your own handling, image loading, rendering, and array entries but there should be no need to store positions in an array for a 2D game board like this, as the positions can be implied by the tile index.
I hope this gives you something to go on. But I'm afraid I don't fully understand your original program/premise.
I am trying to implement a minmax algorithm to create an AI for connect four. I'm having quite a bit of trouble with it though as I feel like I have overcomplicated things (and it doesn't work properly), perhaps someone here can help. I'm going to post my code first and then the issue I'm having with it below.
This is the initial call to the minmax algorithm
public int getColumnForMove(ConnectFour game)
{
game.minimax(2, game.getCurrentPlayer(), game);
int column = game.getBestMove();
return column;
}
This is the initial minimax method (it is inside the ConnectFour class which is not where the initial method is called from that is in a separate AI class) that is called and a subclass that holds each column the user moves into as well as the min/max'ed score if it moves into that column.
class ColumnsAndScores
{
int column;
int score;
ColumnsAndScores(int column, int score)
{
this.column = column;
this.score = score;
}
}
List<ColumnsAndScores> cas = new ArrayList<>();
public void minimax(int depth, int turn, ConnectFour game)
{
cas = new ArrayList<>();
minimaxHelper(depth, depth, turn, game);
}
The following are methods that get the min or max score from each set of possible moves:
public int getMax(List<Integer> list)
{
int max = Integer.MIN_VALUE;
int index = -1;
for (int i = 0; i < list.size(); i++)
{
if (list.get(i) > max)
{
max = list.get(i);
index = i;
}
}
return list.get(index);
}
public int getMin(List<Integer> list)
{
int min = Integer.MAX_VALUE;
int index = -1;
for (int i = 0; i < list.size(); i++)
{
if (list.get(i) < min)
{
min = list.get(i);
index = i;
}
}
return list.get(index);
}
This is the actual minimax method (it has a bunch of commented out code that shows it should return a range of values depending on how good the board is if its not a clear cut win or loss but right now I'm just trying to have it make decisions based on a win or loss (if none of that happens in the requested depth it makes a random move)).
public int minimaxHelper(int originalDepth, int depth, int turn, ConnectFour game)
{
//holds future game states
ConnectFour futureGameState;
//holds the current scores
List<Integer> scores = new ArrayList<>();
//if (not at the lowest depth)
if (depth !=0)
{
if (checkForWin(turn))
{
//return Integer.MAX_VALUE or Integer.MIN_VALUE respectively based on who's turn it is
return (turn % 2 == 0) ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
//recursively call getColumnForMove(depth--, otherTurn) for each column if the column isnt full
for (int i = 1; i <= ConnectFour.NUM_OF_COLUMNS; i++)
{
futureGameState = new ConnectFour();
futureGameState.setCurrentGameState(game.getCurrentGameState());
futureGameState.setCurrentPlayer(game.getCurrentPlayer());
if (futureGameState.isValidMove(i))
{
futureGameState.makeMove(i);
futureGameState.switchPlayer();
scores.add(minimaxHelper(originalDepth, depth - 1, futureGameState.getCurrentPlayer(), futureGameState));
}
else //if move isnt valid return the worst possible value so this column doesnt get chosen
{
return (turn % 2 == 0) ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
if (depth == originalDepth)
{
ColumnsAndScores newScore;
if (turn % 2 == 0)
newScore = new ColumnsAndScores(i, getMax(scores));
else
newScore = new ColumnsAndScores(i, getMin(scores));
cas.add(newScore);
}
}
if (turn % 2 == 0)
return getMax(scores);
else
return getMin(scores);
}
else
{
if (checkForWin(turn))
{
//return Integer.MAX_VALUE or Integer.MIN_VALUE respectively based on who's turn it is
return (turn % 2 == 0) ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
else
{
return 0;
}
//else
//if 3 in a row with 2 open spaces that have pieces under those spaces
//return 100
//else if 3 in a row with 1 open space that has a piece under that space
//return 80;
//else if 3 in a row
//return 60;
//else if 2 in a row
//return 40
//else
//return 0
}
}
and finally this is a method that is called by the AI to get the best move from the list that minimax added the ColumnAndScores too.
public int getBestMove()
{
int highestScore = Integer.MIN_VALUE;
int best = -1;
for (int i = 0; i < cas.size(); ++i) {
if (highestScore < cas.get(i).score) {
highestScore = cas.get(i).score;
best = i;
}
}
if (highestScore == 0)
return 1 + ((int) (Math.random() * 7));
else
return best;
}
While I believe there are a couple of logic errors the thing I am having the most difficulty with at the moment is that when I dofutureGameState = new ConnectFour();
futureGameState.setCurrentGameState(game.getCurrentGameState());
This should put it into a separate instance so that when I then make a move it should only last for that branch of the tree and not corrupt the actual game being played but that isn't the case. It is changing the actual state of the game being passed in.
The issue is most probably caused by the implementation of ConnectFour, something like
private int[][] state;
public void setCurrentGameState(int[][] newState) {
this.state = newState;
}
That's okay, but causes your "copy" of the game state to actually reference the same int[][] state, thus any modifications to it will apply to both states. What you want is
public class ConnectFour implements Cloneable<ConnectFour> {
private static final int NUM_ROWS = 6;
private static final int NUM_COLS = 7;
private int[][] state = new int[NUM_ROWS][NUM_COLS];
// ...
public ConnectFour clone() {
int[][] stateCopy = new int[NUM_ROWS][NUM_COLS];
for (int i = 0; i < NUM_ROWS; i++)
for (int j = 0; j < NUM_COLS; j++)
stateCopy[i][j] = this.state[i][j];
ConnectFour cloned = new ConnectFour();
cloned.setCurrentGameState(stateCopy);
// copy other fields over to cloned
return cloned;
}
}
I'm just going to address one issue. You should try not to have too many per question, and include the code relevant to your question, such as your ConnectFour class here.
If you want to make a copy of the board you can modify without changing the original, you need to make a deep copy, not a copy of the reference. To make a shallow copy of your house, you make a copy of your house key. If you give it to someone, you shouldn't be surprised to see changes when you get home. To make a deep copy of your house, you get a second lot and build a new house from blueprints and photos of your house. If you give a key to the new house to someone, he/she might not notice the difference immediately, but any changes shouldn't affect you directly, and changes you make won't affect the recipient.
"Deep copy" is actually ambiguous because your object may contain object members that have object members. When you make a deep copy, you have to decide whether to make deep copies or shallow copies of any member objects. If your ConnectFour class contains an ArrayList of Move objects, each of which is a wrapper for an int representing a column, you have 3 choices:
You can copy a reference to the ArrayList.
You can make a new ArrayList with references to the same set of moves.
You can make a new ArrayList with references to copies of the moves.
Anyway, my guess is that you don't yet have nested member objects, so your deep copy method can look something like the following:
public class ConnectFour{
private int[][] board = new int[6][7];
public setCurrentGameState(int[][] state){
for(int i = 0; i<6; i++)
for(int j=0; j<7; j++)
board[i][j] = state[i][j];
}
...