This question already has an answer here:
Loop doesn't see value changed by other thread without a print statement
(1 answer)
Closed 7 years ago.
I am writing a basic Tic-Tac-Toe Single player game using basic swing graphics. I completed the game, but there is a weird problem I am facing. At one place, I used a while loop with a SOP statement. If I omit this statement, program works differently and nothing happens (like some kind of infinite loop), and if I keep it, it works just fine. I don't know what's happening in the code. Please help.
Below is the source code which causing problem. Sorry for my amateur coding style.
import java.util.Random;
public class SinglePlayer implements Runnable{
public final int MINIMUM = -1000000;
private GameBoard game;
public SinglePlayer(){
game = new GameBoard("Single Player");
}
public static void main(String[] args){
SinglePlayer gameSingle = new SinglePlayer();
gameSingle.run();
}
public void run(){
boolean machinePlayed = true, userPlayed = false;
// Outer loop is to maintain re-match option of program
while(this.game.quitTwoPlayer == false){
// Inner loop is a single game b/w user and machine
while(this.game.GameQuitStatus() == false){
/* I kept two conditions to switch b/w machine and user mode
* of game and they just keep changing to simulate the game
* b/w machine and user.
*/
if(machinePlayed == false && userPlayed){
try {
MachineMove("O");
} catch (CloneNotSupportedException e) {
e.printStackTrace();
break;
}
this.game.ChangePlayerLabels();
machinePlayed = true;
userPlayed = false;
}
else if(machinePlayed && userPlayed == false){
int earlierCount = this.game.CountSteps();
/* THIS IS THE WHILE LOOP I AM TALKING ABOUT.
* If I omit the print statement inside the body of loop,
* program behaves differently, but when I keep it,
* it working just fine.
* */
while(earlierCount == this.game.CountSteps()){
System.out.println("Player User thinking");
}
this.game.ChangePlayerLabels();
machinePlayed = false;
userPlayed = true;
}
this.game.DeclareResult();
}
this.game.dispose();
}
}
public void MachineMove(String player) throws CloneNotSupportedException{
/* If board is empty, play at center of the board */
if(this.game.CountSteps() == 0){
this.game.MakeMove(1, 1);
}
/* If center is blank, play it there. Otherwise, pick a corner randomly */
else if(this.game.CountSteps() == 1){
if(this.game.IsEmpty(1, 1))
this.game.MakeMove(1, 1);
else{
Random randomNum = new Random();
int num = randomNum.nextInt(4);
if(num == 0)
this.game.MakeMove(0, 0);
else if(num == 1)
this.game.MakeMove(2, 0);
else if(num == 2)
this.game.MakeMove(0, 2);
else if(num == 3)
this.game.MakeMove(2, 2);
}
}
else{
/* If the next move is such that it should be taken, otherwise opponent will win */
String opponent = "";
if(this.game.GetCurrentPlayer().equals("O"))
opponent = "X";
else
opponent = "O";
for(int i = 0; i<3; i++){
for(int j = 0; j<3; j++){
if(this.game.IsEmpty(i,j)){
GameBoard tempGame = new GameBoard(this.game, "Single Player");
tempGame.MakePossibleMove(i, j, opponent);
if(tempGame.GameWinner().equals(opponent + " wins")){
this.game.MakeMove(i,j);
return;
}
}
}
}
/* If the next move is not such that if missed, game is lost, then play most optimal move towards winning */
Move tempMove = new Move(MINIMUM, 0, 0);
Move bestMove = new Move(MINIMUM, 0, 0);
for(int i = 0; i<3; i++){
for(int j = 0; j<3; j++){
if(this.game.IsEmpty(i,j)){
GameBoard tempGame = new GameBoard(this.game, "Single Player");
tempMove = MakeMoves(tempGame, i, j);
if(tempMove.score > bestMove.score){
bestMove.row = tempMove.row;
bestMove.col = tempMove.col;
bestMove.score = tempMove.score;
}
}
}
}
this.game.MakeMove(bestMove.row, bestMove.col);
}
}
public Move MakeMoves(GameBoard tempGame, int row, int col){
String player = tempGame.GetCurrentPlayer();
tempGame.MakeMove(row, col);
if(tempGame.GameWinner().equals("Match Draw")){
return new Move(0, row, col);
}
else if(tempGame.GameWinner().equals("X wins")){
if(player.equals("X")){
return new Move(1, row, col);
}
else{
return new Move(-1, row, col);
}
}
else if(tempGame.GameWinner().equals("O wins")){
if(player.equals("O")){
return new Move(1, row, col);
}
else{
return new Move(-1, row, col);
}
}
else{
Move bestMove = new Move(MINIMUM, 0, 0);
Move tempBestMove = new Move(0, 0, 0);
for(int i = 0; i<3; i++){
for(int j = 0; j<3; j++){
if(tempGame.IsEmpty(i,j)){
GameBoard newGame = new GameBoard(tempGame, "Single Player");
tempBestMove = MakeMoves(newGame, i, j);
if(tempBestMove.score > bestMove.score)
bestMove = tempBestMove;
}
}
}
return bestMove;
}
}
}
class Move{
public int score;
public int row;
public int col;
public Move(int score, int row, int col){
this.score = score;
this.row = row;
this.col = col;
}
}
Your loop is likely typing up your processor, and the SOP slows the loop enough to allow other processes to occur. But regardless and most importantly, you don't want to have this loop present in the first place. You state that you have a,
Tic-Tac-Toe Single player game using basic swing graphics
Remember that Swing is an event driven GUI library, so rather than loop as you would in a linear console program, let events occur, but respond to them based on the state of the program.
In other words, give your class several fields including a boolean variable that tells whose turn it is, such as boolean playersTurn, a boolean variable gameOver, ..., and change the state of these variables as the game is played, and base the games behavior depending on these states. For instance the game would ignore the player's input if it was not his turn.
Related
Hi so I've recently started programming in java and I've set myself a task of making an AI for a tic tac toe game I've made
However the minmax algorithm is throwing a Stack Overflow error and I cant see in the error or the program where the problem is.
Here's the program:
public State minmax(boolean max, State currentState)
{
if (currentState.getNull() == 0) {
return currentState;
}
else {
State[] successorStates = currentState.getSuccessorStates(aiPlayer);
ArrayList<Integer> scoresTemp = new ArrayList<>();
for (State state : successorStates) {
scoresTemp.add(evaluate(aiPlayer, minmax(!max, state)));
}
Integer[] scores = (Integer[]) scoresTemp.toArray();
if (max) {
State maxState = successorStates[0];
int maxScore = evaluate(aiPlayer, maxState);
for (int score : scores) {
if (scores[0] > maxScore) {
maxScore = score;
maxState = successorStates[score];
}
}
return maxState;
}
else
{
State minState = successorStates[0];
int minScore = evaluate(aiPlayer, minState);
for (int score : scores) {
if (scores[0] > minScore) {
minScore = score;
}
}
return minState;
}
}
}
It returns the state which is the best move to make.
getNull() returns the amount of spaces left that can be played on.
getSuccesorStates(Player) returns all of the succeeding states of that state by making a new state of which contains the old moves and a new one of the Player.
evaluate() returns the value -1, 0 or 1 depending on a win, draw or loss in that state. None returns 0
edit:
public int getNull()
{
int amount = 0;
for (int x =0; x<9; x++)
{
if (getAllCells()[x]==null)
{
amount++;
}
}
return amount;
}
public State[] getSuccessorStates(Player player)
{
State[] states = new State[getNull()];
Player[][] stateCells = cells.clone();
int[][] nullPositions = getNulls();
for (int x=0; x<getNull(); x++)
{
stateCells[nullPositions[x][0]][nullPositions[x][1]] = player;
states[x] = new State(player, stateCells);
stateCells = cells.clone();
}
return states;
}
Caused by: java.lang.StackOverflowError
at sample.AI.minmax(AI.java:23)
at sample.AI.minmax(AI.java:32)
at sample.AI.minmax(AI.java:32)
.
.
.
23: if (currentState.getNull() == 0)
32: scoresTemp.add(evaluate(aiPlayer, minmax(!max, state)));
public Player[] getAllCells()
{
Player[] cellList = new Player[9];
for (int x = 0; x<3; x++)
{
for (int y = 0; y<3; y++)
{
cellList[y*3+x] = cells[x][y];
}
}
return cellList;
}
minmax is called in:
public Ply getPly(State state)
{
State bestState = minmax(true, state);
State[] successorStates = state.getSuccessorStates(aiPlayer);
ArrayList<State> states = new ArrayList<State>();
for (int x=0; x<successorStates.length; x++)
{
states.add(successorStates[x]);
}
int[][] nulls = state.getNulls();
Ply bestPly = new Ply(aiPlayer, nulls[states.indexOf(bestState)][0], nulls[states.indexOf(bestState)][1]);
return bestPly;
}
Thankyou if anyone could help:)
Your problem is here:
scoresTemp.add(evaluate(aiPlayer, minmax(!max, state)));
When you call the minmax method you create a bunch of data that uses up the memory ( java allows a certain amount of the computers memory to be used ).
You then inside minmax call minmax again making it create even more data and this is happening infinitely until there is no more memory left and Java throws the StackOverflow exception.
http://imgur.com/a/V7LPP
Grid images are in the link
I have 2 main issues with my current connect4 code, sometimes the "AI" places 2 discs in one turn and the player can't fill the entire board without the "AI" entering an infinite loop. Any help is appreciated.
/**
* Auto Generated Java Class.
*/
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class EmptyFrame1 implements ActionListener
{
//Array of JButtons
static JButton [] mnuBtn = new JButton[2];
static JButton [] btnArray = new JButton[7];
static JLabel [][] board = new JLabel[6][7];
static int [][] numBoard = new int[6][7];
static int choice = 0;
static int playerColour = 1;
static int computerColour = 2;
static int computerTurn = 0;
static ImageIcon emptyGrid = new ImageIcon("EmptyGrid.png");
static ImageIcon yellowGrid = new ImageIcon("YellowGrid.png");
static ImageIcon redGrid = new ImageIcon("RedGrid.png");
static JPanel pnlBoard;
static boolean validTurn;
static int pieceCount = 42;
public EmptyFrame1()
{
JFrame game = new JFrame("Connect 4");
//mainFrame panel to hold all components
JPanel mainFrame = new JPanel();
pnlBoard = new JPanel();
pnlBoard.setLayout(new GridLayout(7,6));
//Change mainFrame layout to vertical BoxLayout
mainFrame.setLayout(new BoxLayout(mainFrame, BoxLayout.Y_AXIS));
//For every single button
//Set the button text to its index value, add an action listener and add it to the pnlButtons
for (int i = 0; i < btnArray.length; i++)
{
btnArray[i] = new JButton("place");
btnArray[i].addActionListener(this);
pnlBoard.add(btnArray[i]);
}//end for
for (int r = 0; r < board.length; r++)
{
for (int c = 0; c < board[r].length; c++)
{
board[r][c] = new JLabel(emptyGrid);
board[r][c].setPreferredSize(new Dimension(69, 69));
pnlBoard.add(board[r][c]);
}//end for
}//end for
//Add all the panels to the mainFrame panel
mainFrame.add(pnlBoard);
//add mainFrame to the JFrame
game.add(mainFrame);
game.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Added this for security
game.pack();
game.setVisible(true);
game.setResizable(false);
}
//*Action listener for all buttons*
public void actionPerformed(ActionEvent e)
{
for (int i = 0; i < btnArray.length; i++)
{
//if the current button was triggered, set the choice to the button index
if (btnArray[i] == e.getSource())
{
choice = i;
colCheck(choice, 1);
//System.out.println("User turn used");
pieceCount--;
for (int j = 0; j < btnArray.length; j++)
{
if (numBoard[0][j] == 0) btnArray[j].setEnabled(true);
}
if (pieceCount > 0)
{
validTurn = false;
while(!validTurn)
{
computerTurn = (int) (Math.random() * 7);
System.out.print(computerTurn + " ");
validTurn = colCheck(computerTurn, 2);
//System.out.println("CPU tried to move");
}
}
System.out.println();
setGrid();
pieceCount--;
System.out.println(pieceCount);
validTurn = false;
}//end if
}//end for
pnlBoard.repaint();
}//end actionPerformed
public static boolean colCheck(int choice, int currentTurn)
{
int row = -1;
for (int r = 5; r >= 0; r--)
{
if (numBoard[r][choice] == 0)
{
row = r;
break;
}
}
//System.out.println("Row That CPU Chooses: " + row);
if (row > -1)
{
numBoard[row][choice] = currentTurn;
if (row == 0)
{
btnArray[choice].setEnabled(false);
return false;
}
return true;
}
return false;
}
public static void setGrid()
{
for (int r = 0; r < numBoard.length; r++)
{
for (int c = 0; c < numBoard[r].length; c++)
{
if (numBoard [r][c] == 0)
{
board[r][c].setIcon(emptyGrid);
}
else if (numBoard [r][c] == 1)
{
board[r][c].setIcon(redGrid);
}
else if (numBoard [r][c] == 2)
{
board[r][c].setIcon(yellowGrid);
}
}
}
}
/**
* Inner helper class that defines the graphics
*/
public static void main(String[] args)
{
new EmptyFrame1();
}
//end constructor
Some problems and suggestions:
Your colCheck method and the while (!validTurn) { loop in the actionPerformed method is where the problem is located.
This method should not set numBoard value or disable buttons. In other words, it should not have any "side effects".
Instead it should only check for validity and then return a boolean value, nothing more or less
You should have another method that is called first, one that checks if no valid columns are available, if the game is effectively over. This should be called before the while loop above and should prevent the while loop from entering. This will end your endless loop problem because it's looping because no valid columns can be found for the computer turn.
You should have another method for setting the numBoard state and for enabling and disabling JButtons.
Change your colCheck to this to see where the loop is coming from:
public static boolean colCheck(int choice, int currentTurn) {
int row = -1;
for (int r = 5; r >= 0; r--) {
if (numBoard[r][choice] == 0) {
row = r;
break;
}
}
// System.out.println("Row That CPU Chooses: " + row);
if (row > -1) {
numBoard[row][choice] = currentTurn;
if (row == 0) {
btnArray[choice].setEnabled(false);
System.out.printf("debug 1 row %d %n", row);
return false;
}
System.out.printf("debug 2 row %d %n", row);
return true;
}
System.out.printf("debug 3 row %d %n", row);
return false;
}
Other issues not directly related to your problem at hand, but which should be addressed
You're grossly over-using the static modifier, and in fact none of your fields should be static. All should be private instance fields.
You've got your program logic code, the model, mixed in the same class as the GUI, the view, making it hard to debug problems and enhance the program. Much better if you could make your model a completely separate class, one that is "view-agnostic" meaning that it is testable on its own and can work with any view, be it a command line or Swing or Android UI.
Your question depends on images, EmptyGrid.png, YellowGrid.png.... that we have no access to, preventing us from testing it.
You're using magic numbers, such as 0, 1, 2 for position values, and need to avoid doing this.
Instead use an enum for empty, user and computer
This is Java not Javascript. You'll want to be clear on the difference because they're two completely different languages.
I am making a tic tac toe game in java, between human-computer.
I need a Check to ensure that the player has made a valid move, e.g. that the player is not trying to make a move in a square that is already occupied.
I tried to write a method and call it from the chick() method, or to make if statment within win() but It didn't work.
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
public class XOGAMEMAIN extends JFrame implements ActionListener {
private JButton [][]buttons = new JButton[3][3];
private JButton XButton = new JButton("play with X ");
private JButton OButton = new JButton("play with O");
private JButton CButton = new JButton("let computer start with X");
private JButton C1Button = new JButton("let computer start with O");
private JLabel statusLabel = new JLabel(" ");
private XOGAMEAI game = null;
private int human = 0;
private int computer = 0;
private boolean isPlay = false;
private String []chars=new String[]{"","X","O"};
private void setStatus(String s) {
statusLabel.setText(s);
}
private void setButtonsEnabled(boolean enabled) {
for(int i=0;i<3;i++)
for(int j=0;j<3;j++) {
buttons[i][j].setEnabled(enabled);
if(enabled) buttons[i][j].setText(".");
}
}
public XOGAMEMAIN() {
setTitle(" X/O Game ");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
JPanel centerPanel = new JPanel(new GridLayout(3,3));
Font font = new Font("Arial",Font.BOLD, 32);
for(int i=0;i<3;i++)
for(int j=0;j<3;j++) {
buttons[i][j] = new JButton(".");
buttons[i][j].setFont(font);
buttons[i][j].addActionListener(this);
buttons[i][j].setFocusable(false);
centerPanel.add(buttons[i][j]);
}
XButton.addActionListener(this);
OButton.addActionListener(this);
CButton.addActionListener(this);
C1Button.addActionListener(this);
JPanel northPanel = new JPanel();
northPanel.add(statusLabel);
JPanel southPanel = new JPanel();
southPanel.add(XButton);
southPanel.add(OButton);
southPanel.add(CButton);
southPanel.add(C1Button);
setStatus("wlc,to start chose X or O or let computer decide");
setButtonsEnabled(false);
add(northPanel,"North");
add(centerPanel,"Center");
add(southPanel,"South");
setSize(600,600);
setLocationRelativeTo(null);
}
public static void main(String []args) {
new XOGAMEMAIN().setVisible(true);
}
private void computerTurn1() { ////////// this method for computer ( when the human chose the computer buttons to start first ) with X or O
int []pos = game.nextMove(computer);
if(pos!=null) {
int i = pos[0];
int j = pos[1];
buttons[i][j].setText(chars[computer]);
game.setBoardValue(i,j,computer);
setStatus("computer did its move ");
}
checkState();
}
private void computerTurn() { // this method for computer movment( when the human chose (X,O)buttons to start ) human method==>(using click method) and after that(computer turn==> this method will run
int []pos = game.nextMove(computer);
if(pos!=null) {
int i = pos[0];
int j = pos[1];
buttons[i][j].setText(chars[computer]);
game.setBoardValue(i,j,computer);
setStatus("computer did its move , now your turn ");
}
checkState();
}
private void gameOver(String s) {
setStatus(s);
setButtonsEnabled(false);
isPlay = false;
}
private void checkState() {
if(game.isWin(human)) {
gameOver(" You've Won!");
}
if(game.isWin(computer)) {
gameOver("Sorry, You Lose!");
}
if(game.nextMove(human)==null && game.nextMove(computer)==null) {
gameOver(" Draw ");
}
}
private void click(int i,int j) { //// this method is for human , when he click any button
if(game.getBoardValue(i,j)==XOGAMEAI.EMPTY) {
buttons[i][j].setText(chars[human]);
game.setBoardValue(i,j,human);
checkState();
computerTurn();}
}
public void actionPerformed(ActionEvent event) { //// this is action event that is senseing any click from the human(every button click ==> go for a method==> this methods are declared before )
if(event.getSource()==C1Button) { /// event.getsourse mean ==> the click that human make is on C1 button ?
play4(); /// if yes go to method play4() ==> thats mean the computer will start first with O char
}
if(event.getSource()==CButton) { /// for computer to start first with X
play3();
}
if(event.getSource()==OButton) { ////////// human click to start first with O==> start play1 method
play1();}
else {
for(int i=0;i<3;i++) //// if this is not the first click ==> thats mean this will check in turn 2 for human for example , put not the first 1
for(int j=0;j<3;j++) ////it will go for method click() directly for any of the 9 buttons choices
if(event.getSource()==buttons[i][j]) ///
click(i,j); }
////
if(event.getSource()==XButton) { // the same here but the human has chose Xbutton to start first
play();
}else {
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
if(event.getSource()==buttons[i][j])
click(i,j);
}
}
private void play4() { //// this is play4() method , its happened when Computer supposed to start first with O
game = new XOGAMEAI();
human = XOGAMEAI.ONE; // human is int number=0 , now the is Matrix defined which include "","x","O" , human=o ==> char "", human=ONE ==>char X , human=TWO ==>char O
computer = XOGAMEAI.TWO; // ///
setButtonsEnabled(true); ///
isPlay = true;
computerTurn1();
///
}
private void play3() { //// for Computer to start first with X
game = new XOGAMEAI();
human = XOGAMEAI.TWO; //
computer = XOGAMEAI.ONE; // ///
setButtonsEnabled(true); ///
isPlay = true;
computerTurn1();
///
}
private void play1() { //// for O
game = new XOGAMEAI();
human = XOGAMEAI.TWO; //
computer = XOGAMEAI.ONE; //
setStatus("Your Turn"); ///
setButtonsEnabled(true); ///
isPlay = true; ///
} ////
private void play() { ////// for X
game = new XOGAMEAI();
human = XOGAMEAI.ONE;
computer = XOGAMEAI.TWO;
setStatus("Your Turn");
setButtonsEnabled(true);
isPlay = true;
}
public static class XOGAMEAI {
/* the board */
private int board[][];
/* empty */
public static final int EMPTY = 0;
/* player one */
public static final int ONE = 1;
/* player two */
public static final int TWO = 2;
public XOGAMEAI() {
board = new int[3][3];
}
/* get the board value for position (i,j) */
public int getBoardValue(int i,int j) {
if(i < 0 || i >= 3) return EMPTY;
if(j < 0 || j >= 3) return EMPTY;
return board[i][j];
}
/* set the board value for position (i,j) */
public void setBoardValue(int i,int j,int token) {////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
if(i < 0 || i >= 3) return;
if(j < 0 || j >= 3) return;
board[i][j] = token;
}
/* calculate the winning move for current token */
public int []nextWinningMove(int token) {
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
if(getBoardValue(i, j)==EMPTY) {
board[i][j] = token;
boolean win = isWin(token);
board[i][j] = EMPTY;
if(win) return new int[]{i,j};
}
return null;
}
public int inverse(int token) {
return token==ONE ? TWO : ONE;
}
/* calculate the best move for current token */
public int []nextMove(int token) {
/* lucky position in the center of board*/
if(getBoardValue(1, 1)==EMPTY) return new int[]{1,1};
/* if we can move on the next turn */
int winMove[] = nextWinningMove(token);
if(winMove!=null) return winMove;
/* choose the move that prevent enemy to win */
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
if(getBoardValue(i, j)==EMPTY)
{
board[i][j] = token;
boolean ok = nextWinningMove(inverse(token)) == null;
board[i][j] = EMPTY;
if(ok) return new int[]{i,j};
}
/* choose available move */
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
if(getBoardValue(i, j)==EMPTY)
return new int[]{i,j};
/* no move is available */
return null;
}
/* determine if current token is win or not win */
public boolean isWin(int token) {
final int DI[]={-1,0,1,1};
final int DJ[]={1,1,1,0};
for(int i=0;i<3;i++)
for(int j=0;j<3;j++) {
// we skip if the token in position(i,j) not equal current token */
if(getBoardValue(i, j)!=token){
continue;
}
for(int k=0;k<4;k++) {
int ctr = 0;
while(getBoardValue(i+DI[k]*ctr, j+DJ[k]*ctr)==token) ctr++;
if(ctr==3) return true;
}
}
return false;
}
}
}
Inside the click(int i, int j) method you check the condition if(game.getBoardValue(i,j)==XOGAMEAI.EMPTY) - doesn't that do what you what you want?
OK, I've found the bug, it was in the ActionPerformed method. The problem was, you wrote
if(xbutton pressed){...}
else{click()}
if(ybutton pressed){...}
else{click()}
And both else statements were executed. Here's the fixed code:
public void actionPerformed(ActionEvent event) { //// this is action event
//// that is senseing
//// any click from the
//// human(every button
//// click ==> go for a
//// method==> this
//// methods are
//// declared before )
if (event.getSource() == C1Button) { /// event.getsourse mean ==> the
/// click that human make is on
/// C1 button ?
play4(); /// if yes go to method play4() ==> thats mean the computer
/// will start first with O char
} else if (event.getSource() == CButton) { /// for computer to start
/// first with
/// X
play3();
}
else if (event.getSource() == OButton) { ////////// human click to start
////////// first with O==> start
////////// play1 method
play1();
} else if (event.getSource() == XButton) { // the same here but the
// human has
// chose Xbutton to start first
play();
} else {
for (int i = 0; i < 3; i++) //// if this is not the first click ==>
//// thats mean this will check in turn
//// 2 for human for example , put not
//// the first 1
for (int j = 0; j < 3; j++) //// it will go for method click()
//// directly for any of the 9
//// buttons choices
if (event.getSource() == buttons[i][j]) ///
click(i, j);
}
}
I can't figure this out, my dungeon is printing improperly(question and output at bottom). This is going to be a bit of code, but it's all necessary.
here's the dungeon constructor:
public Dungeon ( )
{
Random r = new Random();
int determinedDungeonSize = r.nextInt(10-5) + 5;
int weaponRandom = r.nextInt(determinedDungeonSize);
dungeon = new ArrayList<String>(determinedDungeonSize);
String cell = ("|____| ");
char[] cellArray = cell.toCharArray();
for(int i=0; i<determinedDungeonSize ;i++)
{
int randomProbability = r.nextInt(10000);
if(randomProbability < 5000)
{
cellArray[2] = 'M';
}
if(i == weaponRandom)
{
if(randomProbability < 5000)
{
cellArray[3] = 'S';
cellArray[4] = 'w';
}
else
{
cellArray[3] = 'S';
cellArray[4] = 't';
}
}//end if
cell = String.valueOf(cellArray);
dungeon.add(cell);
}//end for
}//end Dungeon()
toString for the Dungeon:
public String toString()
{
String dungeonString = "";
for(int i = 0; i < dungeon.size(); i++)
{
dungeonString += dungeon.get(i);
}
return dungeonString;
}
Now here's the problem. I'm printing off the dungeon in the driver class with this statement-> System.out.print(d.toString());, where d is just a Dungeon object created w/ Dungeon d = new Dungeon()
And the console is outputting(Here's a bad case):
|_M__| |_M__| |_M__| |_MSt| |_MSt| |_MSt| |_MSt|
The probability of a Monster, denoted 'M', being in a cell is 50/50. The probability for a Weapon(Stick('St') or Sword('Sw')) to exist is ONE WEAPON PER DUNGEON(only one weapon in any given dungeon, cell must be random). The constructor should be doing all this just fine, I have no idea what is going wrong here and I have been trying to solve this for 6 hours now.
EDIT: EXPECTED OUTPUT: |M_| |M_| |_| |_St_| |M| |_| |M| |___|
Your problem is that you never refresh the value of cellArray to be a "new" cell at the end of your for loop. You just write on top of the existing cell. This means that if an M appears in the very first cell, it will appear in every cell. Furthermore, the weapon will appear in every cell after the first time it appears. To fix this, you need to re-initialize cell each time you run the for loop. Just move the initialization to inside the loop:
for(int i=0; i<determinedDungeonSize ;i++)
{
//just move these to the inside of the loop
//so they are fresh each time
String cell = ("|____| ");
char[] cellArray = cell.toCharArray();
int randomProbability = r.nextInt(10000);
if(randomProbability < 5000)
{
cellArray[2] = 'M';
}
if(i == weaponRandom)
{
if(randomProbability < 5000)//this does mean that if the weapon is in the same room as a monster, it will always be Sw. Consider generating a new random value
{
cellArray[3] = 'S';
cellArray[4] = 'w';
}
else
{
cellArray[3] = 'S';
cellArray[4] = 't';
}
}//end if
cell = String.valueOf(cellArray);
dungeon.add(cell);
}//end for
I'm making a sudoku program, and I 'created' a recursive algorithm to solve a sudoku. The problem is, that sometimes it works perfectly and in an instant, sometimes it gets stuck and works 10s of seconds, and sometimes I just have to quit it.
Any ideas what might be causing this?
I left the code as it is, since I'm not sure how you answer the question(if you copy and try it out or just check the logic). If you want, I can just write out snippets.
Thanks!
import java.util.Random;
/**
* #Program Sudoku
*
* #date November 2013
* #hardware MacBook Pro 17", mid 2010, i7, 8GiB RAM
* #IDE eclipse SDK 4.3.1
* #purpose generates a valid 9x9 sudoku grid
*
*/
public class SudokuSolver //seems to werk !!
{
//create a validitychecker object(will be used as Sudoku.isValid();)
validityChecker Sudoku = new validityChecker();
//Create a 2D array where the full sudoku grid will be stored
private int[][] grid = new int[9][9];
//Creates a 2D array for the playable sudoku grid (with elements removed)
private int[][] playingGrid = new int[9][9];
private Random Ran = new Random();
/**
* #purpose use this construct if you wish to generate a new sudoku
* #param difficultyLevel removes amount of elements from sudoku using the equation elementToRemove=40+15*difficultyLevel
*/
SudokuSolver(int difficultyLevel)
{
//generate an empty grid
generateBaseGrid();
//populate it with a valid sudoku
solveSudoku(0,0);
//store this in a new from which elements shall be removed
for(int i = 0; i < grid.length; i++)
playingGrid[i] = grid[i].clone();
//calculate the amount of elements to be removed
int difficultyMultiplier = 15;
int baseDifficulty = 40;
int difficulty = baseDifficulty+difficultyLevel*difficultyMultiplier;
//and remove them
removeElements(difficulty);
}
/**
* #purpose use this constructor if you just want to use methods and solve
* #param the sudoku you wish to solve. values have to be within the range 1-9(inclusive), and -1 for unknown
* #note to get the solved sudoku use the fullGrid getter.
*/
SudokuSolver(int[][] pg)
{
//lets clone out the arrays - we don't want to just have references ...
for(int i = 0; i < pg.length; i++)
grid[i] = pg[i].clone();
for(int i = 0; i < grid.length; i++)
playingGrid[i] = grid[i].clone();
int coords[] = findOnes(grid);
solveSudoku(coords[0],coords[1]);
System.out.println(coords[0]+" "+coords[1]);
}
/**
* Use this if you only wish to use the internal methods
*/
SudokuSolver()
{}
//this method was implemented later, and I'm too lazy to change methods that use the procedure, but don't call the method. Maybe in next version
/**
* #purpose creates a copy of the passed array
* #param the array you wish to be copied
* #return returns a clone of the passed 2D array
*/
public int[][] cloneBoard(int[][] sudokuArray)
{
int[][] result = new int[9][9];
for(int i = 0; i < sudokuArray.length; i++)
result[i] = sudokuArray[i].clone();
return result;
}
/*
*#purpose fills the grid with -1s; This is for proper functionality during validation
*/
private void generateBaseGrid()
{
//iterates through all the values and stores -1s in it
for(int r=0;r<9;r++)
for(int c=0;c<9;c++)
grid[r][c] = -1;
//System.out.println("Base Grid Created");
}
/**
* #purpose checks if there are -1s in the grid, if so the grid is playable (its not a solution)
* #return true if its playable
*/
public boolean isGridPlayable()
{
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
if(grid[i][j]==-1)
return true;
return false;
}
/**
*
* #return the generated grid with all elements (for one without some elements use theGrid()) for generator
* #return the solved grid for solver
*/
public int[][] fullGrid()
{
return grid;
}
/**
* #purpose returns the playing grid
* #return the playable grid
*/
public int[][] theGrid()
{
return playingGrid;
}
/*
* #purpose removes "amnt" of elements from the playingGrid
* #return whether the current method was successful
* #param the amount of elements to be removed
*/
private boolean removeElements(int amnt)
{
if(amnt==0) //yay base case
return true;
for(int i=0; i<20;i++)
{
int r=Ran.nextInt(9);
int c=Ran.nextInt(9);
int element=playingGrid[r][c];
if(element!=-1)
{
playingGrid[r][c]=-1;
if(removeElements(amnt-1))
{return true;}
}else{playingGrid[r][c]=element;//removed as per suggestioni--;}
}
}
return false;
}
//--------------Debugging--------------------------------
public void printUserGrid(int[][] printie)
{
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
{
int x = printie[i][j];
String bexp = Integer.toString(x);
if(x==-1)
bexp="[]";
else bexp+=" ";
System.out.print(bexp+" ");
if(j==2||j==5)
System.out.print(" ");
}
System.out.println();
if(i==2||i==5)
System.out.println();
}
}
// //----------Main only for debugging-----------------------
public static void main(String[] args)
{
SudokuSolver Generator = new SudokuSolver(2);
int[][] generatedGrid = Generator.theGrid();
int[][] fullGrid = Generator.fullGrid();
Generator.printUserGrid(fullGrid);
// Generator.printUserGrid(generatedGrid);
System.out.println("\n\n");
SudokuSolver Solver = new SudokuSolver(generatedGrid);
Solver.printUserGrid(fullGrid);
}
}
EDIT:
One key thing I forgot to mention, the solveSudoku method, it rearranges some of the values. That means if I'm starting with a **3 it doesn't have a problem returning 312 (this is just an example for illustration). So I'd assume there is some serious logic error somewhere in there.
What you are attempting to solve is an Artificial Intelligence problem. Going by brute-force, or better called plain backtracking would actually mean you possibly have an exponential time complexity.
An exponential solution is expected to take long. In some cases where the order of guesswork matches the actual solution, your solver will return with a result faster.
So what you can do:
Read upon the AI technique called Constraint Satisfaction, and try to implement that.
Read upon more specific AI sudoku solving techniques, maybe some research paper if there is one and try and implement that.
This is my version of a SudokuSolver:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class Sudoku
{
//This member has been intentionally left public, as the solved sudoku is
//obtained from here and user will find it much easier to handle this.
//The Sudoku input has to be given through this instance variable only.
//Moreover, many a times, Sudoku inputs as an edit-able 2D array is more
//comfortable than passing a whole 9x9 2D array as a constructor parameter.
public String SUDOKU[][]=new String[9][9];
//This variable records the nature of the Sudoku whether solvable or not
public boolean VALID=true;
public Sudoku()//this constructor can be used to create SUDOKU objects
{
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
SUDOKU[i][j]="";
}
private Sudoku(String SDK[][])//This is constructor kept private for program use
{
SUDOKU=SDK;
}
//This function checks if a certain digit is possible in a particular cell
private boolean isPossible(int n,int i,int j)
{
for(int a=i/3*3;a<i/3*3+3;a++)
for(int b=j/3*3;b<j/3*3+3;b++)
if(SUDOKU[a][b].length()==1 && n==Integer.parseInt(SUDOKU[a][b]))
return false;
for(int k=0;k<9;k++)
if(SUDOKU[i][k].length()==1 && n==Integer.parseInt(SUDOKU[i][k]) || SUDOKU[k][j].length()==1 && n==Integer.parseInt(SUDOKU[k][j]))
return false;
return true;
}
//The following function is compulsory as it is the only function to place appropriate
//possible digits in the cells of the Sudoku. The easiest Sudokus will get solved here.
private void fillPossibles()
{
boolean newdigit=false;
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
if(SUDOKU[i][j].length()!=1)
{
SUDOKU[i][j]="";
int count=0;
for(int k=1;k<10;k++)
if(isPossible(k,i,j))
{
SUDOKU[i][j]+=k;
count++;
}
if(count==1)
newdigit=true;
}
if(newdigit)
fillPossibles();
}
//This following function is optional, if the Sudoku is known to be genuine. As,
//in that case it only increases the solving speed!! But if the nature is not known,
//this function becomes necessary because the nature of the Sudoku is checked here.
//It returns true if any cell is filled with a digit and false for all other cases
private boolean deepFillPossibles(int ai,int aj,int bi,int bj,boolean first)
{
if(SUDOKU!=null)
for(char c='1';c<='9';c++)
{
int count=0,digit=0,possibility=0,i=0,j=0;
boolean valid=true;
for(int a=ai;a<bi;a++)
for(int b=aj;b<bj;b++)
{
if(SUDOKU[a][b].length()==0)
valid=false;
for(int k=0;k<SUDOKU[a][b].length();k++)
if(SUDOKU[a][b].charAt(k)==c)
{
if(SUDOKU[a][b].length()>1)
{
i=a; j=b; count++;
}
if(SUDOKU[a][b].length()==1)
digit++;
possibility++;
}
}
//the following code is executed only if solution() is called first time
if(first && (digit>1 || valid && possibility==0))
{
SUDOKU=null; return false;
}
if(count==1)
{
SUDOKU[i][j]=String.valueOf(c);
fillPossibles(); return true;
}
}
return false;
}
//This function is also optional if Sudoku is genuine. It only combines the solving
//power of fillPossibles() and deepFillPossibles() to reduce memory consumption
//in the next stages. In many cases the Sudoku gets solved at this stage itself.
private void solution(boolean first)
{
fillPossibles();
for(int i=0;i<9;i++)
if(deepFillPossibles(i,0,i+1,9,first) || deepFillPossibles(0,i,9,i+1,first) ||
deepFillPossibles(i/3*3,i%3*3,i/3*3+3,i%3*3+3,first))
i=-1;
}
//This function is the most challenging. No Sudoku can ever escape solution after
//passing this stage. It uses ECHO TREE logic implementing brute force to check all
//kinds of combinations until solution is obtained. It returns a null for no solution.
private void treeSolution(Tracker track)
{
if(SUDOKU==null)
{
track.TRACK_SUDOKU=null;
return;
}
solution(false);
//For a genuine sudoku the statement could have been replaced by "fillPossibles();"
//(Only it would make solving slower and increase memory consumption!)
//But it is risky if there is a doubt regarding the genuineness of the Sudoku
int a=-1,b=-1;
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
if(SUDOKU[i][j].length()>1 && a==-1)
{
a=i; b=j;
}
else if(SUDOKU[i][j].length()==0)
return;
if(a==-1)//checks if the Sudoku is solved or not setting necessary flags
{
track.TRACK_SOLUTION++;
for(int i=0;i<9;i++)
for(int j=0;j<9;track.TRACK_SUDOKU[i][j]=SUDOKU[i][j],j++);
return;
}
String temp[][]=new String[9][9];
for(int k=0;k<SUDOKU[a][b].length();k++)
{
for(int i=0;i<9;i++)
for(int j=0;j<9;temp[i][j]=SUDOKU[i][j],j++);
temp[a][b]=String.valueOf(SUDOKU[a][b].charAt(k));
new Sudoku(temp).treeSolution(track);
//The following condition stops the braching TREE if the Sudoku is solved by
//echoing back to the root, depending on the flags set on being solved
if(track.TRACK_SOLUTION==2 || track.TRACK_SUDOKU==null)
return;
}
return;
}
//This is the last function which has public access and can be called by the user.
//It sets the Sudoku as null if non-genuine and VALIDity as false if no unique solution
public void solve()
{
try
{
for(int i=0;i<9;SUDOKU[i][8]=SUDOKU[i][8],SUDOKU[8][i]=SUDOKU[8][i],i++);
}
catch(Exception e)
{
SUDOKU=null; VALID=false; return;
}
Tracker track=new Tracker();
solution(true);
treeSolution(track);
SUDOKU=track.TRACK_SOLUTION==0?null:track.TRACK_SUDOKU;
if(track.TRACK_SOLUTION!=1)
VALID=false;
}
}
//the following class is purposely designed to easily track the changes during solution,
//including the nature of the Sudoku and the solution of the Sudoku(if possible)
class Tracker
{
protected int TRACK_SOLUTION=0;
protected String TRACK_SUDOKU[][]=new String[9][9];
}
public class SudokuSolver extends JApplet implements KeyListener, MouseListener
{
private String SUDOKU[][]=new String[9][9];
private Sudoku SDK=new Sudoku();
private Image BOX[][]=new Image[9][9],SELECT_IMG;//BOX is an array of square images
private int SELECT_ROW=4,SELECT_COLUMN=4;//stores the position of the selection box
//this function is used to initialize the coloured square images and fonts
public void init()
{
resize(190,190);
setFont(new Font("Dialog",Font.BOLD,12));
addMouseListener(this);
addKeyListener(this);
Graphics g;
for(int i=0;i<9;i++)
for(int j=0;j<9;SUDOKU[i][j]="",j++)
{
BOX[i][j]=createImage(22,22);
g=BOX[i][j].getGraphics();
if((i/3+j/3)%2==0)
g.setColor(Color.yellow);
else
g.setColor(Color.green);
g.fillRect(0,0,21,21);
g.setColor(Color.black);
g.drawRect(0,0,21,21);
}
//the following statements colour the selection box red
SELECT_IMG=createImage(22,22);
g=SELECT_IMG.getGraphics();
g.setColor(Color.red);
g.fillRect(0,0,21,21);
g.setColor(Color.black);
g.drawRect(0,0,21,21);
}
public void mouseExited(MouseEvent e){}
public void mouseEntered(MouseEvent e){}
public void mouseReleased(MouseEvent e){}
public void mouseClicked(MouseEvent e){}
//the following function handles the mouse click operations
public void mousePressed(MouseEvent e)
{
if(SDK.SUDOKU==null)
{
SDK.SUDOKU=new String[9][9];
for(int i=0;i<9;i++)
for(int j=0;j<9;SUDOKU[i][j]="",SDK.SUDOKU[i][j]="",j++);
SDK.VALID=true;
}
if(e.getY()<190 && e.getX()<190 && e.getY()%21!=0 && e.getX()%21!=0)
{
SELECT_ROW=e.getY()/21;
SELECT_COLUMN=e.getX()/21;
}
repaint();
}
public void keyReleased(KeyEvent e){}
//this function manages the operations associated with the various keys
public void keyPressed(KeyEvent e)
{
int code=e.getKeyCode();
if(code==KeyEvent.VK_DELETE || code==KeyEvent.VK_BACK_SPACE || code==KeyEvent.VK_SPACE)
{
SUDOKU[SELECT_ROW][SELECT_COLUMN]=""; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="";
}
switch(code)
{
case KeyEvent.VK_Y : if(SDK.SUDOKU!=null) SDK.VALID=true; break;
case KeyEvent.VK_UP : SELECT_ROW=(SELECT_ROW+8)%9; break;
case KeyEvent.VK_DOWN : SELECT_ROW=(SELECT_ROW+1)%9; break;
case KeyEvent.VK_LEFT : SELECT_COLUMN=(SELECT_COLUMN+8)%9; break;
case KeyEvent.VK_RIGHT : SELECT_COLUMN=(SELECT_COLUMN+1)%9; break;
case KeyEvent.VK_ENTER : SDK.solve(); break;
case KeyEvent.VK_N : if(SDK.SUDOKU!=null){ SDK.VALID=true; code=KeyEvent.VK_ESCAPE;}
case KeyEvent.VK_ESCAPE : if(SDK.SUDOKU==null)
{
SDK.SUDOKU=new String[9][9];
for(int i=0;i<9;i++)
for(int j=0;j<9;SUDOKU[i][j]="",SDK.SUDOKU[i][j]="",j++);
SDK.VALID=true;
}
else
for(int i=0;i<9;i++)
for(int j=0;j<9;SDK.SUDOKU[i][j]=SUDOKU[i][j],j++);
}
repaint();
}
//this function is for entering the numbers in the sudoku grid
public void keyTyped(KeyEvent e)
{
char code=e.getKeyChar();
if(Character.isDigit(code))
switch(code)
{
case '1': SUDOKU[SELECT_ROW][SELECT_COLUMN]="1"; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="1"; break;
case '2': SUDOKU[SELECT_ROW][SELECT_COLUMN]="2"; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="2"; break;
case '3': SUDOKU[SELECT_ROW][SELECT_COLUMN]="3"; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="3"; break;
case '4': SUDOKU[SELECT_ROW][SELECT_COLUMN]="4"; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="4"; break;
case '5': SUDOKU[SELECT_ROW][SELECT_COLUMN]="5"; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="5"; break;
case '6': SUDOKU[SELECT_ROW][SELECT_COLUMN]="6"; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="6"; break;
case '7': SUDOKU[SELECT_ROW][SELECT_COLUMN]="7"; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="7"; break;
case '8': SUDOKU[SELECT_ROW][SELECT_COLUMN]="8"; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="8"; break;
case '9': SUDOKU[SELECT_ROW][SELECT_COLUMN]="9"; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="9"; break;
default : SUDOKU[SELECT_ROW][SELECT_COLUMN]=""; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="";
}
}
//the paint() function is designed to print the the sudoku grid and other messages
public void paint(Graphics g)
{
if(!SDK.VALID)
{
g.setColor(Color.white);
g.fillRect(1,1,188,188);
g.setColor(Color.black);
g.drawRect(0,0,189,189);
if(SDK.SUDOKU==null)
{
g.drawString("INVALID SUDOKU!!",45,80);
g.drawString("[PRESS ESCAPE TO RE-ENTER]",10,105);
}
else
{
g.drawString("INCOMPLETE SUDOKU!!",30,60);
g.drawString("Would you like to see a",30,90);
g.drawString("possible solution?",45,105);
g.drawString("Y / N",80,120);
}
}
else
{
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
{
g.drawImage(BOX[i][j],j*21,i*21,null);
g.drawString(SDK.SUDOKU[i][j],8+j*21,15+i*21);
}
g.drawImage(SELECT_IMG,SELECT_COLUMN*21,SELECT_ROW*21,null);
g.drawString(SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN],8+SELECT_COLUMN*21,15+SELECT_ROW*21);
}
}
}
I have tried to use minimum brute force wherever possible