I am trying to create a program that does the game TicTacToe. I have finished creating
all the methods and I just need to create the driver program. Before creating the
driver program, I tried to just print the board along with a character but I don't
think my methods are correct. Here is what my error looks like:
java.lang.ArrayIndexOutOfBoundsException: 3
at TicTacToeBoard.move(TicTacToeBoard.java:75)
at TicTacToe.main(TicTacToe.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at edu.rice.cs.drjava.model.compiler.JavacCompiler.runCommand(JavacCompiler.java:271)
Here are my two programs:
This is my driver program that I can't seem to complete. The last thing that will be shown is the
template so that you have the idea of how each program works.
class TicTacToe
{
public static void main(String [] args)
{
//System.out.println("Welcome! Tic-Tac-Toe is a two player game.");
//System.out.println("Enter player one's name: ");
TicTacToeBoard game = new TicTacToeBoard();
System.out.println(game.toString());
//int count = 0;
game.move('x', 1, 3);
// game.move('o', 1, 1);
/* while (game.gameWon() !true || count != 9)
{
System.out.print(game.move());
System.out.print(game.isEmpty());
}*/
}
}
This is where all the methods are......
class TicTacToeBoard
{
private char [][] board = new char[3][3];
String b;
// This a new constructor that creates a tic-tac-toe board
public TicTacToeBoard()
{
for (int rows = 0; rows < board.length; rows++)// creates rows
{
for (int columns = 0; columns <board[rows].length;columns++)// creates columns
{
//System.out.println("| ");
board[rows][columns] = ' ';
//System.out.println(" |\n" );
}
}
}
// creates a string form of the tic-tac-toe board and allows the user
// to access it during the game.
public String toString()
{
String b = "";
// creates a vertical bar at the beginning and the end of each row
for (int rows = 0; rows < board.length; rows++)
{
b += "| ";
// adds a space for each row and column character in tic-tac-toe board.
for (int columns = 0; columns < board[rows].length; columns++)
{
b += board[rows][columns] + " ";
}
b += "|\n";// prints a | space space space | and breaks off to create two new lines.
}
return b; // prints the tic-tac-toe board to be accessed by the user.
}
String move(char x, int rows, int columns)
{
String b = "";
// creates a vertical bar at the beginning and the end of each row
for (int r = 0; r < board.length; r++)
{
b += "| ";
for (int c = 0; c < board[r].length; c++)
{
b += board[r][c] + " "; //prints 3 spaces on each line.
// prints string character from user input if row and column not equal to zero
if (board[rows - 1][columns - 1] >= 0 && board[rows - 1][columns - 1] <= 2 )
{
board[rows - 1][columns - 1] = x;// prints character in the specified index from user input
b += board[rows - 1][columns - 1];// prints out the board and the new character in specified space.
}
else if (board[rows - 1][columns - 1] < 0) // makes user pick another choice
return "ILLEGAL MOVE, TRY AGAIN!";
// adds a space for each row and column character in tic-tac-toe board.
}
b += "|\n";// prints a | space space space | and breaks off to create two new lines.
}
return b; // prints the tic-tac-toe board to be accessed by the user.
}
// checks if a space character is empty
void isEmpty(char x, int row, int col)
{
if (board [row - 1][col - 1] == ' ')
board[row - 1][col - 1] = x;
else // makes user pick another row and column if space character is not empty
System.out.println("ILLEGAL CHOICE, PICK AGAIN!");
}
// checks if game is won
public boolean gameWon(int row, int col)
{
if ((board[2][0] == board[1][1]) && (board[2][0] == board[0][2]))
return true;
else if ((board[2][0] != board[1][1]) && (board[2][0] != board[0][2]))
return false;
if ((board[2][2] == board[1][1])&& (board[2][2] == board[0][0]))
return true;
else if ((board[2][2] != board[1][1])&& (board[2][2] != board[0][0]))
return false;
if ((board[0][0] == board[1][0]) && (board[0][0] == board[2][0]))
return true;
else if ((board[0][0] != board[1][0]) && (board[0][0] != board[2][0]))
return false;
if ((board[0][1] == board[1][1]) && (board[0][1] == board[2][1]))
return true;
else if ((board[0][1] != board[1][1]) && (board[0][1] != board[2][1]))
return false;
if ((board[0][2] == board[1][2]) && (board[0][2] == board[2][2]))
return true;
else if ((board[0][2] != board[1][2]) && (board[0][2] != board[2][2]))
return false;
if ((board[0][0] == board[0][1]) && (board[0][0] == board[0][2]))
return true;
else if ((board[0][0] != board[0][1]) && (board[0][0] != board[0][2]))
return false;
if ((board[1][0] == board[1][1]) && (board[1][0] == board[1][2]))
return true;
else if ((board[1][0] != board[1][1]) && (board[1][0] != board[1][2]))
return false;
if ((board[2][0] == board[2][1]) && (board[2][0] == board[2][2]))
return true;
else
return false;
}
}
Here is the template for the whole thing!!!!!
class TicTacToe
{
public static void main (String [] args)
{
TicTacToeBoard b = new TicTacToeBoard();
while (game not over)
{
swtich player
increment turn counter
until user enters a valid move
{
prompt for move
}
make move
b.makeMove (player, row, col);
print board
System.out.println(b);
}
print outcome
}
}
class TicTacToeBoard
{
private char [][] board = ...;
public TicTacToeBoard()
{
initialize board with spaces
}
public void makeMove (char c, int row, int col)
{
store symbol in specified position
}
public boolean isEmpty(int row, int col)
{
return true if square is unfilled
}
public boolean gameWon()
{
check board for a win
}
public String toString ()
{
return String representation of board
}
}
Programming languages are unforgiving for errors and force rigor and care on us.
Your code is quite difficult for us to read and thus for both us and you to debug, starting with your indentation which is all over the place, but there are also careless errors, especially this one:
for (int r = 0; r < board.length; rows++)
Do you see what is wrong here? r is not the same as rows, and you can't use one as the index for the loop and then increment the other. You're using both of these variables inside of the loop. There are several other careless errors in the code as well.
I recommend that you start over but be much more careful with your code and be especially careful with your indentation. If you don't line up your curly braces correctly, you will not see when one code block ends and another begins (nor will we!).
Oh, and next time, please let us know which lines of your code are causing your error. It will be much easier to help you if we don't have to guess this information.
Edit
Your new code indentation is some better, but still is off. This is what you have:
String move(char x, int rows, int columns)
{
String b = "";
// creates a vertical bar at the beginning and the end of each row
for (int r = 0; r < board.length; r++)
{
b += "| ";
for (int c = 0; c < board[r].length; c++)
{
b += board[rows][columns] + " ";
and this is what I recommend:
String move(char x, int rows, int columns) {
String b = "";
// creates a vertical bar at the beginning and the end of each row
for (int r = 0; r < board.length; r++) {
b += "| ";
for (int c = 0; c < board[r].length; c++) {
// let's check to see what the variables hold!
System.out.printf("rows: %d, columns %d, r: %d, c: %d%n", rows, columns, r, c);
b += board[rows][columns] + " "; // **** the offending line ****
Even more important, note the result from the printf statement followed immediately by the exception:
rows: 1, columns 3, r: 0, c: 0
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
So here it is the column variable that holds the value of 3 and causes your array to explode. You will need to track back to see how you call this method and why it is passing a 3 into the column parmaeter.
Edit 2
On re-review of your latest post, you're still hard coding your move method to accept a 3 as the column parameter:
game.move('x', 1, 3);
Fix that first and foremost. That parameter can't be 3.
Related
I'm trying to get the diagonals of a matrix that is at least 5x5; trying to loop through new columns in multiple rows. The example I have is 8x8. I can't get it to trigger the middle section as recorded "hits" in a sequence diagonally. Here is my code. This is regarding button presses that then register a persons choice, based on a player color. It loops through fine on the edges like this:
JButton[][] buttons = new JButton[8][8];
String red = "";
String green = "";
string blue = "";
int col = 0;
for (int row = 0; row < 8; row++, col++) {
if (buttons[row][col].getBackground() == Color.RED) {
red = red.concat("+");
} else if (buttons[row][col].getBackground() == Color.GREEN) {
green = green.concat("+");
} else if (buttons[row][col].getBackground() == Color.BLUE) {
blue = blue.concat("+");
} else {
red = red.concat(",");
green = green.concat(",");
blue = blue.concat(",");
}
}
I am trying to solve it like this:
JButton[][] buttons = new JButton[8][8];
String red = "";
String green = "";
string blue = "";
int col = 0;
for (int loop = 0; loop < 5; loop++) {
for (int row = (0 + loop); row < 8; row++, col++) {
if (buttons[row][col].getBackground() == Color.RED) {
red = red.concat("+");
} else if (buttons[row][col].getBackground() == Color.GREEN) {
green = green.concat("+");
} else if (buttons[row][col].getBackground() == Color.BLUE) {
blue = blue.concat("+");
} else {
red = red.concat(",");
green = green.concat(",");
blue = blue.concat(",");
}
}
}
What I came up with is that I am always checking only from top to bottom and
Only to the right
Only to the bottom
Bottom down diagonally
Bottom left diagonally
So in my opinion this covers all possible wining configurations if the search is done field by field from the top.
I use 1D array to represent the gameboard and I always check if any of those checks can be performed or not, and skipping the one that.
Running example tries to randomly fill the board of given dimensions step by step and stops execution when winning condition is met (or it is a tie).
For checking winning conditions I just define the "field index progression" for the next field so I can do the ckeck as simple for loop.
public class TheGame {
static int side = 8;
static int fieldsCount = side * side;
static int[] fields = new int[fieldsCount];
static int toWin = 5;
public static void main(String[] args) {
//generate list of random picks
List<Integer> fieldsToBet = IntStream.range(0, fields.length).boxed().collect(Collectors.toList());
Collections.shuffle(fieldsToBet);
Iterator<Integer> it = fieldsToBet.iterator();
Arrays.fill(fields, ' ');
//place bets using list above
char nextBet = 0;
while (it.hasNext()) {
nextBet = nextBet == 'o' ? 'x' : 'o';
Integer idx = it.next();
fields[idx] = nextBet;
char winner=(char) gameOver(idx); //check the condition, if game is done, return the winner
if (winner > 0) {
System.out.println();
System.out.println("Winner is: " + nextBet);
break;
}
}
printout();
}
private static int gameOver(Integer i) {
if (fields[i] == ' ') {
return 0;
}
int column = i % side; //this can be ommited if using nested for loops
int row = i / side;
boolean canCheckRight = column + toWin <= side;
boolean canCheckDown = row <= side - toWin;
boolean canCheckLeft = column + 1 - toWin >= 0;
boolean canCheckRightDownDiagonal = canCheckDown && canCheckRight;
boolean canCheckLeftDownDiagonal = canCheckDown && canCheckLeft;
boolean itsEnd =
canCheckRight && check(i, toWin, idx -> idx += 1) || //every field to check is just "next field"
canCheckDown && check(i, toWin, idx -> idx += side) || // every field to check is in next row
canCheckRightDownDiagonal && check(i, toWin, idx -> idx += side + 1) || // next field to check is in next row skewed by 1 to the right
canCheckLeftDownDiagonal && check(i, toWin, idx -> idx += side - 1);// next field to check is in next row skewed by 1 to the left
return itsEnd ? fields[i]:0;
}
private static boolean check(int idx, int iterations, Function<Integer, Integer> nextCoordinateProducer) {
int figure = fields[idx];
if (figure == ' ') {
return false;
}
for (int i = 1; i < iterations; i++) {
idx = nextCoordinateProducer.apply(idx);
if (figure != fields[idx]) {
return false;
}
}
return true;
}
public static void printout() {
for (int i = 0; i < fieldsCount; i += side) {
System.out.println(Arrays.stream(fields)
.skip(i)
.limit(side)
.boxed()
.map(v -> Character.valueOf((char) v.intValue()).toString())
.collect(Collectors.joining("|")));
}
}
}
I'm a game where you have to make a line in a row/column of 3 or more (up to 7) consecutive elements in a 2d array. Every time there is one of those lines you add the score and delete those consecutive elements from the row/column (Similar as to candycrash disappearing candies.
If you have something like this ['a' 'b' 'b' 'b' 'a'] the result should be ['a' '' '' '' 'a']
The problem comes to more complex cases like ['b' 'b' ' ' 'b' 'b'], that ends up like this
['' '' '' '' ''] because its also detected as a >=3 elem. consecutive line.
I know its due to the counter that keeps adding to the value it already has, but I couldnt come to any other solution till now
The code I have up to now is this:
**
public int[] checkRows() {
int[] results = new int[BOARD_SIZE];
for (int i = 0; i < BOARD_SIZE; i++) {
int counter = 1;
int counterHead = 1;
int j = 0;
while (j < BOARD_SIZE) {
if (j+1 < BOARD_SIZE) {
if (getBoard()[i][j].getType() == getBoard()[i][j+1].getType() && getBoard()[i][j].getType() != ' ') {
counter++;
}
}
if (counter >= 3)
deleteRows(i, j);
j++;
}
results[i] = counter;
}
return results;
}
**
So we want to see if there are 3 consecutive elements with the same value, but what it looks like to me is we are simply testing if there are three values in a row of the board that are equal. This works if we find a section of consecutive equivalent elements, but as soon as we find a space or non-equal element the logic fails.
Things we need to consider:
If the counter reaches 3 we can break out of your conditional and delete. This case is covered.
If your counter is updated from its original value (counter > 1) and never reaches 3 we know we must have encountered either a space or a non-equivalent value. In this case we should reset the counter and continue our test for the rest of the row.
Your problem is that you are not counting consecutive values, but rather pairs of consecutive values. For 'b', 'b', 'b', there are three b characters, but only 2 pairs of values, 1) the first and second b, and 2) the second and third b. Your counter should start at 1 rather than 0 because every value is by itself a run of 1 "consecutive" values. This, along with resetting the counter when you see a non-matching letter should give you the result you desire.
I haven't tried this as I don't have your data or the structures that this code uses, but try this:
public int[] checkRows() {
int[] results = new int[BOARD_SIZE];
for (int i = 0; i < BOARD_SIZE; i++) {
int counter = 1;
int counterHead = 1;
int j = 1;
while (j < BOARD_SIZE) {
if (j+1 < BOARD_SIZE) {
if (getBoard()[i][j].getType() == getBoard()[i][j+1].getType() && getBoard()[i][j].getType() != ' ') {
counter++;
}
else {
counter = 1;
}
}
if (counter >= 3)
deleteRows(i, j);
j++;
}
results[i] = counter;
}
return results;
}
First of all, I would suggest that for such complex problems, always try to break your problems and then merge those pieces together.
Below is the algorithm for your problem, just for simplicity and ease of explanation I used an Integer array which you can easily replace with a string or character array and change the condition.
A basic function for performing candyCrush Algorithm.
public void candyCrush(){
int[][] result = new int[][]{{1,2,2,2,2},
{1,2,2,2,1},
{1,2,1,1,1},
{2,1,2,1,1}
};
int count = 0;
for (int i = 0; i < result.length; i++) {
for (int j = 0; j < result[i].length; j++) {
if(result[i][j]==-1){
continue;
}
boolean hasRowElements = checkRowForCurrentIndex(result, i, j);
boolean hasColumnElements = checkColumnForCurrentIndex(result, i, j);
if(hasRowElements || hasColumnElements){
count++;
}
}
}
System.out.println(count);
}
private boolean checkRowForCurrentIndex(int[][] result, int row, int col) {
int forTop = row, forBottom = row;
int topStartingLimit = 0, bottomEndingLimit = 0;
while(forTop>= 0){
if(result[row][col] == result[forTop][col] && result[forTop][col]!=-1){
topStartingLimit = forTop;
}else{
break;
}
forTop--;
}
while(forBottom< result.length){
if(result[row][col] == result[forBottom][col] && result[forBottom][col]!=-1){
bottomEndingLimit = forBottom;
}else{
break;
}
forBottom++;
}
if(topStartingLimit==bottomEndingLimit){
return false;
}
if(bottomEndingLimit-topStartingLimit>=2 && bottomEndingLimit-topStartingLimit<= 6) {
deleteRows(result, topStartingLimit, bottomEndingLimit, row, col);
return true;
}
return false;
}
private void deleteRows(int[][] result, int topStartingLimit, int bottomEndingLimit, int row, int col) {
while(topStartingLimit<= bottomEndingLimit){
result[topStartingLimit][col] = -1;
topStartingLimit++;
}
return;
}
private boolean checkColumnForCurrentIndex(int[][] result, int row, int col) {
int forLeft = col, forRight = col;
int leftStartingLimit = 0, rightEndingLimit = 0;
while(forLeft>= 0){
if(result[row][col] == result[row][forLeft] && result[row][forLeft]!=-1){
leftStartingLimit = forLeft;
}else{
break;
}
forLeft--;
}
while(forRight< result[row].length){
if(result[row][col] == result[row][forRight] && result[row][forRight]!=-1){
rightEndingLimit = forRight;
}else{
break;
}
forRight++;
}
if(leftStartingLimit==rightEndingLimit){
return false;
}
if(rightEndingLimit-leftStartingLimit>=2 && rightEndingLimit-leftStartingLimit<= 6) {
deleteCols(result, leftStartingLimit, rightEndingLimit, row, col);
return true;
}
return false;
}
private void deleteCols(int[][] result, int leftStartingLimit, int rightEndingLimit, int row, int col) {
while(leftStartingLimit<= rightEndingLimit){
result[row][leftStartingLimit] = -1;
leftStartingLimit++;
}
return;
}
Definitely, you can optimize this code better by using the same function again for Rows and Cols, but for sake of simplicity. I tried to break everything so that the solution becomes more clear.
Hope this algorithm would work.
So I created a checkWinner method, using 'row' and 'col' private variables so I can locate the 'curPlayer' position in the 2D array.
import java.util.Scanner;
public class TicTacBoard
{
private char[][] board; // 2-D array of characters
private char curPlayer; // the player whose turn it is (X or O)
// added so I can locate the current player location in the board
private int row;
private int col;
// Constructor: board will be size x size
public TicTacBoard(int size)
{
board = new char[size][size];
// initialize the board with all spaces:
for(row=0; row < board.length; row++)
for(col=0; col < board[row].length; col++)
board[row][col] = ' ';
curPlayer = 'X'; // X gets the first move
}
public void playGame()
{
display();
do
{
takeTurn();
display();
}while(!checkWinner(row, col));
}
/////// display ////////
// Display the current status of the board on the
// screen, using hyphens (-) for horizontal lines
// and pipes (|) for vertical lines.
public void display()
{
System.out.println();
dispRow(0);
System.out.println("-----");
dispRow(1);
System.out.println("-----");
dispRow(2);
System.out.println();
}
// Display the current status of row r of the board
// on the screen, using hyphens (-) for horizontal
// lines and pipes (|) for vertical lines.
private void dispRow(int r)
{
System.out.println(board[r][0] + "|" + board[r][1]
+ "|" + board[r][2]);
}
/////// takeTurn ////////
// Allow the curPlayer to take a turn.
// Send output to screen saying whose turn
// it is and specifying the format for input.
// Read user's input and verify that it is a
// valid move. If it's invalid, make them
// re-enter it. When a valid move is entered,
// put it on the board.
public void takeTurn()
{
Scanner scan = new Scanner(System.in);
int row, col;
boolean invalid;
do{
invalid = false; // assume correct entry
System.out.println("It is now " + curPlayer + "'s turn.");
System.out.println("Please enter your move in the form row column.");
System.out.println("So 0 0 would be the top left, and 0 2 would be the top right.");
row = scan.nextInt();
col = scan.nextInt();
if(row < 0 || col < 0 || row > 2 || col > 2)
{
System.out.println("Invalid entry: row and column must both be between 0 and 2 (inclusive).");
System.out.println("Please try again.");
invalid = true;
}
else if(board[row][col] != ' ')
{
System.out.println("Invalid entry: Row " + row + " at Column " + col
+ " already contains: " + board[row][col]);
System.out.println("Please try again.");
invalid = true;
}
}while(invalid);
// Now that input validation loop is finished, put the move on the board:
board[row][col] = curPlayer;
// Switch to the other player (take turns):
if(curPlayer == 'X')
curPlayer = 'O';
else
curPlayer = 'X';
}
// If the game is over, print who won (if anyone),
// and return true. If the game is not over, return false.
public boolean checkWinner(int row, int col)
{
// YOUR CODE GOES HERE
int x = row;
int y = col;
// board length is always 3 here
// check winner on column
for (int i = 0; i < board.length; i++) {
if (board[x][i] != curPlayer)
break;
if (i == board.length - 1)
System.out.println("Player " + curPlayer + " wins!");
return true;
}
//check winner on row
for (int i = 0; i < board.length; i++) {
if (board[i][y] != curPlayer)
break;
if (i == board.length - 1)
System.out.println("Player " + curPlayer + " wins!");
return true;
}
// checks winner on diagonal up
if (x == y) {
for (int i = 0; i < board.length; i++) {
if (board[i][i] != curPlayer)
break;
if (i == board.length - 1)
System.out.println("Player " + curPlayer + " wins!");
return true;
}
}
// check winner on diagonal down
if (x + y == board.length - 1){
for (int i = 0; i < board.length; i++) {
if (board[i][(board.length-1)-i] != curPlayer)
break;
if (i == board.length - 1)
System.out.println("Player " + curPlayer + " wins!");
return true;
}
}
// checks if board is full
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board.length; j++) {
if (board[i][j] == '-')
System.out.println("Nobody won, game ends in a draw!");
return true;
}
}
return false;
}
}
The code works but I while I was checking I got this:
| |
-----
| |
-----
| |
It is now X's turn.
Please enter your move in the form row column.
So 0 0 would be the top left, and 0 2 would be the top right.
2 0
| |
-----
| |
-----
X| |
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at TicTacBoard.checkWinner(TicTacBoard.java:126)
at TicTacBoard.playGame(TicTacBoard.java:43)
at Main.main(Main.java:14)
I thought the board length is always 3 with the location ranging from 0 to 3. Any solutions to this error? Any more efficient ways to do this? Please let me know!
You have a "shadowing" problem - that is, you're shadowing the instance fields row and col with local variables in your takeTurn method.
In it's current state...
// Constructor: board will be size x size
public TicTacBoard(int size) {
board = new char[size][size];
// initialize the board with all spaces:
for (row = 0; row < board.length; row++) {
for (col = 0; col < board[row].length; col++) {
board[row][col] = ' ';
}
}
curPlayer = 'X'; // X gets the first move
}
after the constructor has run, row and col will be 3, but in takeTurn, you define row and col as local variables...
public void takeTurn() {
Scanner scan = new Scanner(System.in);
int row, col;
boolean invalid;
This means, that when you call checkWinner in the playGame method...
public void playGame() {
display();
do {
takeTurn();
display();
} while (!checkWinner(row, col));
}
You're passing the instance field values (of 3/3) and everything breaks.
So, the "quick" solution would be to remove the local declaration of row/col from takeTurn
public void takeTurn() {
Scanner scan = new Scanner(System.in);
//int row, col;
boolean invalid;
You could also fix this in the constructor, but making row/col local variables
for (int row = 0; row < board.length; row++) {
for (int col = 0; col < board[row].length; col++) {
board[row][col] = ' ';
}
}
but at some point, you need to update the row/col value for the player, but I might consider passing this information back from takeTurn rather than trying to use instance fields.
You also have a subtle, but common bug in your if statements. Without brackets, { and }, only the line IMMEDIATELY following the if statement will be executed when the conditional statement above is true. Your INDENTATION, however, indicates that you expected a different behavior.
For instance, your very first for loop is:
for (int i = 0; i < board.length; i++) {
if (board[x][i] != curPlayer)
break;
if (i == board.length - 1)
System.out.println("Player " + curPlayer + " wins!");
return true;
}
Here, only the System.out.println() line is executed when the if statement is true. The indentation of the return true; statement indicates that you expect it to only run with the println(), only when the conditional is true.
The return true; line is NOT dependent upon the preceding if statement, though, because it is not within brackets and the if statement only runs the line immediately following it. This means that the for loop is only ever running ONE ITERATION because the return line is STAND-ALONE and executes every single time, regardless of how those if statements evaluate.
You should ALWAYS, ALWAYS, ALWAYS add brackets to your if statements, even if they are "one-liners". With that in mind, I'd expect it to look more like:
for (int i = 0; i < board.length; i++) {
if (board[x][i] != curPlayer) {
break;
}
if (i == board.length - 1) {
System.out.println("Player " + curPlayer + " wins!");
return true;
}
}
Now the return line is only executed when the preceding if statement is true.
I'm working on a Connect Four game for the console in Java. I have problems with the winning conditions, as I don't know how to program them. Here is my code my Main:
public class Main {
public static char[] playerNumber = new char[]{'1', '2'};
public static char[] Badge = new char[]{'X', 'O'};
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int moves = 7 * 6;
int whichPlayer = 0;
for (int i = 0; i < 10; i++) {
System.out.println(" FOUR IN A ROW");
System.out.println("-------------------------------------------------------");
System.out.println("Welcome to the amazing game Four In A Row:");
System.out.println("Enter a number between 0 and 6 for choosing a column.");
System.out.println();
Board board = new Board();
board.fillBoard();
board.presentBoard();
do {
// 1. get a badge
char Player = playerNumber[whichPlayer];
char badge = Badge[whichPlayer];
// 2. make a turn
board.makeTurn(badge, Player);
board.presentBoard();
// 3. Tjek om der er vinder
if (board.checkWinHorizontal() || board.checkWinVertical()) {
System.out.println("Player " + Player + " has won!");
break;
}
// 4. change the player
whichPlayer = 1 - whichPlayer;
// 5. decrease moves
--moves;
if (moves == 0) {
System.out.println("Game over, nobody has won.");
System.out.println("Do you want to play again? 'Y' or 'N':");
String newGame = scanner.nextLine();
if (newGame.equals("Y") || newGame.equals("y")) {
break;
}
if (newGame.equals("N") || newGame.equals("n")) {
System.out.println("Thanks for the game!");
return;
}
}
// 6. repeat
} while (true);
}
}
And here is my code for my Board class:
public class Board {
char[][] board = new char[6][7];
int column;
// Fills the empty spaces
public void fillBoard() {
for (int i = 0; i < 6; i++) {
for (int j = 0; j < 7; j++) {
board[i][j] = ' ';
}
}
}
// Prints the board
public void presentBoard() {
for (int i = 0; i < 6; i++) {
System.out.print(" | ");
for (int j = 0; j < 7; j++) {
System.out.print(board[i][j] + " | ");
}
System.out.println();
System.out.print(" -----------------------------");
System.out.println();
}
}
// Turn
public void makeTurn(char badge, char Player) {
Scanner scanner = new Scanner(System.in);
do {
// 1. Ask for a column
System.out.println("Player " + Player + " turn: ");
column = scanner.nextInt();
// 2. Check if it's between 0 and 6
if (column > 6) {
System.out.println("That is not a valid number. Please enter a number between 0 and 6: ");
continue;
}
// 3. Place a badge
for (int i = 6 - 1; i >= 0; i--) {
if (board[i][column] == ' ') {
board[i][column] = badge;
return;
}
}
// If column is full
System.out.println("Column " + column + " is full. Try another column:");
} while (true);
}
// Check for vertical win
public boolean checkWinVertical() {
return verticalWin(5, column);
}
// Check for horizontal win
public boolean checkWinHorizontal() {
return horizontalWin(5,column);
}
// Conditions for vertical win
private boolean verticalWin(int x, int y) {
char charToCheck = board[x][y];
if (board[x-1][y] == charToCheck &&
board[x-2][y] == charToCheck &&
board[x-3][y] == charToCheck) {
return true;
}
return false;
}
// Conditions for horizontal win
private boolean horizontalWin(int x, int y) {
char charToCheck = board[x][y];
if (board[x][y+1] == charToCheck &&
board[x][y+2] == charToCheck &&
board[x][y+3] == charToCheck) {
return true;
}
return false;
}
I have succeeded in getting the game recognize a win horizontally and vertically at the bottom row of my array, but I don't know how to make the game recognize for the whole array. I'm only concentrating about the horizontal and vertical, as the diagonal is too complicated for me. And I don't know if this is the right approach or there is a better one.
Thanks!
Here's another solution. It's the same general idea as previously mentioned: loop through each row/column, checking for a streak of 4 in a row. Maybe this implementation will provide some other insight. Below, I've shown an example method checking the horizontal streaks. For vertical, you would iterate over the rows in the inner for loop instead.
public boolean checkWin(char badge) {
return checkHorizontalStreaks(board, badge)
|| checkVerticalStreaks(board, badge);
}
private boolean checkHorizontalStreaks(char[][] board, char badge) {
for (int row = 0; row < board.length; row++) {
// loop throught each row
int currentStreak = 0;
for (int col = 0; col < board[row].length; col++) {
// loop through each column in the row
if (board[row][col] == badge) {
// keep the streak of 'badge' going
currentStreak++;
if (currentStreak == 4) {
// winner
return true;
}
} else {
// restart the streak
currentStreak = 0;
}
}
}
return false;
}
And then update your Main class with
if (board.checkWin(badge)) {
System.out.println("Player " + Player + " has won!");
break;
}
I'd wager there is a more efficient way to determine a winner (perhaps by treating the grid as a graph and traversing it with some special logic). However, I suspect this may be enough for what you need. I'll spare you the output, but it worked with a few different test cases.
Possibly you could check all the adjacent fields around the last played field, so after the user did his turn. So for checking upwards you could do this:
public boolean checkUp(int rowPlayed, int columnPlayed){
boolean checked = false;
if(rowplayed + 1 <= maxrows){ //Checks if you didn't hit the top
if(board[rowPlayed+1][columnPlayed] != null){
if(board[rowPlayed+1][columnPlayed].getPlayer() == currentPlayer){
checked = true;
}
}
}
return checked;
}
and for example implemented like this:
public void checkWin(int rowPlayed, int columnPlayed){
boolean checkingWin = true;
int countWin = 0;
while(checkingWin){
if(checkUp(rowPlayed + countWin, columnPlayed)){
countWin++;
}
else{
checkingWin = false;
}
if(countWin == 4){
checkinWin = false;
//Insert win confirmation here
}
}
}
It's partially pseudo code because I don't know exactly how you handle things in your code, nor do I know if this is the best way to do it. But I hope it was of help for you.
This is a long answer and I'll go around the houses a bit so you can see how I reached my solution (which also expands to diagonal checking at the end).
I would use the last piece added as a starting point and work from there since checking all combinations is exhaustive and unnecessary.
Given the row and column of the last piece added I need to decide what I need to achieve.
I already know that the current row and column has the piece of the colour I'm looking for so I can ignore that.
For horizontal matching, I want to check I want to checking pieces to left and right in the same row have the same colour, and stop if the colour is different or there is no piece.
So imagine the following board (# = empty, R = Red piece, Y = Yellow piece:
6 # # # # # # # #
5 # # # # # # # #
4 # # # # # # # #
3 # # # # # # # #
2 # # # # # # # #
1 # # # # # # # #
0 Y R R R Y Y Y R
0 1 2 3 4 5 6 7
The last move was Yellow, row 0, col 4.
So I want to check left and right from [0][4] and see if the total number of consecutive pieces of the colour is 3, (not 4) since I know [0][4] is Yellow and can be discounted.
Based on this I can take a recursive approach where I check the adjacent to one side, then recursively do the same thing as long as I keep matching pieces of the same colour or do not encounter an empty slot.
I'll start of with a check to the right (to demonstrate):
private static final int COLS = 7;
private static final int ROWS = 6;
public enum Piece {RED, YELLOW}; // null is empty
private Piece[][] board = new Piece[ROWS][COLS]; // the board
private int checkRight(Piece piece, int row, int col) {
// assume valid row for now
col++; // moving col to the right
if (col >= COLS || board[row][col] != piece) {
// We're outside the limits of the column or the Piece doesn't match
return 0; // So return 0, nothing to add
} else {
// otherwise return 1 + the result of checkRight for the next col
return 1 + checkRight(piece, row, col);
}
}
Now I can perform the same to the left.
private int checkLeft(Piece piece, int row, int col) {
// assume valid row for now
col--; // moving col to the left
if (col < 0 || board[row][col] != piece) {
// We're outside the limits of the column or the Piece doesn't match
return 0; // So return 0, nothing to add
} else {
// otherwise return 1 + the result of checkLeft for the next col
return 1 + checkLeft(piece, row, col);
}
}
And to check a winner for horizontal, I could do this:
public boolean checkWinner(Piece piece, int row, int col) {
// if the sum is 3, we have a winner (horizontal only).
return checkRight(piece, row, col) + checkLeft(piece, row, col) == 3;
}
Ugh, there's a lot of repetition isn't there?
We can condense the two methods into one by introducing a new parameter direction which can change if we move col positive or negative through the values 1 and -1 respectively:
private int check(Piece piece, int row, int col, int direction) {
col += direction; // direction is either 1 (right) or -1 (left)
if (col < 0 || col >= COLS || board[row][col] != piece) {
return 0;
} else {
return 1 + check(piece, row, col);
}
}
Update checkWinner() for this new parameter:
private static final int POSITIVE = 1; // right at the moment
private static final int NEGATIVE = -1; // left at the moment
public boolean checkWinner(Piece piece, int row, int col) {
// if the sum is 3, we have a winner (horizontal only).
return check(piece, row, col, POSITIVE) + check(piece, row, col, NEGATIVE) == 3;
}
Now I could implement the same sort of logic for vertical, but instead stay on the same col and change the row. I will skip this part in detail and move onto a solution which includes this and diagonal checking.
This has been done using an enum called CheckType storing values for which row and col should change and is used by the check() method. e.g. for HORIZONTAL the column changes by 1 or -1 (depending upon the direction specified when check() is called) and the row remains 0.
public class Board {
public enum Piece {
RED, YELLOW
};
private enum CheckType {
HORIZONTAL(0, 1), VERTICAL(1, 0), DIAGNONAL_UP(1, 1), DIAGNONAL_DOWN(-1, 1);
int row;
int col;
CheckType(int row, int col) {
this.row = row;
this.col = col;
}
}
private static final int POSITIVE = 1;
private static final int NEGATIVE = -1;
private static final int ROWS = 6;
private static final int COLS = 7;
private Piece[][] board = new Piece[ROWS][COLS];
private boolean hasWinner = false;
public boolean hasWinner() {
return hasWinner;
}
private void checkWinner(Piece piece, int row, int col) {
// check all values of enum CheckType for a winner
// so HORIZONTAL, VERTICAL, etc..
int enumIndex = 0;
while (!hasWinner && enumIndex < CheckType.values().length) {
hasWinner = check(piece, row, col, POSITIVE, CheckType.values()[enumIndex])
+ check(piece, row, col, NEGATIVE, CheckType.values()[enumIndex]) == 3;
enumIndex++;
}
}
private int check(Piece piece, int row, int col, int direction, CheckType type) {
row += type.row * direction;
col += type.col * direction;
if (row >= ROWS || row < 0 || col >= COLS || col < 0 || board[row][col] != piece) {
return 0;
} else {
return 1 + check(piece, row, col, direction, type);
}
}
// for completeness, adding a Piece
public boolean add(Piece piece, int col) {
int row = 0;
while (row < ROWS && board[row][col] != null) {
row++;
}
if (row < ROWS) {
board[row][col] = piece;
// check for winner after successful add
checkWinner(piece, row, col);
}
return row < ROWS;
}
}
Hope this helps.
I am a beginner java student writing a gui tic-tac-toe program for my class. (No players, just computer generated).
Everything in my program works as expected, except for one thing; it seems that the placement of my method call for checkWinner is not place correctly, because the assignment for the X's and O's always finish. Why won't the loop end as soon as there is a winner?
It will return the correct winner based on the method call, but the for-loop will continue to iterate and fill in the rest (so sometimes it looks like both the x and o win or one wins twice). I've been going crazy, thinking it might be the placement of my checkWinner method call and if statement. When I set the winner = true; shouldn't that cancel the loop? I have tried putting it between, inside and outside each for-loop with no luck :(
I have marked the area I think is the problem //What is wrong here?// off to the right of that part the code. Thank you for any input!! :)
public void actionPerformed(ActionEvent e)
{
int total = 0, i = 0;
boolean winner = false;
//stop current game if a winner is found
do{
// Generate random # 0-1 for the labels and assign
// X for a 0 value and O for a 1 value
for (int row = 0; row < gameboard.length; row++) //rows
{
for (int col = 0; col < gameboard[row].length; col++) //columns
{
//Generate random number
gameboard[row][col] = (int)(Math.random() * 2);
//Assign proper values
if(gameboard[row][col] == 0)
{
labels[i].setText("X");
gameboard[row][col] = 10; //this will help check for the winner
}
else if(gameboard[row][col] == 1)
{
labels[i].setText("O");
gameboard[row][col] = 100; //this will help check for winner
}
/**Send the array a the method to find a winner
The x's are counted as 10s
The 0s are counted as 100s
if any row, column or diag = 30, X wins
if any row, column or diag = 300, Y wins
else it will be a tie
*/
total = checkWinner(gameboard); **//Is this okay here??//**
if(total == 30 || total == 300) //
winner = true; //Shouldn't this cancel the do-while?
i++; //next label
}
}//end for
}while(!winner);//end while
//DISPLAY WINNER
if(total == 30)
JOptionPane.showMessageDialog(null, "X is the Winner!");
else if(total == 300)
JOptionPane.showMessageDialog(null, "0 is the Winner!");
else
JOptionPane.showMessageDialog(null, "It was a tie!");
}
The easiest way would be to break all loops at once. (Even if some people dont like this)
outerwhile: while(true){
// Generate random # 0-1 for the labels and assign
// X for a 0 value and O for a 1 value
for (int row = 0; row < gameboard.length; row++) //rows
{
for (int col = 0; col < gameboard[row].length; col++) //columns
{
total = checkWinner(gameboard);
if(total == 30 || total == 300)
break outerwhile; //leave outer while, implicit canceling all inner fors.
i++; //next label
}
}//end for
}//end while
This However would not allow for the "tie" option, because the while will basically restart a game, if no winner has been found. To allow tie, you dont need the outer while at all, and can leave both fors at once, when a winner is found:
Boolean winner = false;
outerfor: for (int row = 0; row < gameboard.length; row++) //rows
{
for (int col = 0; col < gameboard[row].length; col++) //columns
{
total = checkWinner(gameboard);
if(total == 30 || total == 300){
winner = true;
break outerfor; //leave outer for, implicit canceling inner for.
}
i++; //next label
}
}//end for
if (winner){
//winner
}else{
//tie.
}
First of all, your code iterates through a board and generates random marks of X and O. This leads to some very odd board states, being always filled row-by-row, and possibly with unbalanced number of X and O marks.
IMHO you should organize your code in opposite manner to fill a board similary to a true game. I mean a series of 9 marks 'XOXOXOXOX' spreaded over the board.
Let Labels labels be a nine-character array, initialized to 9 spaces.
public int doGame( Labels labels)
{
labels = " ";
int itisXmove = true; // player X or O turn
for( int movesLeft = 9; movesLeft > 0; movesLeft --)
{
int position = // 0 .. movesLeft-1
(int) Math.floor(Math.random() * movesLeft);
for( int pos = 0; pos < 9; pos ++) // find position
if( labels[ pos] == " ") // unused pos?
if( position-- == 0) // countdown
{
if( itisXmove) // use the pos
labels[ pos] = "X"; // for current player
else
labels[ pos] = "O";
break;
}
int result = checkWinner( labels); // who wins (non-zero)?
if( result != 0)
return result;
itisXmove = ! itisXmove; // next turn
}
return 0; // a tie
}
then
public void actionPerformed(ActionEvent e)
{
Labels labels;
int result = doGame( labels);
if( result == valueForX)
JOptionPane.showMessageDialog(null, "X is the Winner!");
else if( result == valueForO)
JOptionPane.showMessageDialog(null, "O is the Winner!");
else
JOptionPane.showMessageDialog(null, "It's a tie!");
for( int rowpos = 0; rowpos < 9; rowpos += 3)
{
for( int colpos = 0; colpos < 3; colpos ++)
/* output (char)label[ rowpos + colpos] */;
/* output (char)newline */;
}
}
I think you should change your loop condition and add one more bool.
You have a "tie" condition but currently you only check for winner. The only explanation without the checkWinner code is that you are encountering a tie every time.
So...
boolean tie;
boolean winner;
do {
//your stuff
}
while(!(tie || winner))
Edit: I didn't realize you put the while loop outside your for loop, you will need to break out of your for loops in order for the while condition to be checked.
//stop current game if a winner is found
do{
for (int row = 0; row < gameboard.length; row++) //rows
{
for (int col = 0; col < gameboard[row].length; col++) //columns
{
if(winner || tie)
break;
}//end for
if(winner || tie)
break;
}//end for
}while(!(winner || tie));//end while
//the rest of your stuff here
You're not checking the value of winner until both for loops complete. Add a break right after you set winner = true, and add an
if (winner)
{
break;
}
to the beginning or end of your outer for loop.
Your issue is that your do/while statement is wrapped around the for statements. So the for statements end up running their entire cycle before it ever reaches the while statement. A solution to get around this is checking for a winner in the for statements and breaking:
//stop current game if a winner is found
do {
for (int row = 0; row < gameboard.length; row++) //rows
{
for (int col = 0; col < gameboard[row].length; col++) //columns
{
// ... your other code ...
total = checkWinner(gameboard);
if(total == 30 || total == 300) {
winner = true;
break; // end current for-loop
}
i++; //next label
}
if (winner) break; // we have a winner so we want to kill the for-loop
} //end for
} while(!winner); //end while
So you should be able to just loop through the two for-statements and break upon a winner. Your code also does not seem to handle a tied case, but I am guessing you already know that.