Collision detection on a 2d side scroller: not colliding properly - java

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

Related

Chess alpha beta returning wrong move for the board

I am trying to implement a chess game with alpha beta pruning. The following is almost working, but it returns wrong moves.
For example, the following can occur.
White (user) to move, white king position - a1 / Black (computer), black king position - h1
White moves its king from a1 - a2, then black return the move g2 - g1???
It appears that the computer returns a move for the wrong node (board representation), as if the best evaluation of a given board position is not being propagated all the way back up the tree. So in one of the simulated positions explored, the computer "imagines" its king moving to g2 and then returns the move to be made from this position, not realising that this position is a simulated position and not the representation of the actual board (the root node?).
How can I correct the code to make the computer return a move for the actual board representation and not one of the simulations by mistake?
Thank you.
Initial call alphaBeta(3, ChessEngine.invertBoard(ChessEngine.board), -10000, 10000, true);
private static int alphaBetaEvaluate = 0;
private static int alphaBetaSelectedSquare = 0;
private static int alphaBetaMoveToSquare = 0;
public static int alphaBeta(int depth, char[] board, int alpha, int beta, boolean maxPlayer) {
//create a copy of the board
char[] boardCopy = board.clone();
//if terminal state has not been met, keep searching
if (maxPlayer == true && depth > 0) {
//for all of the moves that max can make
for (int i = 0; i < board.length; i++) {
for (int move : ChessEngine.getValidMoves(i, boardCopy)) {
//make the move
boardCopy[move] = boardCopy[i];
boardCopy[i] = '.';
alphaBetaEvaluate = rating(board, boardCopy, i, move);
//store the best move to make
int temp = alphaBeta(--depth, ChessEngine.invertBoard(boardCopy), -10000, 10000, false);
if (temp > alpha) {
alphaBetaSelectedSquare = i;
alphaBetaMoveToSquare = move;
alpha = temp;
}
//reset the board for the next simulated move
boardCopy = board.clone();
if (beta <= alpha) {
break;
}
}
}
return alpha;
} else if (maxPlayer == false && depth > 0) {
//for all of the moves that min can make
for (int i = 0; i < board.length; i++) {
for (int move : ChessEngine.getValidMoves(i, boardCopy)) {
//make the move
boardCopy[move] = boardCopy[i];
boardCopy[i] = '.';
beta = Math.min(beta, alphaBeta(--depth, ChessEngine.invertBoard(boardCopy), -10000, 10000, true));
//reset the board for the next simulated move
boardCopy = board.clone();
if (beta <= alpha) {
break;
}
}
}
return beta;
}
return alphaBetaEvaluate;
}
I dont get your implementation after all. First of all what you want to do is create a tree. A decision tree and propagates the decision up. You want to maximize your evaluation and also expect that the enemy will select the move that minimizes your evaluation in return.
So inverting the board does not sound so reasonable for me unless you know that the evaluation you do uppon the situation is correctly adjusting.
Another serious problem for me is that you always call the min/max for the next move with -10k and 10k as the bounderies for alpha and beta. This way your algorithm does not 'learn' from previous moves.
What you need is to check the algorithm again (wikipedia for instance, which I used) and see that they use alpha and beta being modified by former evaluation. This way the calculation in higher depth can firstly stop and secondly evaluate the best move better.
I am no expert in this. its decades ago when I wrote my implementation and I used something different.
Another idea is not to use min and max within the same method but use the min and max methods instead. It makes it more likely you spot other defects.
Also do not use two kings for evaluation. There is no goal in that. Two kings are random, cant win. One thing might be two knights or four queens and alike. It is not so random and you can see the queens dancing around without being able to catch each other. Or use three knights versus a single queen.
And try to create yourself some unit tests around your other parts. Just to insure that the parts are working correctly independently. And why are you using characters? Why not using enums or objects. You can reuse the objets for each field (its more like kinds of figures).
But anyhow this is style and not algorithm correctness.

either a rookie mistake, or misuse of .equals in java

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.

Collision detection in tile-base game only applies to the file tile in the array

I've written a prototype in java that employs a movable character and tile-based graphics and game logic. The layout of each level is stored in a 10x10 2D array. When I use the following code to detect whether or not the player is colliding with a '1' (empty) tile, it only returns positive when the player is colliding with the final '1' tile in the array, why is this? I understand this method is incredibly inefficient.
for(int r = 0; r < levArray.length; r++) {
for(int c = 0; c < levArray[0].length; c++) {
if(levArray[r][c] == 1) {
if(px >= r*64-9 && px <= (r+1)*64+11 && py >= c*64-30 && py <= (c+1)*64+30) {
isColliding = true;
} else {
isColliding = false;
}
}
}
}
You need to stop looping (for example with break) when you find a collision. Now you loop all the way through and the isColliding variable will be set to whatever the last item in the array is.
You are checking every element of the level against the player position (px,py). Since the only control in these loops is the iteration itself, the last comparison that happens is at the last cell. Any result from previous calculations will be overwritten by this last calculation.
As Kayaman says, you can break or return to exit the loop; better would be to check the player position against the level, instead of checking the level against the player. You know where the player is, if you convert that px,py coordinate into r,c coordinates you only have to look at one element in the level array.

Sample code not doing as expected

Here is the algorithm (not working) Please let me know where the error is
Thanks
private void checkSouth(Location point, int player) {
//Loop through everything south
boolean isthereAnOppositePlayer=false;
int oppositePlayer=0;
//Set opposite player
if (player==1) {
oppositePlayer=2;
}else{
oppositePlayer=1;
}
for (int i = point.getVertical(); i < 8; i++) {
//Create a location point with the current location being compared
MyLocation locationBeingChecked= new MyLocation();
locationBeingChecked.setHorizontal(point.getHorizontal());
locationBeingChecked.setVertical(i);
int value = board[locationBeingChecked.getVertical()][locationBeingChecked.getHorizontal()];
//If the first checked is the opposite player
if (value==oppositePlayer) {
//Then potential to evaluate more
isthereAnOppositePlayer=true;
}
//If it isn't an opposite player, then break
if(!isthereAnOppositePlayer && value!=0){
break;
}
//If another of the player's piece found or 0, then end
if (isthereAnOppositePlayer && value==player || isthereAnOppositePlayer && value==0) {
break;
//end
}
//Add to number of players to flip
if(isthereAnOppositePlayer && value==oppositePlayer && value!=0){
//add to array
addToPiecesToTurn(locationBeingChecked);
}
}
}
It looks like the locations that got rotated back to the other player are the exact same as those rotated during the first move. I would guess that the array being populated by addToPiecesToTurn is perhaps not being cleared out between each move, so all the previous locations are still in there.
If you are storing the pieces to be turned in an ArrayList, you can use the clear() method to erase the contents of the collection between each turn.
Another possible problem is that you are checking for the opposite player, and then instantly beginning to populate addToPiecesToTurn. However, the pieces in that direction are not necessarily valid to be rotated unless they are "sandwiched" in by a second location containing the current player's piece. I don't think your code is properly checking for that case; when that happens, you'll want to somehow skip flipping those pieces to the other player, such as clearing out the array of piecesToTurn.
Edit: Looking at your current solution where you are implementing every direction separately, you are going to have a lot of duplicated code. If you think about what it means to walk along a certain direction, you can think of it as adjusting the x/y value by a "step" amount. The step amount could be -1 for backwards, 0 for no move, or 1 for forwards. Then you could create a single method that handles all directions without duplicating the logic:
private void checkDirection(Location point, int player, int yStep, int xStep) {
int x = point.getHorizontal() + xStep;
int y = point.getVertical() + yStep;
MyLocation locationBeingChecked = new MyLocation();
locationBeingChecked.setHorizontal(x);
locationBeingChecked.setVertical(y);
while (isValid(locationBeingChecked)) {
// do the logic here
x += xStep;
y += yStep;
locationBeingChecked = new MyLocation();
locationBeingChecked.setHorizontal(x);
locationBeingChecked.setVertical(y);
}
}
You would need to implement isValid to check that the location is valid, i.e., in the board. Then you could call this method for each direction:
// north
checkDirection(curPoint, curPlayer, -1, 0);
// north-east
checkDirection(curPoint, curPlayer, -1, 1);
// east
checkDirection(curPoint, curPlayer, 0, 1);
// etc
This is the sort of problem that is ripe for some unit testing. You could very easily set up a board, play a move, and validate the answer, and the test results would give plenty of insight into where your expectations and reality diverge.
why didn't you use a 2d array ?
each cell would contain an enum : EMPTY, PLAYER_1, PLAYER_2 .
then, in order to go over the cells, you simply use loops for each direction.
for example, upon clicking on a cell , checking to the right would be:
for(int x=pressedLocation.x+1;x<cells[pressedLocation.y].length;++x)
{
Cell cell=cells[pressedLocation.y][x];
if(cell==EMPTY||cell==currentPlayerCell)
break;
cells[pressedLocation.y][x]=currentPlayerCell;
}
checking from top to bottom would be:
for(int y=pressedLocation.y+1;y<cells.length;++y)
{
Cell cell=cells[y][pressedLocation.x];
if(cell==EMPTY||cell==currentPlayerCell)
break;
cells[y][pressedLocation.x]=currentPlayerCell;
}

Text based battleship enemy AI not working as wanted - Java

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.

Categories