317 lines
5.1 KiB
C++
317 lines
5.1 KiB
C++
// Alexander Huddleston 223000555
|
|
// CSCE420
|
|
// Due: October 2, 2017 (changed to 4)
|
|
// hw1pr3.cpp
|
|
|
|
// Purpose: A breadth-first search program solving a 15-puzzle problem.
|
|
|
|
#include<iostream>
|
|
#include<vector>
|
|
#include<queue>
|
|
|
|
using namespace std;
|
|
|
|
// I could move this into a separate header file, due to time
|
|
// constrains I am keeping it in main for now.
|
|
class GameBoard
|
|
{
|
|
private:
|
|
// A string representing the state of the board.
|
|
string state = "";
|
|
// Which index the blank tile is at.
|
|
int blank = 0;
|
|
// List of game moves so far.
|
|
vector<string> moves;
|
|
public:
|
|
// Should only be used at the beginning to set initial state.
|
|
void setBoardState(string s)
|
|
{
|
|
state = s;
|
|
blank = s.find('a');
|
|
}
|
|
// Return the current board state.
|
|
string getBoardState()
|
|
{
|
|
return state;
|
|
}
|
|
// Return the vector of move strings.
|
|
vector<string> getMoves()
|
|
{
|
|
return moves;
|
|
}
|
|
// Change board state, make move. Not necessarily in that order.
|
|
void swap(char c)
|
|
{
|
|
char temp = 'p';
|
|
switch(c)
|
|
{
|
|
case 'l':
|
|
temp = state[blank - 1];
|
|
state[blank - 1] = 'a';
|
|
state[blank] = temp;
|
|
blank = --blank;
|
|
break;
|
|
case 'r':
|
|
temp = state[blank + 1];
|
|
state[blank + 1] = 'a';
|
|
state[blank] = temp;
|
|
blank = ++blank;
|
|
break;
|
|
case 'u':
|
|
temp = state[blank - 4];
|
|
state[blank - 4] = 'a';
|
|
state[blank] = temp;
|
|
blank = blank - 4;
|
|
break;
|
|
case 'd':
|
|
temp = state[blank + 4];
|
|
state[blank + 4] = 'a';
|
|
state[blank] = temp;
|
|
blank = blank + 4;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
// Debugging
|
|
//print_move(c, state);
|
|
moves.push_back(c + state);
|
|
}
|
|
// Check if a move is legal.
|
|
bool isLegal(char c)
|
|
{
|
|
if(c == 'l')
|
|
{
|
|
return ((blank % 4) > 0);
|
|
}
|
|
if(c == 'r')
|
|
{
|
|
return ((blank % 4) < 3);
|
|
}
|
|
if(c == 'u')
|
|
{
|
|
return (blank > 3);
|
|
}
|
|
if(c == 'd')
|
|
{
|
|
return (blank < 12);
|
|
}
|
|
}
|
|
// Check if we have solved the puzzle
|
|
bool isSolved(string s)
|
|
{
|
|
if(moves.size() > 0)
|
|
{
|
|
return !(moves[moves.size() - 1].substr(1).compare(s));
|
|
}
|
|
else
|
|
{
|
|
return !(state.compare(s));
|
|
}
|
|
}
|
|
// Useful if we want the previous board state back.
|
|
void resetLastMove()
|
|
{
|
|
if(moves.size() > 0)
|
|
{
|
|
moves.pop_back();
|
|
moves.pop_back();
|
|
}
|
|
else
|
|
{
|
|
cerr << "Nothing to reset." << endl;
|
|
}
|
|
}
|
|
} gb;
|
|
|
|
// Global GameBoard queue.
|
|
queue<GameBoard> gbq;
|
|
|
|
string get_input_game_board()
|
|
{
|
|
string input = "";
|
|
getline(cin, input);
|
|
string output = "";
|
|
|
|
string token = "";
|
|
|
|
for(char c : input)
|
|
{
|
|
if(c == ',')
|
|
{
|
|
output += (char)(stoi(token) + 97);
|
|
token = "";
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
token += c;
|
|
}
|
|
}
|
|
output += (char)(stoi(token) + 97);
|
|
|
|
return output;
|
|
}
|
|
|
|
void print_move(char m, string gb)
|
|
{
|
|
string move = "";
|
|
switch(m)
|
|
{
|
|
case 'l':
|
|
move = "Left";
|
|
break;
|
|
case 'r':
|
|
move = "Right";
|
|
break;
|
|
case 'u':
|
|
move = "Up";
|
|
break;
|
|
case 'd':
|
|
move = "Down";
|
|
break;
|
|
default:
|
|
move = "Start";
|
|
break;
|
|
}
|
|
|
|
for(int x = 0; x < 16; ++x)
|
|
{
|
|
if(x == 0)
|
|
{
|
|
cout << "\t" << move << "\t";
|
|
}
|
|
else if(x % 4 == 0)
|
|
{
|
|
cout << "\n\t\t";
|
|
}
|
|
|
|
if(((int)gb[x]) - 97 < 10)
|
|
{
|
|
cout << ((int)gb[x] - 97) << " ";
|
|
}
|
|
else
|
|
{
|
|
cout << ((int)gb[x] - 97) << " ";
|
|
}
|
|
}
|
|
if(m == 's')
|
|
{
|
|
cout << "\nSwap the blank\n";
|
|
}
|
|
else
|
|
{
|
|
cout << "\n\n";
|
|
}
|
|
}
|
|
|
|
GameBoard solve_game(GameBoard gb, string solved)
|
|
{
|
|
gbq.pop();
|
|
if(gb.isSolved(solved))
|
|
{
|
|
return gb;
|
|
}
|
|
if(gb.isLegal('l'))
|
|
{
|
|
gb.swap('l');
|
|
gbq.push(gb);
|
|
if(gb.isSolved(solved))
|
|
{
|
|
return gb;
|
|
}
|
|
else
|
|
{
|
|
gb.swap('r');
|
|
gb.resetLastMove();
|
|
}
|
|
}
|
|
if(gb.isLegal('r'))
|
|
{
|
|
gb.swap('r');
|
|
gbq.push(gb);
|
|
if(gb.isSolved(solved))
|
|
{
|
|
return gb;
|
|
}
|
|
else
|
|
{
|
|
gb.swap('l');
|
|
gb.resetLastMove();
|
|
}
|
|
}
|
|
if(gb.isLegal('u'))
|
|
{
|
|
gb.swap('u');
|
|
gbq.push(gb);
|
|
if(gb.isSolved(solved))
|
|
{
|
|
return gb;
|
|
}
|
|
else
|
|
{
|
|
gb.swap('d');
|
|
gb.resetLastMove();
|
|
}
|
|
}
|
|
if(gb.isLegal('d'))
|
|
{
|
|
gb.swap('d');
|
|
gbq.push(gb);
|
|
if(gb.isSolved(solved))
|
|
{
|
|
return gb;
|
|
}
|
|
else
|
|
{
|
|
gb.swap('u');
|
|
gb.resetLastMove();
|
|
}
|
|
}
|
|
return solve_game(gbq.front(), solved);
|
|
}
|
|
|
|
int main()
|
|
{
|
|
// Initializing with simple test case just in case something goes wrong.
|
|
string solved = "bcdefghijklmnopa";
|
|
string init = "bcdefghijkalnopm";
|
|
|
|
// Getting input.
|
|
cout << "Enter 15-puzzle starting state by rows (0 for blank):" << endl;
|
|
init = get_input_game_board();
|
|
|
|
cout << "Enter ending state by rows (0 for blank):" << endl;
|
|
solved = get_input_game_board();
|
|
|
|
// Push starting board into queue, start recursion.
|
|
gb.setBoardState(init);
|
|
gbq.push(gb);
|
|
gb = solve_game(gbq.front(), solved);
|
|
|
|
// Print the solution.
|
|
cout << "Solution:" << endl;
|
|
print_move('s', init);
|
|
|
|
if(gb.getMoves().size() > 0)
|
|
{
|
|
for(string m : gb.getMoves())
|
|
{
|
|
char c = m[0];
|
|
string state = m.substr(1);
|
|
print_move(c, state);
|
|
}
|
|
}
|
|
|
|
// I added various pieces of code to help the program
|
|
// if the user decides to put in an already solved puzzle.
|
|
if(gb.getMoves().size() > 0)
|
|
{
|
|
cout << "Done!\tGenerated " << gb.getMoves().size() << " states." << endl;
|
|
}
|
|
else
|
|
{
|
|
cout << "You gave me a solved puzzle." << endl;
|
|
}
|
|
|
|
return 0;
|
|
}
|