背景
xiyoulinux兴趣小组 的免试题蛮有意思的,第三关要求不仅要有脑洞,还是得有代码编写的能力的.
第三关入口
打开就是这个样子了…
迷宫这么大,时间肯定不够.
可以,写代码吧…用程序跑
回溯法
一说到走迷宫第一个想到就是回溯法了.(先解决问题,时间还是不够那再想其他办法)
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
大致想法就是从起点出发,上,下,左,右搜索,哪个能走就走哪个.这条路不通就退回之前的点继续之前的步骤.用一个栈保存之前走过的点就可以了
地图的一部分大概长这样
// 地图放在map.txt中
#include <iostream>
#include <vector>
#include <stack>
#include <cstdio>
#include <utility>
#include <unistd.h>
using namespace std;
int main()
{
vector<vector<char>> map(101, vector<char>(102));
vector<vector<bool>> route(101, vector<bool>(102, false));
FILE *fp = fopen("map.txt", "r");
int ch;
for(auto &i : map) {
for(auto &j : i) {
if((ch = fgetc(fp)) == EOF) {
break;
}
j = ch;
}
}
int x = 1;
int y = 0;
route[x][y] = true;
stack<pair<int, int>> s;
s.push(make_pair(x, y));
while(!s.empty() && !(x == 100 && y == 99)) {
// turn down
if(x + 1 < 101 && map[x + 1][y] == '.' && route[x + 1][y] == false) {
x++;
route[x][y] = true;
s.push(make_pair(x, y));
} else if(y + 1 < 102 && map[x][y + 1] == '.' && route[x][y + 1] == false) {
y++;
route[x][y] = true;
s.push(make_pair(x, y));
} else if(x - 1 > 0 && map[x - 1][y] == '.' && route[x - 1][y] == false) {
x--;
route[x][y] = true;
s.push(make_pair(x, y));
} else if(y - 1 > 0 && map[x][y - 1] == '.' && route[x][y - 1] == false) {
y--;
route[x][y] = true;
s.push(make_pair(x, y));
} else {
// 回溯
s.pop();
if(s.empty()) {
break;
}
auto prev_pos = s.top();
x = prev_pos.first;
y = prev_pos.second;
}
}
// 看看走出来是啥样
for(auto i: route) {
for(auto j: i) {
if(j) {
cout << '.';
} else {
cout << ' ';
}
}
cout << endl;
}
return 0;
}
路径打印出来
按键模拟
现在有走迷宫算法了,能够走迷宫了,但是我们不可能用手来,因为我们手速太慢了.这个时候希望能有模拟键盘摁下键盘的程序.
搜了搜,发现可以用将键盘摁下的事件写到/dev/input/event`X’ 中.这是个好办法,无论在图形界面下,还是字符模式下,都能模拟键盘.不过Linux每次重启后,内核绑定的event`X’文件都不一样,虽然可以用
cat /proc/bus/input/devices
来查看当前绑定的event`X’,不过还是要频繁改代码..
继续搜,发现可以用 XTextFakeKeyEvent 来模拟键盘输入.
这个玩意是 X11 里面的…
先 X11 下载开发包
sudo apt-get install xorg-dev -y
这样我们就可以使用X11 里面的Xtest了
#include <stdio.h>
#include <X11/extensions/XTest.h>
#include <X11/Xlib.h>
static Display *dsp;
int vk_init(void)
{
dsp = XOpenDisplay(NULL);
if(!dsp) {
printf("open display failed\n");
return -1;
}
return 0;
}
int vk_presskey(int s)
{
if(dsp == NULL) {
return -1;
}
KeyCode key = XKeysymToKeycode(dsp, s);
if(key == NoSymbol)
return -1;
XTestFakeKeyEvent(dsp, key, 1, CurrentTime);
XFlush(dsp);
return 0;
}
int vk_releasekey(int s)
{
if(dsp == NULL) {
return -1;
}
KeyCode key = XKeysymToKeycode(dsp, s);
if(key == NoSymbol) {
return -1;
}
XTestFakeKeyEvent(dsp, key, 0, CurrentTime);
XFlush(dsp);
return 0;
}
int vk_kill(void)
{
if(dsp != NULL) {
XCloseDisplay(dsp);
return 0;
}
return -1;
}
把它编译成库文件(当然直接编译最后一起链接也是没问题的.(其实就是命令太长… + 懒…))
gcc test.c -lX11 -lXtst -fPIC -shared -o libvirkey.so
# 安装!
sudo cp ./libvirkey.so /usr/lib/
最后我们改下之前的代码,加上摁键模拟就好了
#include <iostream>
#include <vector>
#include <stack>
#include <cstdio>
#include <utility>
#include <unistd.h>
using namespace std;
extern "C" {
int vk_init();
int vk_kill();
int vk_presskey(int);
int vk_releasekey(int);
}
#define press_W() \
vk_presskey('w'); \
vk_releasekey('w');
#define press_S() \
vk_presskey('s'); \
vk_releasekey('s');
#define press_A() \
vk_presskey('a'); \
vk_releasekey('a');
#define press_D() \
vk_presskey('d'); \
vk_releasekey('d');
const size_t S = 30000;
int main()
{
vk_init();
vector<vector<char>> map(101, vector<char>(102));
vector<vector<bool>> route(101, vector<bool>(102, false));
stack<pair<int, int>> s;
FILE *fp = fopen("map.txt", "r");
int ch;
for(auto &i : map) {
for(auto &j : i) {
if((ch = fgetc(fp)) == EOF) {
break;
}
j = ch;
}
}
int x = 1;
int y = 0;
// 为了自己有时间切换到浏览器界面
sleep(10);
route[x][y] = true;
s.push(make_pair(x, y));
while(!s.empty() && !(x == 100 && y == 99)) {
// turn down
if(x + 1 < 101 && map[x + 1][y] == '.' && route[x + 1][y] == false) {
x++;
route[x][y] = true;
s.push(make_pair(x, y));
// 摁键必须有空隙.不能太快
usleep(S);
press_S();
} else if(y + 1 < 102 && map[x][y + 1] == '.' && route[x][y + 1] == false) {
y++;
route[x][y] = true;
s.push(make_pair(x, y));
usleep(S);
press_D();
} else if(x - 1 > 0 && map[x - 1][y] == '.' && route[x - 1][y] == false) {
x--;
route[x][y] = true;
s.push(make_pair(x, y));
usleep(S);
press_W();
} else if(y - 1 > 0 && map[x][y - 1] == '.' && route[x][y - 1] == false) {
y--;
route[x][y] = true;
s.push(make_pair(x, y));
usleep(S);
press_A();
} else {
auto curr_pos = s.top();
s.pop();
if(s.empty()) {
break;
}
auto prev_pos = s.top();
x = prev_pos.first;
y = prev_pos.second;
if(curr_pos.first - prev_pos.first > 0) {
// up
usleep(S);
press_W();
} else if(curr_pos.first - prev_pos.first < 0) {
// down
usleep(S);
press_S();
} else if(curr_pos.second - prev_pos.second > 0) {
// left
usleep(S);
press_A();
} else if(curr_pos.second - prev_pos.second < 0) {
// right
usleep(S);
press_D();
}
}
}
vk_kill();
return 0;
}
编译它
gcc test.cc -lvirkey
快跟我一起贪玩迷宫,打怪升级,装备不花一分钱