Stack Overflow with recursion in Java - java

I'm trying to write an AI that never loses at Tic Tac Toe, and I want to use the minimax algorithm to do so. However, when I try to run the program, a stack overflow appears and I can't seem to find what is the error. Could you take a look and tell me what I'm doing wrong? It doesn't go as deep in the recursion I believe, since it should only go through all the possible game outcomes, which go up to 8 moves (since the player is first to play, not the AI). It is probably me doing something wrong, but I can't find anything.
EDIT: Here's the full code, the mechanics function is the main part:
EDIT2: Fixed the constructor
package Packet;
import java.util.*;
import java.util.Scanner;
public class Logic {
public static class TicTacToe{
private int[] currentBoard = new int[9];
private int[] availableSpots = new int [9];
private int emptySpace = 0;
private int playerAI = 1;
private int playerHuman = 2;
void TicTacToe(){
for (int i = 0; i < 9; i++){
this.currentBoard[i] = this.emptySpace;
}
for (int i = 0; i < 9; i++){
this.availableSpots[i] = i;
}
}
private int movesNumber(){
int counter = 0;
for (int i = 0; i < 9; i++){
if (this.currentBoard[i] == this.emptySpace){
counter++;
}
}
return counter;
}
private boolean win(int[] board,int player){
if (
(board[0] == player && board[1] == player && board[2] == player) ||
(board[3] == player && board[4] == player && board[5] == player) ||
(board[6] == player && board[7] == player && board[8] == player) ||
(board[0] == player && board[3] == player && board[6] == player) ||
(board[1] == player && board[4] == player && board[7] == player) ||
(board[2] == player && board[5] == player && board[8] == player) ||
(board[0] == player && board[4] == player && board[8] == player) ||
(board[2] == player && board[4] == player && board[6] == player) ){
return true;
}
else{
return false;
}
}
private int mechanics(int[] newBoard, int player){
if (win(newBoard,this.playerHuman)){
return -10;
}
else if (win(newBoard, this.playerAI)){
return +10;
}
else if (this.movesNumber() == 0){
return 0;
}
ArrayList<Integer> moves = new ArrayList<Integer>();
ArrayList<Integer> scores = new ArrayList<Integer>();
for (int i = 0; i < this.movesNumber(); i++){
int[] possibleBoard = new int[9];
possibleBoard = newBoard;
int availableSpotNumber = i;
int j = i;
while (this.availableSpots[j] == 9){
availableSpotNumber++;
j++;
}
possibleBoard[availableSpotNumber] = player;
if (player == this.playerAI){
scores.add(this.mechanics(possibleBoard, this.playerHuman));
}
else{
scores.add(this.mechanics(possibleBoard, this.playerAI));
}
moves.add(availableSpotNumber);
possibleBoard[availableSpotNumber] = this.emptySpace;
}
int bestMove = 0;
if (player == this.playerAI){
int bestScore = -10000;
for (int i = 0; i < moves.size(); i++){
if (scores.get(i) > bestScore){
bestScore = scores.get(i);
bestMove = i;
}
}
}
else {
int bestScore = 10000;
for (int i = 0; i < moves.size(); i++){
if (scores.get(i) < bestScore){
bestScore = scores.get(i);
bestMove = i;
}
}
}
return moves.get(bestMove);
}
public void printTable(){
System.out.println(this.currentBoard[0] + " | " + this.currentBoard[1] + " | " + this.currentBoard[2]);
System.out.println("- - -");
System.out.println(this.currentBoard[3] + " | " + this.currentBoard[4] + " | " + this.currentBoard[5]);
System.out.println("- - -");
System.out.println(this.currentBoard[6] + " | " + this.currentBoard[7] + " | " + this.currentBoard[8]);
System.out.println();
}
private void fillTable(int position,int player){
this.currentBoard[position] = player;
this.availableSpots[position] = 9;
}
public void startGame(){
while(true){
this.printTable();
Scanner ulaz = new Scanner(System.in);
fillTable(ulaz.nextInt(), this.playerHuman);
this.printTable();
fillTable(this.mechanics(this.currentBoard, this.playerAI), this.playerAI);
ulaz.close();
}
}
public void resetGame(){
for (int i = 0; i < 9; i++){
this.currentBoard[i] = this.emptySpace;
}
for (int i = 0; i < 9; i++){
this.availableSpots[i] = i;
}
}
}
public static void main(String[] args){
TicTacToe game = new TicTacToe();
game.startGame();
}
}
Also, here's the exact errors I get:
Exception in thread "main" java.lang.StackOverflowError
at Packet.Logic$TicTacToe.mechanics(Logic.java:54)
at Packet.Logic$TicTacToe.mechanics(Logic.java:84)
at Packet.Logic$TicTacToe.mechanics(Logic.java:87)
at Packet.Logic$TicTacToe.mechanics(Logic.java:84)
at Packet.Logic$TicTacToe.mechanics(Logic.java:87)
at Packet.Logic$TicTacToe.mechanics(Logic.java:84)
at Packet.Logic$TicTacToe.mechanics(Logic.java:87)
After this part, these parts appear a bunch of times (at least 50)
at Packet.Logic$TicTacToe.mechanics(Logic.java:84)
at Packet.Logic$TicTacToe.mechanics(Logic.java:87)
Line 54:
if (win(newBoard,this.playerHuman)){
Line 84:
scores.add(this.mechanics(possibleBoard, this.playerHuman));
Line 87:
scores.add(this.mechanics(possibleBoard, this.playerAI));

This might or might not be a code issue, as Java is not a fully functional language with it comes to recursion, you might want to see this answer: Does Java 8 have tail call optimization?
Basically, a language that allows for unlimited-depth recursion has to have tail recursion optimization. With tail call optimization, if the return value is the result of exactly the same function with different parameters, the stack will get replaced instead of the new call being added to the stack.
If a language does not have tail call optimization, then you're limited by stack size in how deep your recursion goes, even if the recursive calls have correct termination conditions (note: I haven't analyzed the code in depth, so obviously there might be a problem in the recursive logic itself). If you want to tweak the stack size, use the -Xss Java runtime parameter. Generally, increasing the stack size is a good heuristic (although not a fool-proof method) of checking whether the fault is with the language or with your algorithm.

There's a couple of problems, but here's how you can debug this:
add a StringBuilder debug = new StringBuilder(); field to your class, then change the main loop like this:
int debugLen = debug.length();
debug.append("\nSetting ").append(availableSpotNumber).append(" to ").append(player);
possibleBoard[availableSpotNumber] = player;
try {
if (player == this.playerAI) {
scores.add(this.mechanics(possibleBoard, this.playerHuman));
} else {
scores.add(this.mechanics(possibleBoard, this.playerAI));
}
moves.add(availableSpotNumber);
} catch (StackOverflowError error) {
throw new StackOverflowError(debug.toString());
}
debug.setLength(debugLen);
possibleBoard[availableSpotNumber] = this.emptySpace;
Then you will see what is happening, which will give you a clue what to fix next. For example, the current version is doing this, for initial human move 1:
Setting 0 to 1
Setting 0 to 2
Setting 0 to 1
Setting 0 to 2
etc..
But, if you're too lazy, you can find a fixed version here.

Related

Android - An algorithm to check recursively if a map is solvable

I am making an android Hashikawekero puzzle game, I have implemented a algorithm to spawn nodes (Islands) at random positions using a 2-d array this works fine it creates the node at random position but most of the times the map cant be solved. The map nodes spawn at random.
BoardCreation.java Class - this generates the map.
package Island_and_Bridges.Hashi;
import android.annotation.TargetApi;
import android.os.Build;
import android.util.Log;
import java.util.Random;
import static junit.framework.Assert.*;
//This class Creates the map by random using a 2d array
public class BoardCreation {
// This class member is used for random initialization purposes.
static private final Random random = new Random();
// The difficulty levels.
private static final int EASY = 0;
static public final int MEDIUM = 1;
static public final int HARD = 2;
static public final int EMPTY = 0;
private static int ConnectionFingerprint(BoardElement start, BoardElement end) {
int x = start.row * 100 + start.col;
int y = end.row * 100 + end.col;
// Swap to get always the same fingerprint independent whether we are called
// start-end or end-start
if (x > y ) {
int temp = x;
x = y;
y = temp;
}
Log.d("", String.format("%d %d" , x ,y));
return x ^ y;
}
public class State {
// The elements of the board are stored in this array.
// A value defined by "EMPTY" means that its not set yet.
public BoardElement [][] board_elements = null;
public int [][] cell_occupied = null;
// The width of the board. We only assume squared boards.
public int board_width=0;
public State(int width) {
board_width = width;
board_elements = new BoardElement[width][width];
cell_occupied = new int[width][width];
}
public State CloneWithoutConnections() {
State newstate = new State(board_width);
if (board_elements != null) {
newstate.board_elements = new BoardElement[board_elements.length][board_elements.length];
for (int i = 0; i < board_elements.length; ++i) {
for (int j = 0; j < board_elements.length; ++j) {
if (board_elements[i][j] == null)
continue;
newstate.board_elements[i][j] = board_elements[i][j].clone();
}
}
}
if (cell_occupied != null) {
assert board_elements != null;
newstate.cell_occupied = new int[board_elements.length][board_elements.length];
for (int i = 0; i < board_elements.length; ++i) {
System.arraycopy(cell_occupied[i], 0, newstate.cell_occupied[i], 0, board_elements.length);
}
}
return newstate;
}
public void AddToBridgeCache(BoardElement first, BoardElement second) {
if (first == null || second == null) { return; }
final int fingerprint = ConnectionFingerprint(first, second);
Log.d(getClass().getName(),
String.format("Fingerprint of this bridge %d", fingerprint));
// mark the end points as occupied.
cell_occupied[first.row][first.col] = fingerprint;
cell_occupied[second.row][second.col] = fingerprint;
int dcol = second.col - first.col;
int drow = second.row - first.row;
if (first.row == second.row) {
for (int i = (int) (first.col + Math.signum(dcol)); i != second.col; i += Math.signum(dcol)) {
cell_occupied[first.row][i] = fingerprint;
String.format("deleting bridge");
}
} else {
assert first.col == second.col;
for (int i = (int) (first.row + Math.signum(drow)); i != second.row; i+= Math.signum(drow)) {
cell_occupied[i][first.col] = fingerprint;
}
}
}
} // end of state
private State current_state, old_state;
static private final int WIDTH_EASY = 7;
private void NewGame(int hardness) {
switch(hardness) {
case EASY:
Log.d(getClass().getName(), "Initializing new easy game");
InitializeEasy();
old_state = getCurrentState().CloneWithoutConnections();
break;
}
}
public void ResetGame() {
if (old_state != null) {
Log.d(getClass().getName(), "Setting board_elements to old_elements");
setCurrentState(old_state.CloneWithoutConnections());
} else {
Log.d(getClass().getName(), "old_lements are zero");
}
}
public BoardCreation(int hardness) {
NewGame(hardness);
}
public boolean TryAddNewBridge(BoardElement start, BoardElement end, int count) {
assertEquals(count, 1);
assert (start != null);
assert (end != null);
final int fingerprint = ConnectionFingerprint(start, end);
Log.d(getClass().getName(),
String.format("considering (%d,%d) and (%d,%d)", start.row,start.col, end.row,end.col));
if (start.row == end.row && start.col == end.col) {
Log.d(getClass().getName(), "Same nodes selected!");
return false;
}
assert count > 0;
int dcol = end.col - start.col;
int drow = end.row - start.row;
// It must be a vertical or horizontal bridge:
if (Math.abs(dcol) > 0 && Math.abs(drow) > 0) {
Log.d(getClass().getName(), "not a horizontal or vertical bridge.");
return false;
}
// First we check whether start and end elements can take the specified bridge counts.
int count_start = start.GetCurrentCount();
int count_end = end.GetCurrentCount();
if (count_start + count > start.max_connecting_bridges ||
count_end + count > end.max_connecting_bridges) {
Log.d(getClass().getName(), "This Bridge is not allowed");
return false;
}
Log.d(getClass().getName(),
String.format("Sums:%d # (%d,%d) and %d # (%d,%d)",
count_start, start.row, start.col,
count_end, end.row, end.col));
Connection start_connection = null;
Connection end_connection = null;
// Next we check whether we are crossing any lines.
if (start.row == end.row) {
for (int i = (int) (start.col + Math.signum(dcol)); i != end.col; i += Math.signum(dcol)) {
if (getCurrentState().cell_occupied[start.row][i] > 0 &&
getCurrentState().cell_occupied[start.row][i] != fingerprint) {
Log.d(getClass().getName(), "Crossing an occupied cell.");
return false;
}
}
assert start.col != end.col;
if (start.col > end.col) {
start.connecting_east = GetOrCreateConnection(end, start.connecting_east);
end.connecting_west = GetOrCreateConnection(start, end.connecting_west);
start_connection = start.connecting_east;
end_connection = end.connecting_west;
} else {
start.connecting_west = GetOrCreateConnection(end, start.connecting_west);
end.connecting_east = GetOrCreateConnection(start, end.connecting_east);
start_connection = start.connecting_west;
end_connection = end.connecting_east;
}
} else {
assert start.col == end.col;
for (int i = (int) (start.row + Math.signum(drow)); i != end.row ; i += Math.signum(drow)) {
if (getCurrentState().cell_occupied[i][start.col] > 0 &&
getCurrentState().cell_occupied[i][start.col] != fingerprint) {
Log.d(getClass().getName(), "Crossing an occupied cell.");
return false;
}
}
if (start.row > end.row ) {
start.connecting_north = GetOrCreateConnection(end, start.connecting_north);
end.connecting_south = GetOrCreateConnection(start, end.connecting_south);
start_connection = start.connecting_north;
end_connection = end.connecting_south;
} else {
start.connecting_south= GetOrCreateConnection(end, start.connecting_south);
end.connecting_north = GetOrCreateConnection(start, end.connecting_north);
start_connection = start.connecting_south;
end_connection = end.connecting_north;
}
}
start_connection.destination = end;
end_connection.destination = start;
start_connection.second += count;
end_connection.second += count;
getCurrentState().AddToBridgeCache(start, end);
Log.d(getClass().getName(),
String.format("New bridge added. Sums:%d # (%d,%d) and %d # (%d,%d)",
count_start, start.row,start.col,
count_end, end.row,end.col));
return true;
}
private Connection GetOrCreateConnection(
BoardElement end,
Connection connection) {
if (connection!= null) { return connection; }
return new Connection();
}
#TargetApi(Build.VERSION_CODES.N)
private void InitializeEasy() {
Random rand = new Random();
String[][] debug_board_state = new String[7][7];
setCurrentState(new State(WIDTH_EASY));
for (int row = 0; row < debug_board_state.length; row++) {
for (int column = 0; column < debug_board_state[row].length; column++) {
debug_board_state[row][column] = String.valueOf(rand.nextInt(5));
}
}
for (int row = 0; row < debug_board_state.length; row++) {
for (int column = 0; column < debug_board_state[row].length; column++) {
System.out.print(debug_board_state[row][column] + " ");
}
System.out.println();
}
for (int row = 0; row < WIDTH_EASY; ++row) {
for (int column = 0; column < WIDTH_EASY; ++column) {
getCurrentState().board_elements[row][column] = new BoardElement();
getCurrentState().board_elements[row][column].max_connecting_bridges = Integer.parseInt(debug_board_state[row][column]);
getCurrentState().board_elements[row][column].row = row;
getCurrentState().board_elements[row][column].col = column;
if (getCurrentState().board_elements[row][column].max_connecting_bridges > 0) {
getCurrentState().board_elements[row][column].is_island = true;
}
}
}
}
private void setCurrentState(State new_state) {
this.current_state = new_state;
}
public State getCurrentState() {
return current_state;
}
}
What algorithm could I use to make sure the Map can be Solved (Islands Connected with Bridges) before spawning the nodes.
This is what the map looks like (don't mind the design)
One thing to consider would be to start with a blank board. Place an island. Then place another island that can be connected to the first one (i.e. on one of the four cardinal directions). Connect the two with a bridge, and increment each island's count.
Now, pick one of the two islands and place another island that it can connect. Add the bridge and increment.
Continue in this way until you've placed the number of islands that you want to place.
The beauty here is that you start with an empty board, and during construction the board is always valid.
You'll have to ensure that you're not crossing bridges when you place new islands, but that's pretty easy to do, since you know where the existing bridges are.

Why do object arrays in my ArrayList fail to retain their values?

I am creating a program in Java to simulate evolution. The way I have it set up, each generation is composed of an array of Organism objects. Each of these arrays is an element in the ArrayList orgGenerations. Each generation, of which there could be any amount before all animals die, can have any amount of Organism objects.
For some reason, in my main loop when the generations are going by, I can have this code without errors, where allOrgs is the Organism array of the current generation and generationNumber is the number generations since the first.
orgGenerations.add(allOrgs);
printOrgs(orgGenerations.get(generationNumber));
printOrgs is a method to display an Organism array, where speed and strength are Organism Field variables:
public void printOrgs(Organism[] list)
{
for (int x=0; x<list.length; x++)
{
System.out.println ("For organism number: " + x + ", speed is: " + list[x].speed + ", and strength is " + list[x].strength + ".");
}
}
Later on, after this loop, when I am trying to retrieve the data to display, I call this very similar code:
printOrgs(orgGenerations.get(0));
This, and every other array in orgGenerations, return a null pointer exception on the print line of the for loop. Why are the Organism objects loosing their values?
Alright, here is all of the code from my main Simulation class. I admit, it might be sort of a mess. The parts that matter are the start and simulator methods. The battle ones are not really applicable to this problem. I think.
import java.awt.FlowLayout;
import java.util.*;
import javax.swing.JFrame;
public class Simulator {
//variables for general keeping track
static Organism[] allOrgs;
static ArrayList<Organism[]> orgGenerations = new ArrayList <Organism[]>();
ArrayList<Integer> battleList = new ArrayList<Integer>();
int deathCount;
boolean done;
boolean runOnce;
//setup
Simulator()
{
done = false;
Scanner asker = new Scanner(System.in);
System.out.println("Input number of organisms for the simulation: ");
int numOfOrgs = asker.nextInt();
asker.close();
Organism[] orgArray = new Organism[numOfOrgs];
for (int i=0; i<numOfOrgs; i++)
{
orgArray[i] = new Organism();
}
allOrgs = orgArray;
}
//graphsOrgs
public void graphOrgs() throws InterruptedException
{
JFrame f = new JFrame("Evolution");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(1000,500);
f.setVisible(true);
Drawer bars = new Drawer();
//System.out.println(orgGenerations.size());
for (int iterator=0;iterator<(orgGenerations.size()-1); iterator++)
{
printOrgs(orgGenerations.get(0));
//The 0 can be any number, no matter what I do it wont work
//System.out.println("first");
f.repaint();
bars.data = orgGenerations.get(iterator);
f.add(bars);
//System.out.println("before");
Thread.sleep(1000);
//System.out.println("end");
}
}
//prints all Orgs and their statistics
public void printOrgs(Organism[] list)
{
System.out.println("Number Of Organisms: " + list.length);
for (int x=0; x<list.length; x++)
{
System.out.println ("For organism number: " + x + ", speed is: " + list[x].speed + ", and strength is " + list[x].strength + ".");
}
System.out.println();
}
//general loop for the organisms lives
public void start(int reproductionTime) throws InterruptedException
{
int generationNumber = 0;
orgGenerations.add(allOrgs);
printOrgs(orgGenerations.get(0));
generationNumber++;
while(true)
{
deathCount = 0;
for(int j=0; j<reproductionTime; j++)
{
battleList.clear();
for(int m=0; m<allOrgs.length; m++)
{
if (allOrgs[m].alive == true)
oneYearBattleCheck(m);
}
battle();
}
reproduction();
if (done == true)
break;
orgGenerations.add(allOrgs);
printOrgs(orgGenerations.get(generationNumber));
generationNumber++;
}
printOrgs(orgGenerations.get(2));
}
//Checks if they have to fight this year
private void oneYearBattleCheck(int m)
{
Random chaos = new Random();
int speedMod = chaos.nextInt(((int)Math.ceil(allOrgs[m].speed/5.0))+1);
int speedSign = chaos.nextInt(2);
if (speedSign == 0)
speedSign--;
speedMod *= speedSign;
int speed = speedMod + allOrgs[m].speed;
if (speed <= 0)
speed=1;
Random encounter = new Random();
boolean battle = false;
int try1 =(encounter.nextInt(speed));
int try2 =(encounter.nextInt(speed));
int try3 =(encounter.nextInt(speed));
int try4 =(encounter.nextInt(speed));
if (try1 == 0 || try2 == 0 || try3 == 0 || try4 == 0 )
{
battle = true;
}
if(battle == true)
{
battleList.add(m);
}
}
//Creates the matches and runs the battle
private void battle()
{
Random rand = new Random();
if (battleList.size()%2 == 1)
{
int luckyDuck = rand.nextInt(battleList.size());
battleList.remove(luckyDuck);
}
for(int k=0; k<(battleList.size()-1);)
{
int competitor1 = rand.nextInt(battleList.size());
battleList.remove(competitor1);
int competitor2 = rand.nextInt(battleList.size());
battleList.remove(competitor2);
//Competitor 1 strength
int strengthMod = rand.nextInt(((int)Math.ceil(allOrgs[competitor1].strength/5.0))+1);
int strengthSign = rand.nextInt(2);
if (strengthSign == 0)
strengthSign--;
strengthMod *= strengthSign;
int comp1Strength = strengthMod + allOrgs[competitor1].strength;
//Competitor 2 strength
strengthMod = rand.nextInt(((int)Math.ceil(allOrgs[competitor2].strength/5.0))+1);
strengthSign = rand.nextInt(2);
if (strengthSign == 0)
strengthSign--;
strengthMod *= strengthSign;
int comp2Strength = strengthMod + allOrgs[competitor2].strength;
//Fight!
if (comp1Strength>comp2Strength)
{
allOrgs[competitor1].life ++;
allOrgs[competitor2].life --;
}
else if (comp2Strength>comp1Strength)
{
allOrgs[competitor2].life ++;
allOrgs[competitor1].life --;
}
if (allOrgs[competitor1].life == 0)
{
allOrgs[competitor1].alive = false;
deathCount++;
}
if (allOrgs[competitor2].life == 0)
{
allOrgs[competitor2].alive = false;
deathCount ++ ;
}
}
}
//New organisms
private void reproduction()
{
//System.out.println("Number of deaths: " + deathCount + "\n");
if (deathCount>=(allOrgs.length-2))
{
done = true;
return;
}
ArrayList<Organism> tempOrgs = new ArrayList<Organism>();
Random chooser = new Random();
int count = 0;
while(true)
{
int partner1 = 0;
int partner2 = 0;
boolean partnerIsAlive = false;
boolean unluckyDuck = false;
//choose partner1
while (partnerIsAlive == false)
{
partner1 = chooser.nextInt(allOrgs.length);
if (allOrgs[partner1] != null)
{
if (allOrgs[partner1].alive == true)
{
partnerIsAlive = true;
}
}
}
count++;
//System.out.println("Count 2: " + count);
partnerIsAlive = false;
//choose partner2
while (partnerIsAlive == false)
{
if (count+deathCount == (allOrgs.length))
{
unluckyDuck=true;
break;
}
partner2 = chooser.nextInt(allOrgs.length);
if (allOrgs[partner2] != null)
{
if (allOrgs[partner2].alive == true)
{
partnerIsAlive = true;
}
}
}
if (unluckyDuck == false)
count++;
//System.out.println("count 2: " + count);
if (unluckyDuck == false)
{
int numOfChildren = (chooser.nextInt(4)+1);
for (int d=0; d<numOfChildren; d++)
{
tempOrgs.add(new Organism(allOrgs[partner1].speed, allOrgs[partner2].speed, allOrgs[partner1].strength, allOrgs[partner2].strength ));
}
allOrgs[partner1] = null;
allOrgs[partner2] = null;
}
if (count+deathCount == (allOrgs.length))
{
Arrays.fill(allOrgs, null);
allOrgs = tempOrgs.toArray(new Organism[tempOrgs.size()-1]);
break;
}
//System.out.println(count);
}
}
}
Main method:
public class Runner {
public static void main(String[] args) throws InterruptedException {
Simulator sim = new Simulator();
int lifeSpan = 20;
sim.start(lifeSpan);
sim.graphOrgs();
}
}
Organism class:
import java.util.Random;
public class Organism {
static Random traitGenerator = new Random();
int life;
int speed;
int strength;
boolean alive;
Organism()
{
speed = (traitGenerator.nextInt(49)+1);
strength = (50-speed);
life = 5;
alive = true;
}
Organism(int strength1, int strength2, int speed1, int speed2)
{
Random gen = new Random();
int speedMod = gen.nextInt(((int)Math.ceil((speed1+speed2)/10.0))+1);
int speedSign = gen.nextInt(2);
if (speedSign == 0)
speedSign--;
speedMod *= speedSign;
//System.out.println(speedMod);
int strengthMod = gen.nextInt(((int)Math.ceil((strength1+strength2)/10.0))+1);
int strengthSign = gen.nextInt(2);
if (strengthSign == 0)
strengthSign--;
strengthMod *= strengthSign;
//System.out.println(strengthMod);
strength = (((int)((strength1+strength2)/2.0))+ strengthMod);
speed = (((int)((speed1+speed2)/2.0))+ speedMod);
alive = true;
life = 5;
}
}
The problem lies in the graphOrgs class when I try to print to check if it is working in preparation for graphing the results. This is when it returns the error. When I try placing the print code in other places in the Simulator class the same thing occurs, a null pointer error. This happens even if it is just after the for loop where the element has been established.
You have code that sets to null elements in your allOrgs array.
allOrgs[partner1] = null;
allOrgs[partner2] = null;
Your orgGenerations list contains the same allOrgs instance multiple times.
Therefore, when you write allOrgs[partner1] = null, the partner1'th element becomes null in all the list elements of orgGenerations, which is why the print method fails.
You should create a copy of the array (you can use Arrays.copy) each time you add a new generation to the list (and consider also creating copies of the Organism instances, if you want each generation to record the past state of the Organisms and not their final state).

Why am i getting the error java.lang.NullPointerException in my battleship game? [duplicate]

This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 8 years ago.
I'm trying to code a battleship game and im getting a weird error when i run it: Exception in thread "main" java.lang.NullPointerException
at Caculations.findShip(Caculations.java:29)
at Board.main(Board.java:60)
Please help im stuck and i dont know how to continue! Here is my code: (Note, its in 2 class files in my eclipse work enviorment)
public class Board {
public static void main(String[] args) {
boolean continuePlay = true;
int[][] board = new int[10][10]; // creating 2d array 'board'
char[][] boardGraphical = new char[10][10]; // creating 2d array 'board
// this time the visual
for (int x = 0; x < 10; x++) { // for within for //initializing elements
// in both boards using double for
// method
for (int y = 0; y < 10; y++) {
board[x][y] = 0;
boardGraphical[x][y] = 'o';
System.out.println("Board element " + x + " " + y // printing
// initialized
// elements
// here
+ " initialized");
}
}
/*
* 1) Make user ships 1 and computer ships 2 (all numbers other than 0 =
* true)
*
* 2) Make it where if the computer gets a hit on a '1' than it sets
* that value to like a 3 or something so it knows when the ship is
* sunk. So in a if it does if([x][y] && [x][y])
* System.out.println("You sunk my ship!");
*
* 3) REMEMBER YOU CAN DO MULTIPLE IFS INSIDE IFS FOR MULTIPLE
* CONDITIONS. 4) declare ships here!
*
* 5) PUT STUFF IN A WHILE LOOP SO COMP CAN KEEP GOING
*/
board[3][3] = 1; // declaring a battleship. Very important.
board[3][4] = 1;
board[3][5] = 1;
boardGraphical[3][3] = 's';
boardGraphical[3][4] = 's';
boardGraphical[3][5] = 's';
while (continuePlay == true) { // while loop so that computer keeps
// guessing
// WITHIN THIS LOOP KEEP REPRINTING THE BOARD
double computerChoiceXd = Math.floor(Math.random() * 10); // using
// Math.random
// functions
// for
// computers
// first
// guess
// to be
// a
// random
// num
double computerChoiceYd = Math.floor(Math.random() * 10);
int computerChoiceX = (int) computerChoiceXd;
int computerChoiceY = (int) computerChoiceYd;
if (board[computerChoiceX][computerChoiceY] == 1) { // checking if
// math.random
// landed on a
// ship point
System.out.println("Computer got a hit at " + computerChoiceX
+ " " + computerChoiceY);
board[computerChoiceX][computerChoiceY] = 2; // setting the
// point as 2 or
// 'hit'
boardGraphical[computerChoiceX][computerChoiceY] = 'H';
for (int row = 0; row < 10; row++) { // printing out graphical
// board using the same
// method as when
// intializing
for (int col = 0; col < 10; col++) {
System.out.print(boardGraphical[row][col]);
}
System.out.println(" "); // spacer for printing
}
Caculations test = new Caculations(computerChoiceX, // Creating
// a new
// object of
// calculation
computerChoiceY, board);
test.findShip();
// break;
if (board[3][3] == 2) { // checking to see if ship is sunk using
// a triple if statement
if (board[3][4] == 2) {
if (board[3][5] == 2) {
System.out.println("Battleship sunk!");
continuePlay = false; // if so than it breaks out of
// loop to end the game
break;
}
}
}
} else if (board[computerChoiceX][computerChoiceY] == 0) { // otherwise
// if
// the
// area
// is a
// 0 or
// 'unmarked'
System.out.println("Computer missed at " + computerChoiceX
+ " " + computerChoiceY);
boardGraphical[computerChoiceX][computerChoiceY] = 'x'; // mark
// area
// as a
// miss
for (int row = 0; row < 10; row++) { // print out board
for (int col = 0; col < 10; col++) {
System.out.print(boardGraphical[row][col]);
}
System.out.println(" ");
}
}
}
}
}
public class Caculations {
int xValue;
int yValue;
int[][] myArray;
int[][] storage = new int[10][10];
boolean xAxisChangeP;
boolean yAxisChangeP;
boolean xAxisChangeN;
boolean yAxisChangeN;
boolean notSunk;
Caculations(int x, int y, int[][] myArray) {
xValue = x;
yValue = y;
xAxisChangeP = true;
yAxisChangeP = true;
xAxisChangeN = true;
yAxisChangeN = true;
notSunk = true;
}
void findShip() {
while (notSunk == true) {
// 1
while (xAxisChangeP == true) {
if (myArray[xValue + 1][yValue] == 1) {
myArray[xValue + 1][yValue] = 2;
if (myArray[3][3] == 2) {
if (myArray[3][4] == 2) {
if (myArray[3][5] == 2) {
System.out.println("Battleship sunk!");
notSunk = false;
}
}
}
continue;
}
else {
xAxisChangeP = false;
}
}
while (xAxisChangeN == true) {
if (myArray[xValue - 1][yValue] == 1) {
myArray[xValue - 1][yValue] = 2;
if (myArray[3][3] == 2) {
if (myArray[3][4] == 2) {
if (myArray[3][5] == 2) {
System.out.println("Battleship sunk!");
notSunk = false;
}
}
}
continue;
}
else {
xAxisChangeN = false;
}
}
// 1
while (yAxisChangeP == true) {
if (myArray[xValue][yValue + 1] == 1) {
myArray[xValue][yValue + 1] = 2;
if (myArray[3][3] == 2) {
if (myArray[3][4] == 2) {
if (myArray[3][5] == 2) {
System.out.println("Battleship sunk!");
notSunk = false;
}
}
}
continue;
}
else {
yAxisChangeP = false;
}
}
while (yAxisChangeN == true) {
if (myArray[xValue][yValue - 1] == 1) {
myArray[xValue][yValue - 1] = 2;
if (myArray[3][3] == 2) {
if (myArray[3][4] == 2) {
if (myArray[3][5] == 2) {
System.out.println("Battleship sunk!");
notSunk = false;
}
}
}
continue;
}
else {
yAxisChangeN = false;
}
}
}
}
}
Have you initialized myarray? Best is debug your code to see, which statement throws the exception. In eclipse you can add NullPointerExeption as your breakpoint and debug.
You use myArray, but you never initialize it.
public class Caculations {
int xValue;
int yValue;
int[][] myArray; // array declared but never initialized
// ....
void findShip() {
while (notSunk == true) {
// 1
while (xAxisChangeP == true) {
if (myArray[xValue + 1][yValue] == 1) // then you use it here
Solution: initialize variables before using.
More importantly, you need to learn the general concepts of how to debug a NPE (NullPointerException). You should critically read your exception's stacktrace to find the line of code at fault, the line that throws the exception, and then inspect that line carefully, find out which variable is null, and then trace back into your code to see why. You will run into these again and again, trust me.
In your constructor for Calculations, you never initialized myArray:
Caculations(int x, int y, int[][] myArray) {
xValue = x;
yValue = y;
xAxisChangeP = true;
yAxisChangeP = true;
xAxisChangeN = true;
yAxisChangeN = true;
notSunk = true;
this.myArray = myArray; //Add this line
}
This is a direct answer to your problem, but all in all, you should do some research regarding the meaning behind the exception that was thrown so you understand what it means.
Why this problem happened
In Java, all objects and primitives, if not initialized manually, are given a default value.
For default values of primitives, check this: Primitive Data Types
In case of non-primitive types - such as Object, String, Thread, etc, as well as any user-defined class (i.e. Calculations) and also arrays (i.e. myArray) - the default value is null.
With that in mind, inside your constructor, as exemplified above, you have not initialized myArray, which means that when this variable was accessed for the first time, the value returned was null.
So, what's the problem with null?
Well, by itself, it does no harm. It's there. It doesn't bother you. Until you decide to use a variable that doesn't have an object assigned to it, but somehow you forget that and treat it as if it held something like a String or an array.
That's when Java will tell you: "Hey! There's no object here. I can't work like this. Let's throw an exception!".

Nothing returned from 2D arrays for a simple tic-tac-toe board

public String tictactoe(String game)
{
game = game.toUpperCase();
char[][] board = new char[3][3];
int loc = 0;
for( char r = 0; r < board.length; r++ )
{
for( char c = 0; c < board[r].length; c++)
{ board[r][c] = game.charAt(loc);
loc++;
}
}
if ((board[0][0] =='X' && board[0][1] =='X' && board[0][2] =='X') ||
(board[1][0] =='X' && board[1][1] =='X' && board[1][2] =='X') ||
(board[2][0] =='X' && board[2][1] =='X' && board[2][2] =='X'))
return("Player 1 wins horizontally!");
else if ((board[0][0] =='O' && board[0][1] =='O' && board[0][2] =='O') ||
(board[1][0] =='O' && board[1][1] =='O' && board[1][2] =='O') ||
(board[2][0] =='O' && board[2][1] =='O' && board[2][2] =='O'))
return("Player 2 wins horizontally!");
else if ((board[0][0] =='X' && board[1][0] =='X' && board[2][0] =='X') ||
(board[0][1] =='X' && board[1][1] =='X' && board[2][1] =='X') ||
(board[0][2] =='X' && board[1][2] =='X' && board[2][2] =='X'))
return("PLayer 2 wins vertically!");
else if ((board[0][0] =='O' && board[1][0] =='O' && board[2][0] =='O') ||
(board[0][1] =='O' && board[1][1] =='O' && board[2][1] =='O') ||
(board[0][2] =='O' && board[1][2] =='O' && board[2][2] =='O'))
return("Player 2 wins vertically!");
return "Tie!";
}
Above is my code for this method. It reads in a 9 letter string for a tic-tac-toe game and then puts it one by one into a 2D array. I unfortunately have to use this method because this is unfortunately what we're learning, and I've continuously had to bother my teacher about this...
The if statements check each row and column for a winner (I realize it does not check the horizontals). My issue here is nothing is being returned, even though it is supposed to return a string. As I have said I have to use 2D-arrays here. The goal is to check a tic-tac-toe game and return whom as won.
Consider replacing each instance of this
if(board[0][0] =='X' && board[0][1] =='X' && board[0][2] =='X') ||
...
With this function:
public boolean isRowFilledWith(int row_index, char x_or_o) {
return (board[row_idx][0] == x_or_o &&
board[row_idx][1] == x_or_o &&
board[row_idx][2] == x_or_o);
}
Now, to see if the top-most row contains all x-s, call it with
if(isRowFilledWith(0, 'X') ||
...
This will make your code a lot more concise, easier to read and easier to debug.
Try doing:
System.out.println(fooObject.tictactoe("XXXXXXOOO");
inside your main argument (public static void main(String[] args)
I suspect you've confused return and System.out.println. The console will not print anything if returned. Instead, returning is what is thrown at the computer after the function is called. Try printing the result of calling the function...THAT is what is returned to the computer and is only going to be visible if you print it. There may not be any error in your code.
First off, to directly answer your question:
Your function works fine, I locally tested it. I tried both horizontal, and vertical, with both x and o. It must be something you are doing with the caller.
The biggest problem with this code is clarity. You can do quite a bit about clarity by separating the logic into groups.
Clarity can be achieved by making application specific objects.
If you have an object to represent a move, a game, and the marker... you can drastically simplify you code and most of all make it easy to read and debug.
I went ahead and implemented tic tac toe just to kinda explain what I mean by clarity.
Any time you start having massive if else chains and block return statements you may need to take the extra time to break up the problem.
This is fully compililable and I think if you debug through it a few times you will find it is much easier to spot mistakes.
It is also easy to ajust the game... Right now, we have 2 players and a 3 by 3 game.
However, we have extra constructors to adjust the size of the game and to give the gamers some options on what they want thier tic tac toe game to look like.
The major reason to do this is for clarity and debugging.
It is always easier to find issues if you have problem specific methods and problem specific objects.
Below is a fully compilable console application for tic tac toe. In its current form, it plays 100 random 3 by 3 games and prints the results of all 100 games.
The application is also capable of taking your string input as console input and processing the moves.
Notice that we are still using two dimensional arrays, but we hidden away the complexity of using them by encapsulating the array in the Board object.
We also gave a move object so we can store moves to play later. This was useful in creating the loop to play many games, as all the facts about the move are contained in a single object.
Since we created three separate methods for validating the move input. We have a much clearer explanation of what we are validating and why we are doing it.
Most debuggers will also show the objects toString method, so by embedding the game display within the toString() we are allowed to see what the board looks like when we are debugging.
The trick to making clean code that doesn't require alot of comments is to attempt to split out any complexity to its own unit.
You probably will cover more about units of work later, but just keep it in mind, the more simple a method, the more reusable it is. The more single purpose a class is, the better and clearer it is.
My Example:
Mark.java
-This enum is the representation of the mark used by a player to signify their move.
public enum Mark{
X, Y;
}
TicTacToe.java
-This class is your main class
it handles gathering user input, cleaning it, processing it, and displaying the output to the user
Notice that I am not doing game display anywhere else but the main method. this keeps all display issues localized to one spot. The advantage to breaking up your code into specific objects is knowing where to fix something when it breaks.
package com.clinkworks.example;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.clinkworks.example.Move;
import com.clinkworks.example.Board;
import com.clinkworks.example.Mark;
public class TicTacToe {
//this variable is simply used for output. Since we cant save CAT in the mark enum, we
// use it to hold cats games.
private static final String catsGameIdentity = "CAT";
//this map contains the counts of wins for each symbol
//it also keeps track of ties and stores it in the map as "CAT"
//soo.. gamesToWinsMap.get("CAT") will produce the amount of ties.
private static final Map<String, Integer> gamesToWinsMap = new HashMap<String, Integer>();
public static void main(String[] args){
String game = args[0].toUpperCase(); //better ways to do this and no validation here.. but it will work
Board board = new Board();
//holds our current place in the string passed in from teh console
int currentStringLocation = 0;
for(int row = 0; row < 3; row++){ //loop through the rows
for(int column = 0; column < 3; column++){ //for each row loop through the columns
if(gameOver(board)){ // we don't care about the rest of the input if the game was won
break;
}
//convert the symbol to a string for use in teh ValueOf function in the enum class
String symbol = "" + game.charAt(currentStringLocation); //better than String.valueOf imho
//allow spaces to represent un marked places
if(" ".equals(symbol)){
currentStringLocation++; //this accounts for spaces in our input
break;
}
//convert the string to a Mark enum... over use of strings is a very very bad practice
Mark nextMarkToPlace = Mark.valueOf(symbol);
Move move = new Move(row, column, nextMarkToPlace); //we create a move object that encapsulates the complexity of placing the mark on the game board
board.play(move); //the game already knows how to play itself, just let it
currentStringLocation++; //increment the posision.
}
}
//since you may not have won the game, or gave a complete string for a cats game,
// lets at least display the board for debugging reasons.
if(board.movesLeft() > 0){
System.out.println("Board isn't finished, but here is what it looks like basd on your input: ");
System.out.println(board);
}
}
//call me main if you want to see what I do
public static void main2(String[] args) {
//lets play 100 games and see the wins and ties
playGames(100);
System.out.println("Number wins by X: " + gamesToWinsMap.get(Mark.X.name()));
System.out.println("Number wins by O: " + gamesToWinsMap.get(Mark.O.name()));
System.out.println("Number of ties: " + gamesToWinsMap.get(catsGameIdentity));
}
public static void playGames(int count) {
//play a new game each iteration, in our example, count = 100;
for (int i = 0; i < count; i++) {
playGame();
}
}
public static void playGame() {
//create a new game board. this initalizes our 2d array and lets the complexity of handling that
// array be deligated to the board object.
Board board = new Board();
//we are going to generate a random list of moves. Heres where we are goign to store it
List<Move> moves = new ArrayList<Move>();
//we are creating moves for each space on the board.
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
moves.add(new Move(row, col));
}
}
//randomize the move list
Collections.shuffle(moves);
//do each move
for (Move move : moves) {
board.play(move);
if(gameOver(board)){
break;
}
}
}
public static boolean gameOver(Board board){
if (board.whoWon() != null) {
System.out.println("Player with the mark: " + board.whoWon() + " won the game!");
System.out.println(board);
Integer winCount = gamesToWinsMap.get(board.whoWon().name());
winCount = winCount == null ? 1 : winCount + 1;
gamesToWinsMap.put(board.whoWon().name(), winCount);
return true;
} else if (board.movesLeft() == 0) {
System.out.println("It was a cats game!!");
System.out.println(board);
Integer catCount = gamesToWinsMap.get(catsGameIdentity);
catCount = catCount == null ? 1 : catCount + 1;
gamesToWinsMap.put(catsGameIdentity, catCount);
return true;
}
return false;
}
Move.java
-This class is responsible for ensuring that only integers are passed to the game board
and for telling the game board who is doing the move (optional)
package com.clinkworks.example;
import com.clinkworks.example.Mark;
public class Move {
private int row;
private int column;
private Mark forcedMark;
public Move(int row, int column) {
this.row = row;
this.column = column;
}
//the board already knows who should be next. only use this constructor to override
// what symbol to put on the game board
public Move(int row, int column, Mark markToPlace){
this.row = row;
this.column = column;
forcedMark = markToPlace;
}
public int getRow() {
return row;
}
public int getColumn() {
return column;
}
public Mark getMark(){
return forcedMark;
}
}
Board.java
-This is where all the state of the board is managed, who is the next player, who won, and
is there any moves left to play. It also ensures that only valid moves are played.
package com.clinkworks.example;
import com.clinkworks.example.Mark;
import com.clinkworks.example.Move;
public class Board {
private final int rowSize;
private final int columnSize;
private final Mark[][] gameBoard;
private Mark currentMark;
private Mark winningMark;
/**
* This constructor defaults the starting player to X with a 3 by 3
* game.
*/
public Board() {
gameBoard = new Mark[3][3];
currentMark = Mark.X; // X always goes first ;P
winningMark = null;
this.rowSize = 3;
this.columnSize = 3;
}
/**
* This constructor defaults the starting player to X, and lets the
* board size be adjusted
*/
public Board(int rowSize, int columnSize) {
gameBoard = new Mark[rowSize][columnSize];
currentMark = Mark.X; // X always goes first ;P
winningMark = null;
this.rowSize = getRowSize();
this.columnSize = columnSize;
}
/**
* this constructor allows the players to choose who goes first on a 3
* by 3 board.
*
* #param firstPlayer
*/
public Board(Mark firstPlayer) {
gameBoard = new Mark[3][3];
currentMark = firstPlayer; // Let the player choose
winningMark = null;
rowSize = 3;
columnSize = 3;
}
/**
* this constructor allows the players to choose who goes first and to
* choose the size of the board.
*
* #param firstPlayer
*/
public Board(Mark firstPlayer, int rowSize, int columnSize) {
gameBoard = new Mark[getRowSize()][columnSize];
currentMark = firstPlayer; // Let the player choose
winningMark = null;
this.rowSize = rowSize;
this.columnSize = columnSize;
}
/**
*
* #return the amount of empty spaces remaining on the game board, or if theres a winning player, zero.
*/
public int movesLeft() {
if(whoWon() != null){
return 0;
}
int moveCount = 0;
for (int x = 0; x < getRowSize(); x++) {
for (int y = 0; y < getColumnSize(); y++) {
moveCount += getMarkAt(x, y) == null ? 1 : 0;
}
}
return moveCount;
}
/**
* If someone won, this will return the winning player.
*
* #return the winning player
*/
public Mark whoWon() {
return winningMark;
}
/**
* This move allows the next player to choose where to place their mark.
* if a move is played without a player given, it will use the current player variable
*
* as a side affect, the next player in rotation is chosen.
*
* #param Move
* #return if the game is over, play will return true, otherwise false.
*/
public boolean play(Move move) {
if (!validMove(move)) {
// always fail early
throw new IllegalStateException("Cannot play " + currentMark + " at " + move.getRow() + ", " + move.getColumn() + "\n" + toString());
}
doMove(move);
boolean playerWon = isWinningMove(move);
if (playerWon) {
winningMark = currentMark;
return true;
}
togglePlayer();
boolean outOfMoves = movesLeft() <= 0;
return outOfMoves;
}
public Mark lastMarkPlayed() {
return currentMark;
}
public int getRowSize() {
return rowSize;
}
public int getColumnSize() {
return columnSize;
}
public Mark getCurrentPlayer() {
return currentMark;
}
public Mark getMarkAt(int row, int column) {
return gameBoard[row][column];
}
private void doMove(Move move) {
if(move.getMark() != null){
currentMark = move.getMark();
}
gameBoard[move.getRow()][move.getColumn()] = getCurrentPlayer();
}
private void togglePlayer() {
if (currentMark == Mark.X) {
currentMark = Mark.O;
} else {
currentMark = Mark.X;
}
}
/**
* A valid move is a move where the row and the column are within boundries
* and no move has been made that the location specified by the move.
*/
private boolean validMove(Move move) {
boolean noMarkAtIndex = false;
boolean indexesAreOk = move.getRow() >= 0 || move.getRow() < getRowSize();
indexesAreOk = indexesAreOk && move.getColumn() >= 0 || move.getColumn() < getColumnSize();
if (indexesAreOk) {
noMarkAtIndex = getMarkAt(move.getRow(), move.getColumn()) == null;
}
return indexesAreOk && noMarkAtIndex;
}
private boolean isWinningMove(Move move) {
// since we check to see if the player won on each move
// we are safe to simply check the last move
return winsDown(move) || winsAcross(move) || winsDiagnally(move);
}
private boolean winsDown(Move move) {
boolean matchesColumn = true;
for (int i = 0; i < getColumnSize(); i++) {
Mark markOnCol = getMarkAt(move.getRow(), i);
if (markOnCol != getCurrentPlayer()) {
matchesColumn = false;
break;
}
}
return matchesColumn;
}
private boolean winsAcross(Move move) {
boolean matchesRow = true;
for (int i = 0; i < getRowSize(); i++) {
Mark markOnRow = getMarkAt(i, move.getColumn());
if (markOnRow != getCurrentPlayer()) {
matchesRow = false;
break;
}
}
return matchesRow;
}
private boolean winsDiagnally(Move move) {
// diagnals we only care about x and y being teh same...
// only perfect squares can have diagnals
// so we check (0,0)(1,1)(2,2) .. etc
boolean matchesDiagnal = false;
if (isOnDiagnal(move.getRow(), move.getColumn())) {
matchesDiagnal = true;
for (int i = 0; i < getRowSize(); i++) {
Mark markOnDiagnal = getMarkAt(i, i);
if (markOnDiagnal != getCurrentPlayer()) {
matchesDiagnal = false;
break;
}
}
}
return matchesDiagnal;
}
private boolean isOnDiagnal(int x, int y) {
if (boardIsAMagicSquare()) {
return x == y;
} else {
return false;
}
}
private boolean boardIsAMagicSquare() {
return getRowSize() == getColumnSize();
}
//prints out the board in a nice to view display
public String toString() {
StringBuffer stringBuffer = new StringBuffer();
for(int y = 0; y < getColumnSize(); y++) {
for(int x = 0; x < getRowSize(); x++) {
Mark mark = getMarkAt(x, y);
String markToPrint = "";
if (mark == null) {
markToPrint = " ";
} else {
markToPrint = mark.name();
}
stringBuffer.append("|").append(markToPrint);
}
stringBuffer.append("|\n");
}
return stringBuffer.toString();
}
}
}

Uno Project: Making sure that a illegal plays return correctly?

I am writing a chunk of program to play Uno with other classes. My Uno project is almost finished, but I need to be sure that the part which checks to make sure that on moves after a Wild Card is played, I play either a legal card or return a -1. Here is my code:
import java.util.*;
public class AlexaL_UnoPlayer implements UnoPlayer
{
public int play(List<Card> hand, Card upCard, Color calledColor, GameState state)
{
int play = -1;
boolean haveWild = false;
boolean matchesWildCall = false;
int indexOfWild = 0;
//turn number of cards all players are holding into ints for later use
int[] array = state.getNumCardsInHandsOfUpcomingPlayers();
int playerNext = array[0];
int playerTwoNext = array[1];
int playerBefore = array[2];
Color upCardColor = upCard.getColor();
for(int i = 0; i < hand.size(); i++)
{
//see if I have any wilds
if(hand.get(i).getRank().equals(Rank.WILD) || hand.get(i).getRank().equals (Rank.WILD_D4))
{
haveWild = true;
indexOfWild = i;
}
//set upCard color to calledColor if wild or wild_d4 are played
if (upCard.getRank().equals(Rank.WILD) || upCard.getRank().equals(Rank.WILD_D4))
{
upCardColor = calledColor;
}
//always play a card matching rank of upCard, if possible, or play the first in hand which matches color
if(hand.get(i).getColor().equals(upCardColor))
{
if(hand.get(i).getNumber() == upCard.getNumber())
{
play = i;
}
}
//if cornered(no matching number or color), play a wild
else if(haveWild == true)
{
play = indexOfWild;
}
//hold reverse cards until person next after me has less cards than person before me
if(hand.get(i).getRank().equals(Rank.REVERSE) && playerNext < playerBefore)
{
play = i;
}
//play skips when person next to me has less cards than me
if((hand.get(i).getRank().equals(Rank.SKIP) || hand.get(i).getRank().equals(Rank.DRAW_TWO)) && playerNext < hand.size())
{
play = i;
}
}
return play;
}
public Color callColor(List<Card> hand)
{
//strategy: change the color to the one i'm holding the most of
Color changeTo = Color.GREEN;
int numBlues = 0;
int numGreens = 0;
int numReds = 0;
int numYellows = 0;
//find out how many of each color i'm holding
for(int i = 0; i < hand.size(); i++)
{
if(hand.get(i).getColor().equals(Color.BLUE))
{
numBlues++;
}
else if(hand.get(i).getColor().equals(Color.RED))
{
numReds++;
}
else if(hand.get(i).getColor().equals(Color.GREEN))
{
numGreens++;
}
else if(hand.get(i).getColor().equals(Color.YELLOW))
{
numYellows++;
}
}
//find out which i'm holding the most of and call that color
//if no majority, return my favorite color(green)
if(numBlues > numReds && numBlues > numGreens && numBlues > numYellows)
{
changeTo = Color.BLUE;
}
else if(numReds > numBlues && numReds > numGreens && numReds > numYellows)
{
changeTo = Color.RED;
}
else if(numGreens > numBlues && numGreens > numYellows && numGreens > numReds)
{
changeTo = Color.GREEN;
}
else if(numYellows > numBlues && numYellows > numGreens && numYellows > numReds)
{
changeTo = Color.YELLOW;
}
else
{
changeTo = Color.GREEN;
}
return changeTo;
}
}
For some reason, my output is telling me this:
You were given this hand:
0. G7
1. G5
2. G+2
and the up card was: W
and the called color was: YELLOW
and you (wrongly) returned 2.
Valid plays would have included: -1
Can anyone provide some insight on why I am getting this error and how to fix it? Much appreciated!
Based on just this code, it is hard to say what is wrong with your implementation.
Did you try step-by-step debugging of your play function?
Alternately, it would be beneficial to instrument your code with more trace statements to better figure out where the issue might be occurring.
For example:
Every where your play variable gets assigned a value, print its value to console with some context of where you are in code.
if(hand.get(i).getNumber() == upCard.getNumber())
{
play = i;
System.out.println("number match step: play = " + play ); // You can add such a line
}
This should give you an idea as to when play's value is getting mutated to 2 unexpectedly.
Just clutching at straws here, but what are the expected values of getColor and getNumber for wild cards.
In case you have them set to Green or 2 respectively, it might explain the output you are seeing because of the following fragment of code.
//always play a card matching rank of upCard, if possible, or play the first in hand which matches color
if(hand.get(i).getNumber() == upCard.getNumber())
{
play = i;
}
else if(hand.get(i).getColor() == upCardColor)
{
play = i;
}

Categories