I am working on a homework project in which we need to generate a state tree for the game of Golf Solitaire. To do this I decided to create a Move class which represents a move from one stack of cards to another - simply put, it holds a reference to the two stacks involved in a single move. Each node in my tree knows what it's move is.
The problem I am currently having is that my trees are much too large - like OutOfMemoryError too large. Edit - I should note that when I make a tree for a very small game I do not get an error, but my tree is still much larger than it should be!
For those unfamiliar with the game: Golf Solitaire Rules. Note - We did not need to implement the restriction on playing a Queen after a King.
public void makeTree()
{
_root = makeNode( null, null, 0 );
}
private Node makeNode( Node parent, Move m, int depth )
{
Node node = new Node( parent, m, depth );
ArrayList<Move> moves = findAllMoves();
if( moves.size() == 0 )
node.setScore( getScore() );
else {
for( Move mv : moves ) {
mv.makeMove();
Node child = makeNode( node, mv, depth++ );
node.addChild( child );
mv.undoMove();
}
}
return node;
}
private ArrayList<Move> findAllMoves()
{
ArrayList<Move> moves = new ArrayList<Move>();
for( int i = 0; i < numPlayPiles; i++ ) {
if( _play[i].size() != 0 ) {
if( Math.abs( _play[i].top().getRank().ordinal() -
discard.getRank().ordinal() ) == 1 ) {
moves.add( new Move( _play[i], _discard ) );
}
}
}
if( _draw.size() != 0 )
moves.add( new Move( _draw, _discard ) );
return moves;
}
_play[i] is simply referencing a pile of cards in the game.
I realize this is a bit much of code to be posting in a question, but my question is simply about my logic. I have a suspicion that my error is in how I am returning makeNode yet I am totally stuck here. Could anybody confirm my suspicion and/or possibly give me some guidance?
For anybody who is curious, my issue was one of not reading the assignment properly. Specifically, my findAllMoves method was the issue. A node should have either a move from the draw pile or all possible moves from play piles, but not both. My code above allows each child, even at the same depth, to each create a Move from the draw pile to discard pile - resulting in a huge amount of duplicate nodes.
It also never checks the end-game conditions. As a result, each leaf node was either a losing score, or 0 (the method would recurse until the draw pile was empty even if the player already won).
Related
I am trying to create "AI" for Nine Men's Morris but I got hardstuck on minMax algorithm. Summing up, I was trying to find the issue for over 10h but didn't manage to. (debugging this recursion is nasty or I am doing it badly or both)
Since I started doubting everything I wrote I decided to post my issue so someone can find anything wrong in my version of minMax. I realise it is really hard task without the whole application so any suggestions where I should triple check my code are also very welcome.
Here is link to the video, explaining minMax, on which I based my implementation: https://www.youtube.com/watch?v=l-hh51ncgDI (First video that pops up on yt after searching for minmax - just in case you want to watch the video and don't want to click the link)
My minMax without alpha-beta pruning:
//turn - tells which player is going to move
//gameStage - what action can be done in this move, where possible actions are: put pawn, move pawn, take opponent's pawn
//depth - tells how far down the game tree should minMax go
//spots - game board
private int minMax(int depth, Turn turn, GameStage gameStage, Spot[] spots){
if(depth==0){
return evaluateBoard(spots);
}
//in my scenario I am playing as WHITE and "AI" is playing as BLACK
//since heuristic (evaluateBoard) returns number equal to black pawns - white pawns
//I have decided that in my minMax algorithm every white turn will try to minimize and black turn will try to maximize
//I dont know if this is correct approach but It seems logical to me so let me know if this is wrong
boolean isMaximizing = turn.equals(Turn.BLACK);
//get all possible (legal) actions based on circumstances
ArrayList<Action> children = gameManager.getAllPossibleActions(spots,turn,gameStage);
//this object will hold information about game circumstances after applying child move
//and this information will be passed in recursive call
ActionResult result;
//placeholder for value returned by minMax()
int eval;
//scenario for maximizing player
if(isMaximizing){
int maxEval = NEGATIVE_INF;
for (Action child : children){
//aplying possible action (child) and passing its result to recursive call
result = gameManager.applyMove(child,turn,spots);
//evaluate child move
eval = minMax(depth-1,result.getTurn(),result.getGameStage(),result.getSpots());
//resets board (which is array of Spots) so that board is not changed after minMax algorithm
//because I am working on the original board to avoid time consuming copies
gameManager.unapplyMove(child,turn,spots,result);
if(maxEval<eval){
maxEval = eval;
//assign child with the biggest value to global static reference
Instances.theBestAction = child;
}
}
return maxEval;
}
//scenario for minimizing player - the same logic as for maximizing player but for minimizing
else{
int minEval = POSITIVE_INF;
for (Action child : children){
result = engine.getGameManager().applyMove(child,turn,spots);
eval = minMax(depth-1,result.getTurn(),result.getGameStage(),result.getSpots());
engine.getGameManager().unapplyMove(child,turn,spots,result);
if(minEval>eval){
minEval=eval;
Instances.theBestAction = child;
}
}
return minEval;
}
}
Simple heuristic for evaluation:
//calculates the difference between black pawns on board
//and white pawns on board
public int evaluateBoard(Spot[] spots) {
int value = 0;
for (Spot spot : spots) {
if (spot.getTurn().equals(Turn.BLACK)) {
value++;
}else if(spot.getTurn().equals(Turn.WHITE)){
value--;
}
}
return value;
}
My issue:
//the same parameters as in minMax() function
public void checkMove(GameStage gameStage, Turn turn, Spot[] spots) {
//one of these must be returned by minMax() function
//because these are the only legal actions that can be done in this turn
ArrayList<Action> possibleActions = gameManager.getAllPossibleActions(spots,turn,gameStage);
//I ignore int returned by minMax() because,
//after execution of this function, action choosed by minMax() is assigned
//to global static reference
minMax(1,turn,gameStage,spots);
//getting action choosed by minMax() from global
//static reference
Action aiAction = Instances.theBestAction;
//flag to check if aiAction is in possibleActions
boolean wasFound = false;
//find the same action returned by minMax() in possibleActions
//change the flag upon finding one
for(Action possibleAction : possibleActions){
if(possibleAction.getStartSpotId() == aiAction.getStartSpotId() &&
possibleAction.getEndSpotId() == aiAction.getEndSpotId() &&
possibleAction.getActionType().equals(aiAction.getActionType())){
wasFound = true;
break;
}
}
//when depth is equal to 1 it always is true
//because there is no other choice, but
//when depth>1 it really soon is false
//so direct child of root is not chosen
System.out.println("wasFound?: "+wasFound);
}
Is the idea behind my implementation of minMax algorithm correct?
I think the error might exist in that you are updating Instances.theBestAction even while evaluating child moves.
For example, lets say 'Move 4' is the true best move that will eventually be returned, but while evaluating 'Move 5', theBestAction is set to the best child action of 'Move 5'. From this point on, you won't update the original theBestAction back to 'Move 4'.
Perhaps just a simple condition that only sets theBestAction when depth == originalDepth?
Rather than using a global, you could also consider returning a struct/object that contains both the best score AND the move that earned the score.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
The community reviewed whether to reopen this question 10 months ago and left it closed:
Original close reason(s) were not resolved
Improve this question
I have a crossword puzzle and a list of words which can be used to solve it (words can be placed multiple times or not even once). There is always a solution for the given crossword and word list.
I searched for clues on how to solve this problem and found out that it is NP-Complete. My maximal crossword size is 250 by 250, the maximal length of the list (amount of words which can be used to solve it) is 200. My goal is to solve crosswords of this size by brute force/backtracking, which should be possible within a few seconds (this is a rough estimation by me, correct me if I am wrong).
For example:
A list of given words which can be used to solve the crossword:
can
music
tuna
hi
The given empty crossword (X are fields which cannot be filled out, the empty fields need to be filled):
The solution:
Now my current approach is to represent the crossword as a 2-D array and search for empty spaces (2 iterations over the crossword). Then I match words to empty spaces depending on their length, then I try all combinations of words to empty spaces which have the same length. This approach got very messy very fast, I got lost trying to implement this, is there a more elegant solution?
The basic idea you have is pretty sensible:
Identify slots on the board.
Try each slot with each word that fits.
If every slots can be filled without conflict, it is solved.
It's an excellent plan.
The next step is to translate it into a design.
For small program like this we can go straight to pseudo code.
The gist of it, as explained by other answers, is recursion:
1 Draw a slot from the slot pool.
2 If slot pool is empty (all slots filled), stop solving.
3 For each word with correct length:
4 If part of the slot is filled, check conflict.
5 If the word does not fit, continue the loop to next word.
// No conflict
6 Fill the slot with the word.
// Try next slot (down a level)
7 Recur from step 1.
8 If the recur found no solution, revert (take the word back) and try next.
// None of them works
9 If no words yield a solution, an upper level need to try another word.
Revert (put the slot back) and go back.
Below is a short but complete example that I cooked up from your requirements.
There is more than one way to skin a cat.
My code swapped step 1 and 2, and combines step 4 to 6 in one fill loop.
Key points:
Use a formatter to fit the code to your style.
The 2D board is stored in a linear character array in row-major order.
This allow the board to be save by clone() and restored by arraycopy.
On creation, the board is scanned for slots in two passes from two directions.
The two slot lists are solved by the same loop, differ mainly in how the slots are filled.
The recur process is displayed, so you can see how it works.
Many assumptions are made. No single letter slot, all words in same case, board is correct etc.
Be patient. Learn whatever is new and give yourself time to absorb it.
Source:
import java.awt.Point;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class Crossword {
public static void main ( String[] args ) {
new Crossword( Arrays.asList( "5 4 4\n#_#_#\n_____\n#_##_\n#_##_\ntuna\nmusic\ncan\nhi".split( "\n" ) ) );
new Crossword( Arrays.asList( "6 6 4\n##_###\n#____#\n___#__\n#_##_#\n#____#\n##_###\nnice\npain\npal\nid".split( "\n" ) ) );
}
private final int height, width; // Board size
private final char[] board; // Current board state. _ is unfilled. # is blocked. other characters are filled.
private final Set<String> words; // List of words
private final Map<Point, Integer> vertical = new HashMap<>(), horizontal = new HashMap<>(); // Vertical and horizontal slots
private String indent = ""; // For formatting log
private void log ( String message, Object... args ) { System.out.println( indent + String.format( message, args ) ); }
private Crossword ( List<String> lines ) {
// Parse input data
final int[] sizes = Stream.of( lines.get(0).split( "\\s+" ) ).mapToInt( Integer::parseInt ).toArray();
width = sizes[0]; height = sizes[1];
board = String.join( "", lines.subList( 1, height+1 ) ).toCharArray();
words = new HashSet<>( lines.subList( height+1, lines.size() ) );
// Find horizontal slots then vertical slots
for ( int y = 0, size ; y < height ; y++ )
for ( int x = 0 ; x < width-1 ; x++ )
if ( isSpace( x, y ) && isSpace( x+1, y ) ) {
for ( size = 2 ; x+size < width && isSpace( x+size, y ) ; size++ ); // Find slot size
horizontal.put( new Point( x, y ), size );
x += size; // Skip past this horizontal slot
}
for ( int x = 0, size ; x < width ; x++ )
for ( int y = 0 ; y < height-1 ; y++ )
if ( isSpace( x, y ) && isSpace( x, y+1 ) ) {
for ( size = 2 ; y+size < height && isSpace( x, y+size ) ; size++ ); // Find slot size
vertical.put( new Point( x, y ), size );
y += size; // Skip past this vertical slot
}
log( "A " + width + "x" + height + " board, " + vertical.size() + " vertical, " + horizontal.size() + " horizontal." );
// Solve the crossword, horizontal first then vertical
final boolean solved = solveHorizontal();
// Show board, either fully filled or totally empty.
for ( int i = 0 ; i < board.length ; i++ ) {
if ( i % width == 0 ) System.out.println();
System.out.print( board[i] );
}
System.out.println( solved ? "\n" : "\nNo solution found\n" );
}
// Helper functions to check or set board cell
private char get ( int x, int y ) { return board[ y * width + x ]; }
private void set ( int x, int y, char character ) { board[ y * width + x ] = character; }
private boolean isSpace ( int x, int y ) { return get( x, y ) == '_'; }
// Fit all horizontal slots, when success move to solve vertical.
private boolean solveHorizontal () {
return solve( horizontal, this::fitHorizontal, "horizontally", this::solveVertical );
}
// Fit all vertical slots, report success when done
private boolean solveVertical () {
return solve( vertical, this::fitVertical, "vertically", () -> true );
}
// Recur each slot, try every word in a loop. When all slots of this kind are filled successfully, run next stage.
private boolean solve ( Map<Point, Integer> slot, BiFunction<Point, String, Boolean> fill, String dir, Supplier<Boolean> next ) {
if ( slot.isEmpty() ) return next.get(); // If finished, move to next stage.
final Point pos = slot.keySet().iterator().next();
final int size = slot.remove( pos );
final char[] state = board.clone();
/* Try each word */ indent += " ";
for ( String word : words ) {
if ( word.length() != size ) continue;
/* If the word fit, recur. If recur success, done! */ log( "Trying %s %s at %d,%d", word, dir, pos.x, pos.y );
if ( fill.apply( pos, word ) && solve( slot, fill, dir, next ) )
return true;
/* Doesn't match. Restore board and try next word */ log( "%s failed %s at %d,%d", word, dir, pos.x, pos.y );
System.arraycopy( state, 0, board, 0, board.length );
}
/* No match. Restore slot and report failure */ indent = indent.substring( 0, indent.length() - 2 );
slot.put( pos, size );
return false;
}
// Try fit a word to a slot. Return false if there is a conflict.
private boolean fitHorizontal ( Point pos, String word ) {
final int x = pos.x, y = pos.y;
for ( int i = 0 ; i < word.length() ; i++ ) {
if ( ! isSpace( x+i, y ) && get( x+i, y ) != word.charAt( i ) ) return false; // Conflict
set( x+i, y, word.charAt( i ) );
}
return true;
}
private boolean fitVertical ( Point pos, String word ) {
final int x = pos.x, y = pos.y;
for ( int i = 0 ; i < word.length() ; i++ ) {
if ( ! isSpace( x, y+i ) && get( x, y+i ) != word.charAt( i ) ) return false; // Conflict
set( x, y+i, word.charAt( i ) );
}
return true;
}
}
Exercise: You can rewrite recursion to iteration; faster and can support bigger boards.
Once that's done it can be converted to multi-thread and run even faster.
You are right the problem is NP-complete. So your best chance is to solve it by brute-force (if you find a polynomial algorithm please tell me, we can both be rich =)).
What I suggest you is to take a look at backtracking. It will allow you to write an elegant (and yet slow given your input size) solution to the crossword problem.
If you need more inspirational material take a look at this solver that uses backtracking as a method to navigate the solution tree.
Note that there are algorithms out there that might in practice perform better than a pure brute-force (even though still of exponential complexity).
Also, a quick search on scholar reveals a good number of papers on the topic that you might want to take a look at, such as the followings:
using genetic algorithm
using a probabilistic approach
A crossword puzzle is a Constraint satisfaction problem which is generally a NP-Complete, but there are many solvers that will apply the most efficient algorithms to a constraint problem that you specify. The Z3 SMT solver can solve these problems very easily and at scale. All you have to do is write a Java program that transforms the crossword puzzle into a SMT problem the solver can understand then gives it to the solver to solve it. Z3 has Java bindings so it should be pretty simple. I have written the Z3 code for solving the first example below. It should not be difficult for you to follow the pattern in your Java program to specify arbitrarily large crossroad puzzles.
; Declare each possible word as string literals
(define-const str1 String "tuna")
(define-const str2 String "music")
(define-const str3 String "can")
(define-const str4 String "hi")
; Define a function that returns true if the given String is equal to one of the possible words defined above.
(define-fun validString ((s String)) Bool
(or (= s str1) (or (= s str2) (or (= s str3) (= s str4)))))
; Declare the strings that need to be solved
(declare-const unknownStr1 String)
(declare-const unknownStr2 String)
(declare-const unknownStr3 String)
(declare-const unknownStr4 String)
; Assert the correct lengths for each of the unknown strings.
(assert (= (str.len unknownStr1) 4))
(assert (= (str.len unknownStr2) 5))
(assert (= (str.len unknownStr3) 3))
(assert (= (str.len unknownStr4) 2))
; Assert each of the unknown strings is one of the possible words.
(assert (validString unknownStr1))
(assert (validString unknownStr2))
(assert (validString unknownStr3))
(assert (validString unknownStr4))
; Where one word in the crossword puzzle intersects another assert that the characters at the intersection point are equal.
(assert (= (str.at unknownStr1 1) (str.at unknownStr2 1)))
(assert (= (str.at unknownStr2 3) (str.at unknownStr4 1)))
(assert (= (str.at unknownStr2 4) (str.at unknownStr3 0)))
; Solve the model
(check-sat)
(get-model)
I recommend the Z3 SMT solver, but there are plenty of other constraint solvers. There is no need for you to implement your own constraint solving algorithm any more than there is a need for you to implement your own sorting algorithm.
To make this problem easier to solve, I'll break this down into smaller, easier problems. Note that I am not including code/algorithms, as I believe that will not help here (If we wanted the best Code, there would be indexes and databases and black magic that makes your head explode just seeing it). Instead, this answer tries to answer the question by talking about methods of thought that will help the OP tackle this problem (and future ones) using the method that works best for the reader.
What you need to know
This answer assumes you know how to do the following
Create and use Objects that have properties and functions
Pick a data structure that works (not necessarily good) for what you want to do with its contents.
Modeling your space
So, it's easy enough to load your crossword into an n by m matrix (2D array, hereby 'grid'), but this is very heard to work with pragmatically. So lets start by parsing your crossword from a grid to a legitimate object.
As far as your program needs to know, each entry in the crossword has 4 properties.
An X-Y coordinate in the grid for the first letter
A direction (down or across)
Word length
Word value
Map of bound indexes
Key: Index of word that is shared with another entry
Value: Entry that index is shared with
(You can make this a tuple and include the shared index from the other entry for easy refrence)
You can find these in the grid based on these rules while scanning.
If Row_1_up is closed and Row_1_down is open, this is the start index of a down word. (scan down for for length. For bound indexes, left or right space will be open. scan left to get linked entry coord-id)
Same as 1 but rotated for across words (You can do this at the same time as the scan for 1)
In your crossword object, you can store the entries using the coordinate+direction as the key for easy reference and easy conversion to/from text grid form.
Using your model
You should now have an object containing a collection of crossword entries, which contain their relevant index bindings. You now need to find a set of values that will satisfy all your entries.
Your entry objects should have helper methods like isValidEntry(str) that checks for the given value, and the current state of the crossword, can I put this word here? By making each object in your model responsible for its own level of logic, the code for the problem one thought layer up can just call the logic without worrying about it's implementation (in this example, Your solver doesn't have to worry about the logic of is a value valid, it can just ask isValidEntry for that)
If you have done the above right, solving the problem is then a simple matter of iterating over all words for all entries to find a solution.
List of sub problems
For reference, here is my list of sub problems that you need to write something to solve.
How can I ideally model my work-space that is easy for me to work with?
For each piece of my model, what does it need to know? What logic can it handle for me?
How can I transform my text input into a usable model object?
How do I solve my problem using my model objects? (For you, it is iterate all words/all entries to find a valid set. Maybe using recursion)
I just implemented a code in Scala to solve such puzzles. I am just using recursion to solve the problem. In short, for each word, I find all possible slots, and pick a slot and fill it with the word, and try to solve the partial puzzle with recursion. If the puzzle cannot be filled with the rest of words, it tries another slot, etc. if not, the puzzle is solved.
Here is the link to my code:
https://github.com/mysilver/AMP/blob/master/Crossword.scala
So I'm currently making a game where the instructions are to move left or right within an array using the integer stored at a marked index (circle in this case) until we can get the circle to the last index of the array. The last integer of the array is always 0.
For example,
[4] 1 2 3 1 0, here we start at the circle 0 (index)
We move 4 to the right, 4 1 2 3 [1] 0
Then 1 time to the right, 4 1 2 3 1 [0]. Here the game stops and we win.
My code is as follows for a recursive method:
public static boolean rightWing (int circle, int[] game, List<Integer> checkerList){
int last = game.length-1;
if (circle == last){ // base case for recursion
return true;
}
if (circle < 0){ // if we go out of bounds on the left
return false;
}
if (circle > last){ // if we go out of bounds on the right
return false;
}
if (checkerList.contains(circle)){ // check for the impossible case
return false;
}
checkerList.add(circle); // adds the circle value for the last check to checkerList so we can check for the impossible case
int moveRight = circle + game[circle]; // these two integers help the game move according to the value of the int at circle
int moveLeft = circle - game[circle];
return rightWing( moveRight, game, checkerList) || rightWing(moveLeft, game,checkerList);
}
This works great, but the only problem is it's recursive and slow. I'm trying to redesign it using loops and stacks/queues to make it more efficient, but I'm stuck after writing this (in pseudo):
Boolean rightWing (int circle, List<int> game, List<int> checkerList)
Int lastPlace = game.size() - 1
For int i <- 0 to game.size() - 1 do
If i equals lastPlace then // returns true when i is at the last position of the game
Return true
Any input on how to go forward would be appreciated!
The most important bit: when debugging app for the slowness, you should collect some performance data first to identify where your app is spending the most of its time. Otherwise fixing performance is inefficient. You can use jvisualvm it's bundled with jdk.
Data structures rule the world of performance
One thing why it can be slow is because of this:
if (checkerList.contains(circle)){ // check for the impossible case
return false;
}
The more items you have in the list, the slower it becomes. List has linear complexity for the contains method. You can make it constant complexity if you'll use HashSet. E.g. if you have list with 100 elements, this part will be around slower 100 times with List than with HashSet.
Another thing which might be taking some time is boxing/unboxing: each time you put element to the list, int is being wrapped into new Integer object - this is called boxing. You might want to use IntSet to avoid boxing/unboxing and save on the GC time.
Converting to the iterative form
I won't expect this to affect your application speed, but just for the sake of completeness of the answer.
Converting recursive app to iterative form is pretty simple: each of the method parameters under the cover is stored on a hidden stack on each call of your (or others function). During conversion you just create your own stack and manage it manually
public static boolean rightWingRecursive(int circle, int[] game) {
Set<Integer> checkerList = new HashSet<Integer>();
Deque<Integer> statesToExplore = new LinkedList<>();
int last = game.length - 1;
statesToExplore.push(circle);
while (!statesToExplore.isEmpty()) {
int circleState = statesToExplore.pop();
if (circleState == last) { // base case for recursion
return true;
}
if (circleState < 0) { // if we go out of bounds on the left
continue;
}
if (circleState > last) { // if we go out of bounds on the right
continue;
}
if (checkerList.contains(circle)) { // check for the impossible case
continue;
}
checkerList.add(circle); // adds the circle value for the last check to
// checkerList so we can check for the
// impossible case
int moveRight = circle + game[circle]; // these two integers help the
// game move according to the
// value of the int at circle
int moveLeft = circle - game[circle];
statesToExplore.push(moveRight);
statesToExplore.push(moveLeft);
}
return false;
}
Im trying to implement the Breadth-First algorithm for 8 puzzle game. I know that it is not a new case and theres a bunch of solutions on web, but I want to make it on my way of thinking.
This code already finds the node result, which is
123
456
780
But it takes 350,000 steps to do it!
Any thoughts would be appreciated!
=)
//This method receives a Collection of `Nodo` objects, each one will be checked and compare with the finalGoal
public void percorreNodos(Collection<Nodo> nodosBase)
{
//In this class a have an array that has all the IDs os nodes that already has been checked
//The ID of a Node, is the representation: 123456780, 354126870 , etc..
System.out.println("idsPercorrido.size() = " + idsPercorridos.size());
//Check if the collection nodosBase contains the finalGoal
Iterator<Nodo> iterator = nodosBase.iterator();
while( iterator.hasNext() )
{
Nodo nodoBase = (Nodo) iterator.next();
//If this current node has already been checked, we dont have to check it again
idsPercorridos.add( nodoBase.getId() );
//Just print the node (sysout)
nodoBase.print();
contPassos++;
System.out.println( "\n" + contPassos + " STEPS(number of nodes checked)..." );
//Check if this node is the final goal
if( nodoBase.isObjetivoFinal() )
{
//set the variable indicating that the result has been found
encontrouObjetivo = true;
System.out.println( "Resultado alcancado EM " + contPassos + " PASSOS..." );
nodoBase.print();
break;
}
}
// Now that we know that no one Node of nosoBase collection is the final goal, we are going to generate the next children to be checked, and call this function recursively
//Just confirm that we didnt find the goal
if(encontrouObjetivo == false)
{
//Creates the next frontier
Collection<Nodo> novaFronteira = new HashSet<Nodo>();
for(Nodo nodoPai : nodosBase)
{
//Generate each Node its childrens and add to a collection called "novaFronteira"
Collection<Nodo> filhos = nodoPai.gerarFilhos();
for(Nodo filho : filhos)
{
//idsPercorridos is a collection<String> which contains all the nodes ids that we checked, we dont want to check a node more than one time !
if( idsPercorridos.contains( filho.getId() ) == false )
{
novaFronteira.add( filho );
}
}
}
this.percorreNodos( novaFronteira );
}
}
You could make sure you don't add duplicate elements to novaFronteira.
There's nothing preventing this code:
for(Nodo nodoPai : nodosBase)
{
Collection<Nodo> filhos = nodoPai.gerarFilhos();
for(Nodo filho : filhos)
{
if( idsPercorridos.contains( filho.getId() ) == false )
{
novaFronteira.add( filho );
}
}
}
From adding many duplicate nodes to novaFronteira.
If you were to add to idsPercorridos inside the if-statement, that would prevent this from happening, and result in less steps, although, depending on exactly what your data and data structures looks like, the added running time of this call may actually make it take longer than it did originally.
If the problem is running time, you should make sure that idsPercorridos is a TreeSet or HashSet, as these allow for efficient contains calls, as opposed to ArrayList or LinkedList, which don't.
If this doesn't help, you could try using the A* algorithm instead, which involves adding a heuristic function to each node, which is the distance to the target - this allows us to explore the nodes closer to the target first, often resulting in less steps to get there.
A good heuristic function might be the sum of Manhattan distances between each tile and its target location.
Note that this would involve quite a few changes to your current code.
According to Wikipedia there are 9!/2 = 181440 possible solvable combinations to this puzzle. If you check each node for each of these combinations (which you don't, but it makes the calculation easier), it makes about (9!/2) * 9 = 1,632,960 steps. Therefore, there I don't see an issue if it takes your algorithm 350,000 steps because a computer can do those steps really fast.
Here is the algorithm (not working) Please let me know where the error is
Thanks
private void checkSouth(Location point, int player) {
//Loop through everything south
boolean isthereAnOppositePlayer=false;
int oppositePlayer=0;
//Set opposite player
if (player==1) {
oppositePlayer=2;
}else{
oppositePlayer=1;
}
for (int i = point.getVertical(); i < 8; i++) {
//Create a location point with the current location being compared
MyLocation locationBeingChecked= new MyLocation();
locationBeingChecked.setHorizontal(point.getHorizontal());
locationBeingChecked.setVertical(i);
int value = board[locationBeingChecked.getVertical()][locationBeingChecked.getHorizontal()];
//If the first checked is the opposite player
if (value==oppositePlayer) {
//Then potential to evaluate more
isthereAnOppositePlayer=true;
}
//If it isn't an opposite player, then break
if(!isthereAnOppositePlayer && value!=0){
break;
}
//If another of the player's piece found or 0, then end
if (isthereAnOppositePlayer && value==player || isthereAnOppositePlayer && value==0) {
break;
//end
}
//Add to number of players to flip
if(isthereAnOppositePlayer && value==oppositePlayer && value!=0){
//add to array
addToPiecesToTurn(locationBeingChecked);
}
}
}
It looks like the locations that got rotated back to the other player are the exact same as those rotated during the first move. I would guess that the array being populated by addToPiecesToTurn is perhaps not being cleared out between each move, so all the previous locations are still in there.
If you are storing the pieces to be turned in an ArrayList, you can use the clear() method to erase the contents of the collection between each turn.
Another possible problem is that you are checking for the opposite player, and then instantly beginning to populate addToPiecesToTurn. However, the pieces in that direction are not necessarily valid to be rotated unless they are "sandwiched" in by a second location containing the current player's piece. I don't think your code is properly checking for that case; when that happens, you'll want to somehow skip flipping those pieces to the other player, such as clearing out the array of piecesToTurn.
Edit: Looking at your current solution where you are implementing every direction separately, you are going to have a lot of duplicated code. If you think about what it means to walk along a certain direction, you can think of it as adjusting the x/y value by a "step" amount. The step amount could be -1 for backwards, 0 for no move, or 1 for forwards. Then you could create a single method that handles all directions without duplicating the logic:
private void checkDirection(Location point, int player, int yStep, int xStep) {
int x = point.getHorizontal() + xStep;
int y = point.getVertical() + yStep;
MyLocation locationBeingChecked = new MyLocation();
locationBeingChecked.setHorizontal(x);
locationBeingChecked.setVertical(y);
while (isValid(locationBeingChecked)) {
// do the logic here
x += xStep;
y += yStep;
locationBeingChecked = new MyLocation();
locationBeingChecked.setHorizontal(x);
locationBeingChecked.setVertical(y);
}
}
You would need to implement isValid to check that the location is valid, i.e., in the board. Then you could call this method for each direction:
// north
checkDirection(curPoint, curPlayer, -1, 0);
// north-east
checkDirection(curPoint, curPlayer, -1, 1);
// east
checkDirection(curPoint, curPlayer, 0, 1);
// etc
This is the sort of problem that is ripe for some unit testing. You could very easily set up a board, play a move, and validate the answer, and the test results would give plenty of insight into where your expectations and reality diverge.
why didn't you use a 2d array ?
each cell would contain an enum : EMPTY, PLAYER_1, PLAYER_2 .
then, in order to go over the cells, you simply use loops for each direction.
for example, upon clicking on a cell , checking to the right would be:
for(int x=pressedLocation.x+1;x<cells[pressedLocation.y].length;++x)
{
Cell cell=cells[pressedLocation.y][x];
if(cell==EMPTY||cell==currentPlayerCell)
break;
cells[pressedLocation.y][x]=currentPlayerCell;
}
checking from top to bottom would be:
for(int y=pressedLocation.y+1;y<cells.length;++y)
{
Cell cell=cells[y][pressedLocation.x];
if(cell==EMPTY||cell==currentPlayerCell)
break;
cells[y][pressedLocation.x]=currentPlayerCell;
}