一、实验目的
通过编程实验,体会并理解人工智能领域常用的新型搜索算法的测试用例 —— (n^2 -1) 数码问题。
二、实验内容和要求
通过深度优先搜索(DFS)、宽度优先搜索(BFS)、A*搜索算法来求解 (n^2 -1) 数码难题,要求如下:
- 初始状态以及目标状态形如下图。
- 输出完整的从初始状态到目标状态的动作序列。
- 对比3种算法的时间、空间消耗。
三、参考实验环境
CPU: Intel Core i5-8400 @ 2.80 GHz / 3.80 GHz, 6C6T
指令集: MMX, SSE, SSE2, SSE3, SSE4.1, SSE4.2, EM64T, VT-x, AES, AVX, AVX2, FMA3
功耗限制: PL1 = 75 W, PL2 = 130 W
步进: Stepping A, Revision U0
操作系统: Windows 10 Professional 64-bit
IDE: Visual Studio 2019
编译器: Microsoft Visual C++(MSVC) Compiler
参数: /std:c17 /std:c++latest /O2 /Oi /GL
参数解释: ISO C17 (2018) Standard (/std:c17)
Preview - Features from the Latest C++ Working Draft (/std:c++latest) (启用 C++20 最新特性)
Maximum Optimization (Favor Speed) (/O2)
Enable Intrinsic Functions: Yes (/Oi)
Whole Program Optimization: Yes (/GL)
四、实验原理
深度优先搜索(DFS)
DFS 是一种搜索树或图的算法。在搜索过程中,该算法总是先尽量向深处(离出发点更远的位置,如果是树,则是从树根开始,往远离根的方向)搜索,再回溯并搜索其它分支。
虽然一些问题不直接具有树或图的结构,但可将其所有的状态视为一棵解答树或图。在本例中,棋盘的不同状态都能看成树的一个节点,每走一步相当于访问一个邻接节点。树根代表棋盘的初始状态。
广度优先搜索(BFS)
BFS 也是一种搜索树或图的算法。在搜索过程中,该算法总是先尽量搜索兄弟节点,再进入更深层的节点继续搜索。
A*搜索算法
A* 搜索算法属于启发式搜索算法,常用于求解最短路(最低开销路径)等问题。通过引入适当的启发式算法,在给出近似最优解的情形下,往往能比保证给出最优解的算法(如:Dijkstra 算法)具有更快的执行速率和更优秀的空间消耗。不同的启发式算法可以令 A* 算法具有各种不同的表现。
理想情况下,A* 亦可以保证给出最优解。
8 数码问题可能有解的条件
首先,我们把去掉空格后的 8 个数看成排列。不难发现:
· 空格左右移动,8 个数构成的排列中,没有任何两个数发生了位置上的交换,逆序数不变。
· 空格上下移动,8 个数构成的排列中,发生 2 次对换,它能通过2次相邻对换等效实现,逆序数不变。
即:初态棋盘对应的排列为奇(偶)排列时,不论空格怎样移动,得到的新棋盘对应的排列依然为奇(偶)排列。
因此,当初态与终态对应的排列的奇偶性不同时,无解。
事实上,有如下充分必要条件:
8 数码问题无解 ⟺ 初态与终态对应的排列的奇偶性不同。
8 数码问题有解 ⟺ 初态与终态对应的排列的奇偶性相同。
篇幅所限,这里不予证明(其实是我不会)。
可以通过改动归并排序算法,在 O(n log n) 的时间复杂度内求得一个排列的逆序数,n 为排列的长度。算法的正确性证明详见:https://blog.csdn.net/COFACTOR/article/details/109005737。
本命题可以推广到更大规模的 15 数码问题、24 数码问题等。
五、源代码
代码解析
输入棋盘的初始状态和目标状态,在验证输入的样例有解后,本代码将依次使用 DFS、BFS 和 A* 算法求解。
本代码使用的 A* 算法是在 BFS 的基础上修改得到的。BFS 总是先扩展深度最浅的状态节点,但 A* 维护一个优先队列,每次选出 f(x) = g(x) + h(x) 最小的节点先行扩展。其中,g(x) 代表从当前状态 x 对应的节点在解答树中的深度;本代码选用当前状态 x与目标状态的 Hamming 距离作为启发式函数 h(x)。
为了避免陷入死循环,本代码还会在搜索过程中通过哈希(使用自定义哈希仿函数的 std::unordered_set 来实现)来判定从根节点到准备扩展的节点的路径上是否有重复的节点。若是,则不再继续扩展此节点。
在 DFS 的过程中,没有显式构造解答树。在 BFS 的过程中,一边构造解答树(树节点为结构体 bfs_node),一边使用队列(std::queue)进行搜索。在 A* 搜索的过程中,同样一边构造解答树(树节点为结构体 astar_node,节点要记录启发式搜索需要的额外信息),一边使用优先队列(std::priority_queue)进行搜索。
代码特色
·支持更大规模的此类问题,例如 15 数码问题和 24 数码问题。可以通过修改代码中预留的常量来求解这些问题。
·支持为棋盘填入不同类型、不同大小的数;可以使用不同的数表示空格,默认为数 0。
相比之下,许多 8 数码问题的题目或实现代码只能为空格以外的 8 格分别填入 1 ~ 8 这八个整数。
·采用 C++ 风格与 C++11 开始的新特性编写代码。包括但不限于:
·函数模板(template<class _Ty>)。
· nullptr 关键字;
· STL 容器新增的成员函数 emplace、emplace_back;
·新标准的数据结构,如 std::array、std::unordered_set;
· C++ 风格的输入输出;
· C++ 风格的类型别名,如 using board_data_type = unsigned;;
· C++ 风格的类型转换,如 static_cast<>();
·高精度计时库 <chrono> 用于进行三种算法的运行耗时的测量。
增强代码运行性能的同时,也增强可读性。
源代码
#include <algorithm>
#include <array>
#include <chrono>
#include <iostream>
#include <queue>
#include <set>
#include <unordered_set>
#include <vector>
using namespace std;
using namespace std::chrono;
using board_data_type = unsigned;
using board_coord_type = unsigned;
using board_depth_type = size_t;
using board_heuristic_value_type = size_t;
const board_coord_type BOARD_ROW_COUNT = 3, BOARD_COLUMN_COUNT = 3;
using board_column_type = array<board_data_type, BOARD_COLUMN_COUNT>;
using board_type = array<board_column_type, BOARD_ROW_COUNT>;
const board_data_type blank_token = 0;
board_type start, goal;
board_coord_type xs, ys;
struct step {
board_type board;
board_coord_type x, y;
step() {
}
step(const board_type& B, const board_coord_type X, const board_coord_type Y) : board(B), x(X), y(Y) {
}
};
inline bool can_up(const board_coord_type x) {
if (x > 0) return true;
return false;
}
inline bool can_down(const board_coord_type x) {
if (x < BOARD_ROW_COUNT - 1) return true;
return false;
}
inline bool can_left(const board_coord_type y) {
if (y > 0) return true;
return false;
}
inline bool can_right(const board_coord_type y) {
if (y < BOARD_COLUMN_COUNT - 1) return true;
return false;
}
inline void up_b(board_type& board, board_coord_type& x, board_coord_type& y) {
swap(board[x][y], board[x - 1][y]);
--x;
}
inline void down_b(board_type& board, board_coord_type& x, board_coord_type& y) {
swap(board[x][y], board[x + 1][y]);
++x;
}
inline void left_b(board_type& board, board_coord_type& x, board_coord_type& y) {
swap(board[x][y], board[x][y - 1]);
--y;
}
inline void right_b(board_type& board, board_coord_type& x, board_coord_type& y) {
swap(board[x][y], board[x][y + 1]);
++y;
}
inline void up_b(step& s) {
swap(s.board[s.x][s.y], s.board[s.x - 1][s.y]);
--s.x;
}
inline void down_b(step& s) {
swap(s.board[s.x][s.y], s.board[s.x + 1][s.y]);
++s.x;
}
inline void left_b(step& s) {
swap(s.board[s.x][s.y], s.board[s.x][s.y - 1]);
--s.y;
}
inline void right_b(step& s) {
swap(s.board[s.x][s.y], s.board[s.x][s.y + 1]);
++s.y;
}
void print_solution(const vector<step>& solution) {
cout << "----------------------------------------------------------------" << endl;
for (board_coord_type i = 0; i < BOARD_ROW_COUNT; ++i) {
for (board_coord_type j = 0; j < BOARD_COLUMN_COUNT; ++j) cout << solution.cbegin()->board[i][j];
//cout << '\n';
cout << ' ';
}
for (vector<step>::const_iterator h = solution.cbegin() + 1; h != solution.cend(); ++h) {
if (h->x - (h - 1)->x == -1 && h->y - (h - 1)->y == 0) cout << "Up" << endl;
else if (h->x - (h - 1)->x == 1 && h->y - (h - 1)->y == 0) cout << "Down" << endl;
else if (h->x - (h - 1)->x == 0 && h->y - (h - 1)->y == -1) cout << "Left" << endl;
else if (h->x - (h - 1)->x == 0 && h->y - (h - 1)->y == 1) cout << "Right" << endl;
for (board_coord_type i = 0; i < BOARD_ROW_COUNT; ++i) {
for (board_coord_type j = 0; j < BOARD_COLUMN_COUNT; ++j) cout << h->board[i][j];
//cout << '\n';
cout << ' ';
}
//cout << '\n';
//cout << '\t';
}
cout << "\n----------------------------------------------------------------" << endl;
}
struct board_hash {
size_t operator()(const board_type& board) const {
size_t ret = 0, temp = 0;
for (board_coord_type i = 0; i < BOARD_ROW_COUNT; ++i) {
for (board_coord_type j = 0; j < BOARD_COLUMN_COUNT; ++j) {
temp += hash<board_data_type>{
}(board[i][j]);
ret += temp * temp;
}
}
return ret;
}
};
inline bool equal_by_board_hash(const board_type& board) {
static const size_t Hgoal = board_hash{
}(goal);
if (board_hash{
}(board) != Hgoal) return false;
return board == goal;
}
bool result;
vector<step> solution;
//set<board_type> container_for_qsearch;
unordered_set<board_type, board_hash> container_for_qsearch;
bool dfs_solve() {
if (solution.back().board == goal) return true;
//if (equal_by_board_hash(solution.back().board) == true) return true;
if (can_up(solution.back().x)) {
step t = solution.back();
up_b(t);
if (container_for_qsearch.emplace(t.board).second == true) {
solution.emplace_back(t);
if (dfs_solve() == true) return true;
}
}
if (can_down(solution.back().x)) {
step t = solution.back();
down_b(t);
if (container_for_qsearch.emplace(t.board).second == true) {
solution.emplace_back(t);
if (dfs_solve() == true) return true;
}
}
if (can_left(solution.back().y)) {
step t = solution.back();
left_b(t);
if (container_for_qsearch.emplace(t.board).second == true) {
solution.emplace_back(t);
if (dfs_solve() == true) return true;
}
}
if (can_right(solution.back().y)) {
step t = solution.back();
right_b(t);
if (container_for_qsearch.emplace(t.board).second == true) {
solution.emplace_back(t);
if (dfs_solve() == true) return true;
}
}
container_for_qsearch.erase(solution.back().board);
solution.pop_back();
return false;
}
struct bfs_node : public step {
bfs_node* parent;
array<bfs_node*, 4> child;
bfs_node() {
}
bfs_node(const board_type& B, const board_coord_type X, const board_coord_type Y) : step(B, X, Y), parent(nullptr), child({
nullptr,nullptr,nullptr,nullptr }) {
}
bfs_node(const board_type& B, const board_coord_type X, const board_coord_type Y, bfs_node* const ParentPtr) : step(B, X, Y), parent(ParentPtr), child({
nullptr,nullptr,nullptr,nullptr }) {
}
bfs_node(const step& s) : step(s.board, s.x, s.y), parent(nullptr), child({
nullptr,nullptr,nullptr,nullptr }) {
}
bfs_node(const step& s, bfs_node* const ParentPtr) : step(s.board, s.x, s.y), parent(ParentPtr), child({
nullptr,nullptr,nullptr,nullptr }) {
}
};
bool bfs_solve() {
queue<bfs_node*> q;
bfs_node* t = new bfs_node(start, xs, ys);
q.emplace(t);
container_for_qsearch.emplace(t->board);
while (q.empty() == false) {
t = q.front();
q.pop();
if (t->board == goal/*equal_by_board_hash(solution.back().board) == true*/) {
bfs_node* p = t;
while (p != nullptr) {
solution.emplace_back(*p);
p = p->parent;
}
reverse(solution.begin(), solution.end());
return true;
}
if (can_up(t->x)) {
step u = *t;
up_b(u);
if (container_for_qsearch.emplace(u.board).second == true) {
t->child[0] = new bfs_node(u, t);
q.emplace(t->child[0]);
}
}
if (can_down(t->x)) {
step u = *t;
down_b(u);
if (container_for_qsearch.emplace(u.board).second == true) {
t->child[1] = new bfs_node(u, t);
q.emplace(t->child[1]);
}
}
if (can_left(t->y)) {
step u = *t;
left_b(u);
if (container_for_qsearch.emplace(u.board).second == true) {
t->child[2] = new bfs_node(u, t);
q.emplace(t->child[2]);
}
}
if (can_right(t->y)) {
step u = *t;
right_b(u);
if (container_for_qsearch.emplace(u.board).second == true) {
t->child[3] = new bfs_node(u, t);
q.emplace(t->child[3]);
}
}
}
return false;
}
struct astar_node : public bfs_node {
board_depth_type depth;
astar_node() {
}
astar_node(const board_type& B, const board_coord_type X, const board_coord_type Y) : bfs_node(B,X,Y), depth(0) {
}
astar_node(const board_type& B, const board_coord_type X, const board_coord_type Y, bfs_node* const ParentPtr) : bfs_node(B, X, Y, ParentPtr), depth(0) {
}
astar_node(const board_type& B, const board_coord_type X, const board_coord_type Y, bfs_node* const ParentPtr, const size_t Depth) : bfs_node(B, X, Y, ParentPtr), depth(Depth) {
}
astar_node(const step& s) : bfs_node(s), depth(0) {
}
astar_node(const step& s, bfs_node* const ParentPtr) : bfs_node(s, ParentPtr), depth(0) {
}
astar_node(const step& s, bfs_node* const ParentPtr, const size_t Depth) : bfs_node(s, ParentPtr), depth(Depth) {
}
};
board_heuristic_value_type heuristic(const board_type& current) {
size_t ret = 0;
for (board_coord_type i = 0; i < BOARD_ROW_COUNT; ++i)
for (board_coord_type j = 0; j < BOARD_COLUMN_COUNT; ++j)
if (current[i][j] != goal[i][j]) ++ret;
return ret;
}
struct astar_node_cmp {
bool operator()(const astar_node* const a, const astar_node* const b) const {
return a->depth + heuristic(a->board) > b->depth + heuristic(b->board); }
};
bool astar_solve() {
priority_queue<astar_node*, vector<astar_node*>, astar_node_cmp> q;
astar_node* t = new astar_node(start, xs, ys, 0);
q.emplace(t);
container_for_qsearch.emplace(t->board);
while (q.empty() == false) {
t = q.top();
q.pop();
if (t->board == goal/*equal_by_board_hash(solution.back().board) == true*/) {
bfs_node* p = t;
while (p != nullptr) {
solution.emplace_back(*p);
p = p->parent;
}
reverse(solution.begin(), solution.end());
return true;
}
if (can_up(t->x)) {
astar_node u = *t;
up_b(u);
if (container_for_qsearch.emplace(u.board).second == true) {
t->child[0] = new astar_node(u, t, u.depth + 1);
q.emplace(static_cast<astar_node*>(t->child[0]));
}
}
if (can_down(t->x)) {
astar_node u = *t;
down_b(u);
if (container_for_qsearch.emplace(u.board).second == true) {
t->child[1] = new astar_node(u, t, u.depth + 1);
q.emplace(static_cast<astar_node*>(t->child[1]));
}
}
if (can_left(t->y)) {
astar_node u = *t;
left_b(u);
if (container_for_qsearch.emplace(u.board).second == true) {
t->child[2] = new astar_node(u, t, u.depth + 1);
q.emplace(static_cast<astar_node*>(t->child[2]));
}
}
if (can_right(t->y)) {
astar_node u = *t;
right_b(u);
if (container_for_qsearch.emplace(u.board).second == true) {
t->child[3] = new astar_node(u, t, u.depth + 1);
q.emplace(static_cast<astar_node*>(t->child[3]));
}
}
}
return false;
}
array<board_data_type, BOARD_ROW_COUNT* BOARD_COLUMN_COUNT> row_as_start_board, row_as_goal_board;
template<class _Ty> size_t merge(_Ty* const _begin, _Ty* const _mid, _Ty* const _end) {
// This function will modify the involved array.
const auto l = _end - _begin + 1; size_t tau = 0;
_Ty* const t = new _Ty[l], * i = _begin, * j = _mid + 1, * k = t;
while (i <= _mid && j <= _end) {
if (*i <= *j) {
*k = *i; ++i; ++k; }
else {
*k = *j; ++j; ++k; tau += _mid - i + 1; }
}
while (i <= _mid) {
*k = *i; ++i; ++k; }
while (j <= _end) {
*k = *j; ++j; ++k; }
std::copy(t, t + l, _begin);
delete[] t;
return tau;
}
template<class _Ty> size_t inversion_count(_Ty* const _begin, _Ty* const _end) {
// This function will modify the involved array.
size_t tau = 0;
if (_begin < _end) {
_Ty* const _mid = _begin + (_end - _begin) / 2;
tau += inversion_count(_begin, _mid);
tau += inversion_count(_mid + 1, _end);
tau += merge(_begin, _mid, _end);
}
return tau;
}
time_point<high_resolution_clock, nanoseconds> t[2];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
for (board_coord_type i = 0; i < BOARD_ROW_COUNT; ++i)
for (board_coord_type j = 0; j < BOARD_COLUMN_COUNT; ++j) {
cin >> start[i][j];
row_as_start_board[i * BOARD_COLUMN_COUNT + j] = start[i][j];
if (start[i][j] == blank_token) {
xs = i; ys = j; }
}
for (board_coord_type i = 0; i < BOARD_ROW_COUNT; ++i)
for (board_coord_type j = 0; j < BOARD_COLUMN_COUNT; ++j) {
cin >> goal[i][j];
row_as_goal_board[i * BOARD_COLUMN_COUNT + j] = goal[i][j];
}
if (inversion_count(&row_as_start_board[0], &row_as_start_board[BOARD_ROW_COUNT * BOARD_COLUMN_COUNT - 1]) % 2
!= inversion_count(&row_as_goal_board[0], &row_as_goal_board[BOARD_ROW_COUNT * BOARD_COLUMN_COUNT - 1]) % 2) {
cout << "No solution." << endl;
return 0;
}
t[0] = high_resolution_clock::now();
solution.emplace_back(start, xs, ys);
container_for_qsearch.emplace(start);
result = dfs_solve();
t[1] = high_resolution_clock::now();
cout << "<DFS> Time elapsed: " << duration_cast<milliseconds>(t[1] - t[0]).count() << " ms." << endl;
cout << "<DFS> Has a solution: "<< boolalpha << result << endl;
cout << "<DFS> The number of steps: " << solution.size() - 1 << endl;
//print_solution(solution);
cout << endl;
solution.clear();
container_for_qsearch.clear();
t[0] = high_resolution_clock::now();
result = bfs_solve();
t[1] = high_resolution_clock::now();
cout << "<BFS> Time elapsed: " << duration_cast<milliseconds>(t[1] - t[0]).count() << " ms." << endl;
cout << "<BFS> Has a solution: " << boolalpha << result << endl;
cout << "<BFS> The number of steps: " << solution.size() - 1 << endl;
print_solution(solution);
cout << endl;
solution.clear();
container_for_qsearch.clear();
t[0] = high_resolution_clock::now();
result = astar_solve();
t[1] = high_resolution_clock::now();
cout << "<A*> Time elapsed: " << duration_cast<milliseconds>(t[1] - t[0]).count() << " ms." << endl;
cout << "<A*> Has a solution: " << boolalpha << result << endl;
cout << "<A*> The number of steps: " << solution.size() - 1 << endl;
print_solution(solution);
cout << endl;
return 0;
}
六、样例与实验结果
样例
样例 0:
0 1 2 3 4 5 6 8 7
0 1 2 3 4 5 6 7 8
样例 1:
7 2 4 5 0 6 8 3 1
0 1 2 3 4 5 6 7 8
样例 2(洛谷 P1379):
2 8 3 1 0 4 7 6 5
1 2 3 8 0 4 7 6 5
样例 0 无解,1、2 有解。
结果
由于 DFS 给出的解太长,因此这里不进行展示。
BFS 给出了步数最少的解(最优解可以不唯一)。A* 在执行 2 个测试用例时,也给出了最优解。
时空复杂度
测试方法:
在 Release 模式(-O2)下,依次注释掉 main 函数里的其它算法的代码,且不打印解,只运行单个算法,在 main 函数的返回语句(return 0;)处设置断点。在断点中断后,于任务管理器(taskmgr)中查看由本代码编译而来的进程的内存占用大小。
测试使用样例 1。
结论
对于样例,当找到第一个解就返回时,DFS 不能保证给出最优解;只有遍历完整棵解答树以后,才能确定获得的所有解中,哪个是最优解。
由于 BFS 本身的特性,BFS 最先给出的深度相同的若干个解都是最优解。本实验仅展示第一个解。
DFS 的运行耗时可以远远小于 BFS(样例 1),也可以反过来(样例 2),但这都只是特例。理论上,在最坏情况下,两者都有可能需要完全遍历解答树以后,才能给出一个可行解或一个最优解。
A* 虽然也无法保证给出最优解,但其在时空复杂度方面的表现比无信息搜索的BFS和DFS要更优秀。这也符合预期。
综上:实验结果均符合预期,实验成功。
七、算法的进一步改进方案(简述)
双向搜索
从起点和终点同时开始进行 DFS 或 BFS。如果相遇,那么获得了可行解。
作为无信息的暴力搜索,DFS 的时间复杂度达到搜索树的最大深度的指数级,BFS 的时空复杂度均达到最优解所在深度的指数级。双向搜索能让指数减半,极大降低时空复杂度。
迭代加深搜索
主要是 IDDFS 和 IDA*。
迭代加深搜索的本质还是深度优先搜索,只不过在搜索的同时带上了深度 d 作为参数,当达到设定的深度时就返回,一般用于找最优解。如果一次搜索没有找到合法的解,就让设定的深度 + 1,重新从根开始。
使用 IDDFS 可以极大降低空间复杂度。虽然它会重复搜索浅层的状态,但在已知浅层存在解时,一般地,其时间复杂度和空间复杂度分别将显著优于 DFS 和 BFS,且空间复杂度与 DFS 相当、时间复杂度不比 BFS 差太多。如使用 IDA*,时空复杂度的表现往往更优。
更优秀的启发式函数
在 A* 算法中,启发式函数决定 A* 算法的行为。
优秀的启发式函数,可以使得付出相对较少的代价就能找到尽量接近最优的解。一个不对不同的输入产生太多相同的值的启发式函数,有利于让 A* 算法扩展的总节点数更少。
但在实验过程中,将使用的启发式函数由 Hamming 距离改为 Manhattan 距离后,A* 算法的耗时反而大大增加,从 30 多 ms 陡增至 200 到 300 ms,原因目前还不明确。因此暂时继续使用当前棋盘与目标棋盘的 Hamming 距离作为启发式函数。
哈希判重
最初,使用的是有序集合 std::set 来判定当前正在搜索的路径中是否有重复的节点。将当前路径中的节点改为存入无序集合 std::unordered_set 并自定义哈希仿函数后,三种算法的性能提升均达到大约 50%。
但若在搜索过程中进一步使用哈希来判定棋盘与目标棋盘相符(即已经搜索到目标状态)而不是使用 std::array 已经重载的 == 运算符来逐个比较棋盘的每个位置,耗时确又数倍增长,原因同样不明。因为理论上棋盘的每个位置的值可以看成字符串的字符,而棋盘的状态就可以看成字符串,用哈希比较字符串是否相等应当比逐个比较快得多。初步猜测,可能是因为作为 C++ STL 的 std::unordered_set 额外使用了大量的优化技巧。
八、附录:(n^2 - 1) 数码问题控制台简易演示程序
在控制台中显示当前的棋盘状况和累计进行的操作(上下左右),按 Backspace 退格。
支持更大规模的此类问题,如 15 数码问题和 24 数码问题。
支持为棋盘填入不同类型、不同大小的数(或字符);可以使用不同的数表示空格。
本演示程序仅支持 Windows 平台。
#include <algorithm>
#include <conio.h>
#include <iostream>
#include <string>
#include <tuple>
using namespace std;
const size_t board_row_count = 3;
const size_t board_column_count = 3;
const size_t board_size = board_row_count * board_column_count + 1;
using board_data_type = char;
//board_data_type board[] = {"wxyz00ijkt12 34pqruv*****"};
board_data_type board[] = {
" xy000abc"};
//tuple<size_t, size_t> coord = { 2,2 };
tuple<size_t, size_t> coord = {
1,1 };
string opseq;
int c;
inline bool up(char* const board, const size_t column, tuple<size_t, size_t>& pos) {
if (get<0>(pos) > 0) {
swap(board[(get<0>(pos) - 1) * column + get<1>(pos)], board[get<0>(pos) * column + get<1>(pos)]);
--get<0>(pos);
return true;
}
else return false;
}
inline bool down(char* const board, const size_t row, const size_t column, tuple<size_t, size_t>& pos) {
if (get<0>(pos) < row - 1) {
swap(board[(get<0>(pos) + 1) * column + get<1>(pos)], board[get<0>(pos) * column + get<1>(pos)]);
++get<0>(pos);
return true;
}
else return false;
}
inline bool left(char* const board, const size_t column, tuple<size_t, size_t>& pos) {
if (get<1>(pos) > 0) {
swap(board[get<0>(pos) * column + get<1>(pos) - 1], board[get<0>(pos) * column + get<1>(pos)]);
--get<1>(pos);
return true;
}
else return false;
}
inline bool right(char* const board, const size_t column, tuple<size_t, size_t>& pos) {
if (get<1>(pos) < column - 1) {
swap(board[get<0>(pos) * column + get<1>(pos) + 1], board[get<0>(pos) * column + get<1>(pos)]);
++get<1>(pos);
return true;
}
else return false;
}
inline void output_board(const char* const board, const size_t row, const size_t column) {
for (size_t i = 0; i < row; ++i) {
for (size_t j = 0; j < column; ++j) {
cout << board[i * column + j] << ' ';
}
cout << endl;
}
}
inline void clear_and_output_board(const char* const board, const size_t row, const size_t column) {
system("cls");
output_board(board, row, column);
}
inline void add_and_output_opseq(const char* const op) {
opseq += op;
cout << opseq << endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
output_board(board, board_row_count, board_column_count);
for (;;) {
c = _getch();
if (c == 224) {
switch (_getch()) {
case 72:
if (up(board, board_column_count, coord) == true) {
clear_and_output_board(board, board_row_count, board_column_count);
add_and_output_opseq("U ");
}
continue;
case 80:
if (down(board, board_row_count, board_column_count, coord) == true) {
clear_and_output_board(board, board_row_count, board_column_count);
add_and_output_opseq("D ");
}
continue;
case 75:
if (left(board, board_column_count, coord) == true) {
clear_and_output_board(board, board_row_count, board_column_count);
add_and_output_opseq("L ");
}
continue;
case 77:
if (right(board, board_column_count, coord) == true) {
clear_and_output_board(board, board_row_count, board_column_count);
add_and_output_opseq("R ");
}
continue;
default:
continue;
}
}
else if (c == '\b') {
if (opseq.empty() == false) {
opseq.pop_back();
c = opseq.back();
switch (c) {
case 'U':
down(board, board_row_count, board_column_count, coord);
break;
case 'D':
up(board, board_column_count, coord);
break;
case 'L':
right(board, board_column_count, coord);
break;
case 'R':
left(board, board_column_count, coord);
break;
}
clear_and_output_board(board, board_row_count, board_column_count);
opseq.pop_back();
cout << opseq << endl;
}
}
//cout << c << endl;
}
return 0;
}