So I'm making a TicTacToe-program, and I made a method for checking if there are 3 pieces in a row, to check if the game is over or not.
public boolean hasWon() {
if (tictac[0][0].equals(tictac[1][0]) && tictac[0][0].equals(tictac[2][0]) && tictac[1][0].equals(tictac[2][0])) { //Vertical
return false;
} else if (tictac[0][1].equals(tictac[1][1]) && tictac[0][1].equals(tictac[2][1]) && tictac[1][1].equals(tictac[2][1])) { //Vertical
return false;
} else if (tictac[0][2].equals(tictac[1][2]) && tictac[0][2].equals(tictac[2][2]) && tictac[1][2].equals(tictac[2][2])) { //Vertical
return false;
} else if (tictac[0][0].equals(tictac[0][1]) && tictac[0][0].equals(tictac[0][2]) && tictac[0][1].equals(tictac[0][2])) { //Horizontal
return false;
} else if (tictac[1][0].equals(tictac[1][1]) && tictac[1][0].equals(tictac[1][2]) && tictac[1][1].equals(tictac[1][2])) { //Horizontal
return false;
} else if (tictac[2][0].equals(tictac[2][1]) && tictac[2][0].equals(tictac[2][2]) && tictac[2][1].equals(tictac[2][2])) { //Horizontal
return false;
} else if (tictac[0][0].equals(tictac[1][1]) && tictac[0][0].equals(tictac[2][2]) && tictac[1][1].equals(tictac[2][2])) { //Diagonal
return false;
} else if (tictac[0][2].equals(tictac[1][1]) && tictac[0][2].equals(tictac[2][0]) && tictac[1][1].equals(tictac[2][0])) { //Diagonal
return false;
} else {
return true;
}
}
The problem here is, with these conditions I figured if, say, all of the indexes "in a row" where f.ex. X, " X X X " it would be a win for the player X. However, my program calls for a winner if any kind of piece fill 3 in a row, f.ex. " X O X " would end the game, which obviously is wrong.
Have I misunderstood .equals?
EDIT: after the wonderful feedback, is there a way that I can make the if-statements to ignore blank spaces (" ")?
You should consider creating a class, Board. Have this class provide an interface that includes getLines. This should return a List<Line>. There are eight lines in a tic tac toe board: three horizontal, three vertical, two diagonal. Create a method isWin(String player, Line line). Return true or false based on whether the count of the player's marker in the cells of the line is three.
I would guess you can write the whole thing in the same amount of space as the horrible nested if with all its inscrutable && clauses, and the functioning will be obvious. (This is also good practice for encapsulation.)
Your prog returns true if all three values on some line are the same. So, it won't return true on XOX. But would on OOO, XXX and SpaceSpaceSpace.
The first two are both OK, for if you check after a move, only the last mover could have three in a line and win. But three spaces are a problem. In every if you should check the first cell for not having space.
It is longer, but if you use the advice of #ajb, you'll even shorten the prog.
As for structuring, I would advice more simple way than that of #syrion. Make an array lines[5,3,2]. It has 5 lines, 3 cells for every line and 2 coords for every cell. And you can do the check in two inserted loops. It is not a Java way to do things, but you can take it as a first step. Or you could move immediately to a more readable structure - a list of 5 x lines= array of Cell = pair of coordinates. I don't know which one is more simple for you.
Related
I have four methods that check whether or not a given grid location is next to an occupied location (value of 1). The grid is assumed to wrap around, ie, if in a 50x50 grid[0][1] is the given location and grid[49][1] is occupied, the method should return true/ My checkNorth and checkEast method are working fine, but I get an ArrayIndexOutofBoundsException: -1 error for either the south or west methods every time I run the program. I checked my math and I think it should work - am I using the modulo incorrectly, or am I missing something else?
EDIT: Clarified the wrapping criterion, word use correction.
boolean checkWest(int indexA, int indexB)
{
if (indexA-1 > 0)
{
if (grid[indexA-1][indexB] == 1)
{
return true;
}
}
if (indexA-1 < 0)
{
if (grid[(indexA-1)%width][indexB] == 1)
{return true;}
else return false;
}
return false;
}
I see a couple problems. First, Java arrays are zero-indexed, which means that the first element is at index 0. So it's okay to check grid[indexA-1][indexB] when indexA-1 is equal to 0. Second, you're not properly handling when indexA equals 0. Here is my implementation. I also simplified the logic a bit.
boolean checkWest(int indexA, int indexB)
{
if (indexA > 0)
return grid[indexA - 1][indexB] == 1;
else
return grid[width + indexA - 2][indexB] == 1;
}
EDIT: I'm pretty sure I butchered the math with the second return statement. It should be right now...
The following block of code is supposed to check if the coordinates that the user entered are the coordinates of the ship. The ship is located on a two dimensional array at (1,1) and (1,2).
The problem started when I surrounded the getUserGuess method implementation with a while loop. The loop checks if the ship is still alive and will keep asking for the user to enter coordinates until the ship is sunk. However, as soon as the user enters either pair of the correct coordinates, the entire ship is sunk.
I have no idea why this keeps happening. As soon as I comment out the loop, the problem stops, but the loop is necessary.
Here is the method:
public void checkResult(String userGuess) {
while (frigateIsAlive == true) {
if (userGuess.equalsIgnoreCase(board[1][1])){
System.out.println("hit!");
numOfHitsOnFrigate++;
board[1][1] = " *";
createBoard();
}
if (userGuess.equalsIgnoreCase(board[1][2])) {
System.out.println("hit!");
numOfHitsOnFrigate++;
board[1][2] = " *";
createBoard();
}
else if (numOfHitsOnFrigate == 2) {
System.out.println("Enemy frigate has been sunk!");
frigateIsAlive = false;
break;
}
else {
System.out.println("miss!");
// try again
}
}
}
public String getUserGuess()
{ // takes the users guess
System.out.println("Choose a coordinate on the board to fire at");
int x = input.nextInt();
int y = input.nextInt();
String userGuess = board[x][y];
return userGuess;
}
Let me know if you need to see any other part of the code in order to better assist me.
This method is flawed :
If userGuess matches board[1][1], the loop will make you increment numOfHitsOnFrigate twice, and then you'll change frigateIsAlive to false and exit.
If userGuess matches board[1][2], the loop will make you increment numOfHitsOnFrigate infinite times and you'll never exit.
If userGuess doesn't match, the loop will never terminate, and keep printing miss! without getting new input.
You need to remove the loop, since this method checks a single userGuess, and change the conditions :
public void checkResult(String userGuess) {
if (userGuess.equalsIgnoreCase(board[1][1])){
System.out.println("hit!");
numOfHitsOnFrigate++;
board[1][1] = " *";
createBoard();
} else if (userGuess.equalsIgnoreCase(board[1][2])) {
System.out.println("hit!");
numOfHitsOnFrigate++;
board[1][2] = " *";
createBoard();
} else {
System.out.println("miss!");
// try again
}
if (numOfHitsOnFrigate == 2) {
System.out.println("Enemy frigate has been sunk!");
frigateIsAlive = false;
}
}
Based on what you wrote - I surrounded the getUserGuess method implementation with a while loop. - you have another loop which keeps getting input from the user. That other loop, whose code you haven't shown us, is necessary, since without it the game won't progress.
What you probably want (pseudo-code):
start (of a loop)
ask user for a guess
check result for the guess
sunk => stop / not sunk => continue to start
(i.e. you misplaced your while loop)
I'm having a problem with my Collision method below. the problem is when there are two enemies in the game. It intersects one enemy within the loop, and proceeds to return true for colliding. But if there is a second enemy within this array List It will not be colliding with that second object, therefore causing it to return false and the player continues walking. any idea's on how I can make him stop when he is in contact with ANY enemy, instead of just continuing because he isn't in contact with all the enemies?
Thanks, here is the code.
public void checkCollision(){
ArrayList<Enemy> enemy = c.getEnemyList();
for ( int i = 0; i < enemy.size(); i++){
Enemy e = enemy.get(i);
if (!getBounds().intersects(e.getBounds())){
walk();
return;
}
if (getBounds().intersects(e.getBounds())){
if (e.getHP() <= 0){
c.removeEnemy(e);
walk();
return;
}
fight();
if (count == 25 || count == 65){
int dd = DCalc.calcDmg(atk, atkMAX);
e.dmg(dd);
}
}
}
}
This is just another example of the 'early return' problem. This problem manifests when you need to do a check of the format (if ANY, x, else y) or (if ALL, x, else y) and you rephrase it in the format (if FIRST, x, else y).
To solve this, you need to remake the algorithm as follows:
bool collided = false
For each enemy:
Are we colliding with this enemy?
If we are, do collision detection and set `collided` to true
end for
If `collided` is false, NOW we can run the code that should only run if we collided with nothing
Still working on my sudoku solver, I have once again run into some trouble. I have gotten the sudoku solver to work, however whenever I attempt to solve a really "hard" sudoku board, my solver tells me there are no possible solutions, due to a stack overflow error. And yes, I know for a fact that these boards DO have a solution.
I am using the brute force algorithm -- I start at square one (or [0][0] if you prefer) and insert the first legal value. I then do a recursive call to the next square, and so on. If my function has not gone through the entire board, and finds there are no possible legal values, it moves to the previous square and attempts to increment the value there. If that fails, it moves further back. If it ends up at square one with no possible solutions, it exits.
I admit my code is not pretty and probably quite inefftective, but please keep in mind that I am a first year student trying to do my best. I don't mind comments on how to effectivize my code though :)
For squares without a final predefined number, here's the solver function:
public void fillRemainderOfBoard(Square sender){
try {
while(true){
if(currentPos > boardDimension){
if(previous != null){
this.setNumrep(-1);
this.setCurrentPos(1);
previous.setCurrentPos(previous.getCurrentPos()+1);
previous.fillRemainderOfBoard(this);
return;
} else if(sender == this){
this.setCurrentPos(1);
} else {
break;
}
}
if((thisBox.hasNumber(currentPos)) || (thisRow.hasNumber(currentPos)) || (thisColumn.hasNumber(currentPos))){
currentPos++;
} else {
this.setNumrep(currentPos);
currentPos++;
break;
}
}
if(this != last){
next.setNumrep(-1);
next.fillRemainderOfBoard(this);
} else {
return;
}
} catch (StackOverflowError e){
return;
}
}
In my code, the numrep value is the value that the square represents on the board. The currentPos variable is a counter that starts at 1 and increments until it reaches a legal value for the square to represent.
For squares with a predefined number, here's the same function:
public void fillRemainderOfBoard(Square sender){
if((sender.equals(this) || sender.equals(previous)) && this != last){
System.out.println(next);
next.fillRemainderOfBoard(this);
} else if (sender.equals(next) && this != first) {
previous.fillRemainderOfBoard(this);
} else {
System.out.println("No possible solutions for this board.");
}
}
Now, my problem is, like I said, that the function DOES solve sudokus very well. Easy ones. The tricky sudokus, like those with many predefined numbers and only a single solution, just makes my program go into a stack overflow and tell me there are no possible solutions. I assume this indicates I am missing something in the terms of memory management, or the program duplicating objects which I call in my recursive functions, but I do not know how to fix them.
Any help is greatly appreciated! Feel free to pick on my code too; I'm still learning (oh, aren't we always?).
___________________EDIT________________
Thanks to Piotr who came up with the good idea of backtracking, I have rewritten my code. However, I still cannot get it to solve any sudoku at all. Even with an empty board, it gets to square number 39, and then returns false all the way back. Here is my current code:
public boolean fillInnRemainingOfBoard(Square sender){
if(this instanceof SquarePredef && next != null){
return next.fillInnRemainingOfBoard(this);
} else if (this instanceof SquarePredef && next == null){
return true;
}
if(this instanceof SquareNoPredef){
currentPos = 1;
if(next != null){
System.out.println(this.index);
for(; currentPos <= boardDimension; currentPos++){
if((thisBox.hasNumber(currentPos)) || (thisRow.hasNumber(currentPos)) || (thisColumn.hasNumber(currentPos))){
continue;
} else {
System.out.println("Box has " + currentPos + "? " + thisBox.hasNumber(currentPos));
this.setNumrep(currentPos);
System.out.println("Got here, square " + index + " i: " + numrep);
}
if(next != null && next.fillInnRemainingOfBoard(this)){
System.out.println("Returnerer true");
return true;
} else {
}
}
}
if(next == null){
return true;
}
}
System.out.println("Returning false. Square: " + index + " currentPos: " + currentPos);
return false;
}
I had to complicate things a bit because I needed to check whether the current object is the last. Hence the additional if tests. Oh, and the reason I am using boardDimension is because this sudoku solver will solve any sudoku -- not just those 9 by 9 sudokus :)
Can you spot my error? Any help is appreciated!
So your problem lies here:
previous.fillRemainderOfBoard(this);
and here
next.fillRemainderOfBoard(this);
Basically you have too many function calls on the stack because you are going forward and backward multiple times. You are not really returning from any call before answer is found (or you get StackOverflowError :)). Instead of increasing stack size by using recursive function try to think how to solve problem using a loop (pretty much always loop is better than recursive solution performance wise).
Ok so you can do something like this (pseudo code):
boolean fillRemainderOfBoard(Square sender){
if (num_defined_on_start) {
return next.fillRemainderOfBoard(this);
} else {
for (i = 1; i < 9; ++i) {
if (setting i a legal_move)
this.setCurrentPos(i);
else
continue;
if (next.fillRemainderOfBoard(this))
return true;
}
return false;
}
The stack size will be ~81.
Im making a text based battleship game and the player plays against the computer. 3 random 3 unit long ships are placed on the board, and I want the computer to be able to guess around where his last guess was if his last guess was a hit. (but I want it to work so that he keeps guessing around the same spot until he got a hit and keep guessing around there until he gets the whole ship, or 3 hits)
It works a bit; the computer will guess near his last guess if it was a hit, but if he misses that guess then he starts guessing randomly again. Can someone help me out a bit?
-getGuess() method is the one with the AI-
/*
* computer class to handle computers guesses/ etc
* most methods are copied from player class, but slightly altered to account for variable names
* Methods that havent been copied have comments
*/
public class Computer{
static int firstCo, secondCo;
static int[] guessedHits={7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7};
//int array to store last guess
static int[] lastGuess = new int[2];
//int array to store current guess
static int[] guess=new int[2];
public static int[] computerShip1=new int[6];
public static int[] computerShip2=new int[6];
public static int[] computerShip3=new int[6];
/*
* method to choose random guess for computer - but make it guess around last guess if last guess was a hit
* return guess coordinate numbers in an array
*/
public static int[] getGuess(){
int[] guess=new int[2];
int firstCo, secCo;
int ran; //random int between 0 and 1 - will help to make random choices for guesses
if(isHit(lastGuess[0],lastGuess[1])){
ran=(int)(Math.random()*2);
//if ran is 0 and last guesses x coordinate was correct, set next guess to last x, and next y to last y +1
if((ran==0 && lastGuess[0]==Player.playerShip1[0]) || (ran==0 && lastGuess[0]==Player.playerShip1[2]) || (ran==0 && lastGuess[0]==Player.playerShip1[4])){
guess[0]=lastGuess[0];
guess[1]=lastGuess[1]+1;
//if ran is 1 and last guesses x coordinate was correct, set next guess to last x, and next y to last y -1
}else if((ran==1 && lastGuess[0]==Player.playerShip1[0]) || (ran==1 && lastGuess[0]==Player.playerShip1[2]) || (ran==1 && lastGuess[0]==Player.playerShip1[4])){
guess[0]=lastGuess[0];
guess[1]=lastGuess[1]-1;
//if ran is 0 and last guesses y coordinate was correct, set next guess to last y, and next x to last x +1
}else if((ran==0 && lastGuess[1]==Player.playerShip1[1]) || (ran==0 && lastGuess[1]==Player.playerShip1[3]) || (ran==0 && lastGuess[1]==Player.playerShip1[5])){
guess[0]=lastGuess[0]+1;
guess[1]=lastGuess[1];
//if ran is 1 and last guesses y coordinate was correct, set next guess to last y, and next x to last x -1
}else if((ran==1 && lastGuess[1]==Player.playerShip1[1]) || (ran==1 && lastGuess[1]==Player.playerShip1[3]) || (ran==1 && lastGuess[1]==Player.playerShip1[5])){
guess[0]=lastGuess[0]-1;
guess[1]=lastGuess[1];
}
return guess;
}else{
guess[0]=(int)(Math.random()*7);
guess[1]=(int)(Math.random()*7);
return guess;
}
}
public static boolean isHit(int firstC, int secC){
for(int i=0; i<Player.playerShip1.length; i=i+2){
if(firstC==Player.playerShip1[i] && secC==Player.playerShip1[i+1]){
return true;
}
if(i==4){
break;
}
}
for(int i=0; i<Player.playerShip2.length; i=i+2){
if(firstC==Player.playerShip2[i] && secC==Player.playerShip2[i+1]){
return true;
}
if(i==4){
break;
}
}
for(int i=0; i<Player.playerShip3.length; i=i+2){
if(firstC==Player.playerShip3[i] && secC==Player.playerShip3[i+1]){
return true;
}
if(i==4){
break;
}
}
return false;
}
public static void addHits(int firstC, int secC){
int index=-1;
for(int i=0; i<guessedHits.length; i++){
if(guessedHits[i]==7){
index=i;
break;
}
}
guessedHits[index]=firstC;
guessedHits[index+1]=secC;
}
public static void setComputerShips(){
int randX, randY;
int direction; //will be random int 0-1, determines direction ship will extend(up/down, left/right)
randX=(int)(Math.random()*7);
randY=(int)(Math.random()*7);
direction=(int)(Math.random()*2);
computerShip1[0]=randX;
computerShip1[1]=randY;
if(direction==0){//extend upwards or downwards 2 units(y values change, x stays the same)
computerShip1[2]=randX;
computerShip1[4]=randX;
if(randY>3){//if y value is greater than 3, has to extend down or it wont fit
computerShip1[3]=randY-1;
computerShip1[5]=randY-2;
}else if(randY<2){//if y value is less than 2, has to extend up or it wont fit
computerShip1[3]=randY+1;
computerShip1[5]=randY+2;
}else{//if direction doesnt matter, just extend upwards
computerShip1[3]=randY+1;
computerShip1[5]=randY+2;
}
}else if(direction==1){//extends left or right 2 units(y values stay the same, x changes)
computerShip1[3]=randY;
computerShip1[5]=randY;
if(randX>3){//if x is greater than 3, must extend left or it wont fit
computerShip1[2]=randX-1;
computerShip1[4]=randX-2;
}else if(randX<2){//if x is less than 2, must extend right or it wont fit
computerShip1[2]=randX+1;
computerShip1[4]=randX+2;
}else{//if direction doesnt matter, just extend right
computerShip1[2]=randX+1;
computerShip1[4]=randX+2;
}
}
//do same for both other ships
do{
randX=(int)(Math.random()*7);
randY=(int)(Math.random()*7);
}while((randX==computerShip1[0] && randY==computerShip1[1])||(randX==computerShip1[2]&&randY==computerShip1[3])||(randX==computerShip1[4]&&randY==computerShip1[5]));
direction=(int)(Math.random()*2);
computerShip2[0]=randX;
computerShip2[1]=randY;
if(direction==0){
computerShip2[2]=randX;
computerShip2[4]=randX;
if(randY>3){
computerShip2[3]=randY-1;
computerShip2[5]=randY-2;
}else if(randY<2){
computerShip2[3]=randY+1;
computerShip2[5]=randY+2;
}else{
computerShip2[3]=randY+1;
computerShip2[5]=randY+2;
}
}else if(direction==1){
computerShip2[3]=randY;
computerShip2[5]=randY;
if(randX>3){
computerShip2[2]=randX-1;
computerShip2[4]=randX-2;
}else if(randX<2){
computerShip2[2]=randX+1;
computerShip2[4]=randX+2;
}else{
computerShip2[2]=randX+1;
computerShip2[4]=randX+2;
}
}
do{
randX=(int)(Math.random()*7);
randY=(int)(Math.random()*7);
}while((randX==computerShip1[0] && randY==computerShip1[1])||(randX==computerShip1[2]&&randY==computerShip1[3])||(randX==computerShip1[4]&&randY==computerShip1[5])||(randX==computerShip2[0] && randY==computerShip2[1])||(randX==computerShip2[2]&&randY==computerShip2[3])||(randX==computerShip2[4]&&randY==computerShip2[5]));
direction=(int)(Math.random()*2);
computerShip3[0]=randX;
computerShip3[1]=randY;
if(direction==0){
computerShip3[2]=randX;
computerShip3[4]=randX;
if(randY>3){
computerShip3[3]=randY-1;
computerShip3[5]=randY-2;
}else if(randY<2){
computerShip3[3]=randY+1;
computerShip3[5]=randY+2;
}else{
computerShip3[3]=randY+1;
computerShip3[5]=randY+2;
}
}else if(direction==1){
computerShip3[3]=randY;
computerShip3[5]=randY;
if(randX>3){
computerShip3[2]=randX-1;
computerShip3[4]=randX-2;
}else if(randX<2){
computerShip3[2]=randX+1;
computerShip3[4]=randX+2;
}else{
computerShip3[2]=randX+1;
computerShip3[4]=randX+2;
}
}
}
public static boolean hasWon(){
if(guessedHits[17]!=7)
return true;
else
return false;
}
}
Your getGuess() function is the one you're after right?
1) You never account for times when you guess the same spot twice. Make a boolean value that determines whether the coordinates you're attempting to guess haven't already been guessed.
2) Your method of keeping ship coordinates is very awkward where 0,2,4 are X coords while 1,3,5 are Y coords? You're better off creating a Ship class that handles coordinates, and checks like isHit.
public class Ship {
int[] xCoords = new int[3];
int[] yCoords = new int[3];
public boolean isHit(int x, int y) {
return (Arrays.asList(xCoords).contains(x) && Arrays.asList(yCoords).contains(y));
}
}
Then you can:
if (Player.ship1.isHit(guess[0],guess[1])) {
....
}
At the very heart of it you have a little ways to go. You'll get better responses here if you start working at the problem then come back with specific problems you may have. Try to be as concise as possible when giving code snippets because not many people will spend much time going through an entire class to find a line or two giving issues.
Good luck!
---PS---
I wrote a battleship game about 3-4 years ago with some fairly advanced AI. I'll link it here:
https://github.com/GrahamBlanshard/AI-Battleship/blob/master/prograham/battleship/player/AIPlayer.java
First, I apologize for the... lame code (I was a much younger programmer, I swear!). If you want to view it to get hints that is fine. A brief explanation:
At the heart of it you need to create some form of datatype that stores his hits. Once a "hit" is scored you push it to the datatype, I used a Stack. The shots that are successful hits get stored on the stack until the ship is sunk. At that point it removes shots from the stack that belonged to the ship that just sunk. If there are shots still on the stack it knows it has hit a second ship during that process and continues to guess in the area.
To accomplish this, it goes through phases:
1) Shoot randomly until a hit.
2) Shoot around that shot (use a random(4) call to get N/S/E/W direction)
-- Keep doing this until you score a second shot
3) Create a "line" with the two points and fire along it until the ship sinks or...
4) Reverse the line and shoot the other direction.
Does that give you a good start to work with?
That's a lot of code to look at. So for now I will give some general suggestions that come to mind:
When the computer AI gets a "hit", set a "global" flag (more likely a class variable) and "remember" where the hit occured. On the following turns, guess the neighboring squares in some predetermined order (say north, south, east, west) until another hit is found. Then set another flag and on the next turn guess in the same direction as the second hit. The initial flag should only be reset when all three hits are found. This should fix the problem that a subsequent miss causes the computer AI to start guessing randomly again.