最近寫了一個對抗搜索 + -剪枝的下五子棋的“人工智障”,貼個代碼,頭文件在下面:
Gobang.cpp at 2019.6.8
效果大概就是這樣,此圖是AI對戰AI時的效果圖。
#include "winshow.cpp"
#include "cursorope.cpp"
//#include <algorithm>
//using namespace std;
/// 五子棋遊戲 Day74 論對抗搜索
#define INF (0x3f3f3f3f)
#define BlackPiece (1)
#define WhitePiece (2)
#define SpaceLand (0)
#define Outside (-1)
#include <ctime>
namespace AIsolve { /// 用人工智障分析局勢
int scorebd[11][11] = { /// 描述暴搜的搜索順序
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, /// line 0
{-1, 65, 64, 63, 62, 61, 60, 59, 58, 57, -1}, /// line 1
{-1, 66, 37, 36, 35, 34, 33, 32, 31, 56, -1}, /// line 2
{-1, 67, 38, 17, 16, 15, 14, 13, 30, 55, -1}, /// line 3
{-1, 68, 39, 18, 5, 4, 3, 12, 29, 54, -1}, /// line 4
{-1, 69, 40, 19, 6, 1, 2, 11, 28, 53, -1}, /// line 5
{-1, 70, 41, 20, 7, 8, 9, 10, 27, 52, -1}, /// line 6
{-1, 71, 42, 21, 22, 23, 24, 25, 26, 51, -1}, /// line 7
{-1, 72, 43, 44, 45, 46, 47, 48, 49, 50, -1}, /// line 8
{-1, 73, 74, 75, 76, 77, 78, 79, 80, 81, -1}, /// line 9
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, /// line 10
};
int PoX[82], PoY[82];
void InitPoXY() { /// 初始化搜索順序
for(int i = 1; i <= 9; i ++) {
for(int j = 1; j <= 9; j ++) {
PoX[scorebd[i][j]] = i;
PoY[scorebd[i][j]] = j;
}
}
}
int tmpline[20], tcnt; /// 用於直線掃描分析的臨時儲存
void clear() { /// 清空臨時儲存
tcnt = 0;
}
void push_back(int item) { /// 向臨時儲存中添加元素
tmpline[++ tcnt] = item;
} /// tmpline 的可用下標從 1開始
void GetLine(int chsbd[11][11], int x, int y, int dirx, int diry) {
/// 從 -1 開始到 -1 結束, 得到一條直線上的所有棋子
clear(); /// 清空臨時儲存
int boardcnt = 0; /// 統計 -1出現的次數
while(1) {
if(chsbd[x][y] == -1) boardcnt ++; /// 統計-1出現的次數
push_back(chsbd[x][y]);
if(boardcnt == 2) break; /// 獲取完成
x += dirx; /// 挪到下一個位置
y += diry;
}
}
bool CheckSame(int A, int B, int player) { /// 判斷兩個元素是否同屬一段
if(B==player || A==player) {
return A == B;
}else {
if(A==SpaceLand || B==SpaceLand) {
return A == B;
}else {
return true;
}
}
}
/// 連續性分析
int mode[20], length[20], mcnt; /// 對儲存的直線信息進行連續性分析
void ContinuousAnalysis(int player) { /// 連續性分析
mcnt = 1; mode[1] = -1; length[1] = 1;
for(int i = 2; i <= tcnt; i ++) {
if(CheckSame(tmpline[i-1], tmpline[i], player)) { /// 延伸上一段
length[mcnt]++;
}else { /// 開闢新一段
mode[++ mcnt] = tmpline[i];
length[mcnt] = 1;
}
}
}
/// 統計局面(注意對於兩個玩家要重新分析)
int CntAry[6][2]; /// 用於統計臨時數據 0活 1衝
void Count(int player) {
int baseNow = 1, LenSum = 0;
for(int i = 2; i <= mcnt; i ++) {
if(mode[i]==3-player || mode[i]==-1) { /// 尋找下一個界標
if(LenSum >= 5) {
for(int j = baseNow+1; j<=i-1; j ++) { /// 枚舉兩個界標之間的每一段內容
if(mode[j]==player && length[j]>1) {
int border = j==baseNow+1 || j==i-1; /// 是否爲衝
CntAry[length[j]][border] ++;
}
}
}
/// 如果總長度小於5 直接忽略這一段
baseNow = i; LenSum = 0; /// 重新統計長度
}else {
LenSum += length[i]; /// 累加除界標外部分的長度
}
}
}
int GetScore(int chsbd[11][11], int player) { /// 得到某個玩家的估價
memset(CntAry, 0x00, sizeof(CntAry)); /// 清除原數據
/// 統計每一行
for(int i = 1; i <= 9; i ++) {
GetLine(chsbd, i, 0, 0, 1);
ContinuousAnalysis(player);
Count(player);
}
/// 統計每一列
for(int i = 1; i <= 9; i ++) {
GetLine(chsbd, 0, i, 1, 0);
ContinuousAnalysis(player);
Count(player);
}
/// 左上右下斜線
GetLine(chsbd, 0, 0, 1, 1); /// 對角中軸線
ContinuousAnalysis(player);
Count(player);
for(int i = 1; i <= 4; i ++) {
GetLine(chsbd, 0, i, 1, 1); /// 對角中軸線上方
ContinuousAnalysis(player);
Count(player);
GetLine(chsbd, i, 0, 1, 1); /// 對角中軸線下方
ContinuousAnalysis(player);
Count(player);
}
/// 右上左下斜線
GetLine(chsbd, 0, 10, 1, -1); /// 對角中軸線
ContinuousAnalysis(player);
Count(player);
for(int i = 1; i <= 4; i ++) {
GetLine(chsbd, 0, 10-i, 1, -1); /// 上方
ContinuousAnalysis(player);
Count(player);
GetLine(chsbd, i, 10, 1, -1); /// 下方
ContinuousAnalysis(player);
Count(player);
}
/// 統計完成
if(CntAry[5][0] || CntAry[5][1]) return 10000;
else if(CntAry[4][0] || CntAry[4][1]>=2 || (CntAry[4][1] && CntAry[3][0])) return 9900;
else if(CntAry[3][0]>=2 || CntAry[4][1]>=2) return 9800;
else if(CntAry[3][0] && CntAry[4][1]) return 9700;
else {
return (CntAry[3][0]+CntAry[4][1])*2000+CntAry[3][1]*300 + CntAry[2][0]*200 + CntAry[2][1]*50;
}
}
int FinalScore(int chsbd[11][11]) { /// 最終得分
int s1 = GetScore(chsbd, 1), s2 = GetScore(chsbd, 2);
if(s2 >= 9000) return -s2;
else if(s1 >= 9000) return s1;
else return s1 - s2;
}
int StartTime;
int Search(int chsbd[11][11], int player, int& bestpos, int depth, int alpha = -INF, int beta = INF) {
int score = FinalScore(chsbd);
if(depth >= 4 || clock() - StartTime > 10000) { /// 停止搜索
if(player == 1) return alpha>score ? alpha : score;
else return beta<score ? beta : score;
}
bool first = 1;
for(int i = 1; i <= 81; i ++) {
int Xn = PoX[i], Yn = PoY[i];
if(beta <= alpha) break;
if(chsbd[Xn][Yn] == 0) { /// 可以放置
if(first) {
bestpos = i;
first = 0;
}
chsbd[Xn][Yn] = player;
int tmp = 0;
score = Search(chsbd, 3-player, tmp, depth+1, alpha, beta);
if(player == 1) {
//alpha = max(alpha, score);
if(score > alpha) {
alpha = score;
bestpos = i;
}
if(score >= 9000) {
chsbd[Xn][Yn] = 0;
break;
}
}else {
//beta = min(beta, score);
if(score < beta) {
beta = score;
bestpos = i;
}
if(score <= -9000) {
chsbd[Xn][Yn] = 0;
break;
}
}
chsbd[Xn][Yn] = 0;
}
}
if(player == 1) return alpha;
else return beta;
}
void FindWay(int chsbd[11][11], int player, int& X, int& Y) {
/// 分析棋盤局勢並且落子於 X行 Y列
StartTime = clock();
int tmp = 0;
Search(chsbd, player, tmp, 0);
X = PoX[tmp];
Y = PoY[tmp];
}
}
namespace Gobang { /// 五子棋遊戲
int chessboard[11][11]; /// 描述棋盤狀態
wshow::dot GetPiece(int item) { /// 找到棋子對應的形狀
switch(item) {
case BlackPiece: return wshow::dot("●", makecol(clr::YELLOW, clr::BLACK));
case WhitePiece: return wshow::dot("●", makecol(clr::YELLOW, clr::WHITE|clr::LIGHT));
case SpaceLand: return wshow::dot("┼", makecol(clr::YELLOW, clr::WHITE));
case 3: return wshow::dot("●", makecol(clr::YELLOW, clr::RED|clr::LIGHT));
default: return wshow::dot("□", makecol(clr::BLACK, clr::YELLOW));
}
}
void Disp(int y, int x) { /// 顯示棋盤到屏幕
for(int i = 0; i <= 10; i ++) {
for(int j = 0; j <= 10; j ++) {
wshow::output(GetPiece(chessboard[i][j]), y+i, x+j);
}
}
}
void Init() { /// 初始化棋盤
memset(chessboard, 0x00, sizeof(chessboard));
for(int i = 0; i <= 10; i ++) {
chessboard[i][0] = chessboard[i][10] = -1;
chessboard[0][i] = chessboard[10][i] = -1;
}
}
void RndTest() { /// 生成隨機局面(用於輸出測試)
for(int i = 1; i <= 9; i ++) {
for(int j = 1; j <= 9; j ++) {
chessboard[i][j] = RND(0, 2);
}
}
}
int tmpx, tmpy, tmpdx, tmpdy;
bool CheckWinInDir(int x, int y, int dirx, int diry) { /// 檢測某一點開始某一方向上是否有五連珠
tmpx = x; tmpy =y; tmpdx = dirx; tmpdy = diry;
int tmp = chessboard[x][y];
if(tmp <= 0) return false; /// 空地不可能成爲五連珠的起始
for(int i = 1; i <= 5; i ++) {
if(chessboard[x][y] != tmp) return false; /// 發現不同, 不可能出現五連珠
x += dirx;
y += diry;
}
return true; /// 未發現不同 說明存在五連珠
}
void MakeSuc() { /// 將五連珠染紅
for(int i = 1; i <= 5; i ++) {
chessboard[tmpx][tmpy] = 3;
tmpx += tmpdx;
tmpy += tmpdy;
}
}
bool CheckWinOnPos(int x, int y) { /// 檢測某一點開始是否有五連珠
/// 我們只需要檢查 左下, 右下, 右, 下 這四個方向
return CheckWinInDir(x, y, 0, 1) || /// 右
CheckWinInDir(x, y, 1, 0) || /// 下
CheckWinInDir(x, y, 1, -1) || /// 左下
CheckWinInDir(x, y, 1, 1); /// 右下
}
int CheckWin() { /// 檢測是否有人勝利
for(int i = 1; i <= 9; i ++) {
for(int j = 1; j <= 9; j ++) {
if(CheckWinOnPos(i, j)) {
MakeSuc();
Disp(0, 0);
return chessboard[i][j]; /// 返回勝利玩家
}
}
}
return 0; /// 返回遊戲未結束
}
POINT GetClickOnChessboard(int y, int x) { /// 等待棋盤上的單擊左鍵
POINT p;
while(1) {
p = cursorope::WaitCursorLeftClick();
p.x /= 2; /// 注意橫向放縮的問題
#define Rin(A, B, C) (((A)<=(B))&&((B)<=(C))) /// 判斷 A<=B<=C
if(Rin(x+1, p.x, x+9) && Rin(y+1, p.y, y+9)) {
p.x -= x; p.y -= y; /// 獲得相對位置
if(chessboard[p.y][p.x] == 0) break;
}
}
return p;
}
int MsgCnt = 0;
void OutputMsg(const char* str, int pos = 47, int col = clr::DEFAULT) { /// 用於輸出遊戲信息
wnd::gotoxy(MsgCnt%20+1, pos);
for(int i = pos; i <= 78; i ++) putchar(' ');
wnd::gotoxy(MsgCnt%20, pos);
for(int i = pos; i <= 78; i ++) putchar(' ');
wnd::gotoxy(MsgCnt%20, pos);
wnd::color(col);
printf("%3d: %s", MsgCnt+1, str);
wnd::color(clr::DEFAULT);
MsgCnt ++;
}
const int WARN_COLOR = makecol(clr::BLACK, clr::RED|clr::LIGHT);
const int SUCC_COLOR = makecol(clr::BLACK, clr::GREEN|clr::LIGHT);
const int TIPS_COLOR = makecol(clr::BLACK, clr::YELLOW|clr::LIGHT); /// 定義幾種顏色
const int BLUE_COLOR = makecol(clr::BLACK, clr::PURPLE|clr::LIGHT);
void Game(int y, int x, bool ifperson1 = false, bool ifperson2 = true) {
Init();
chessboard[5][5] = BlackPiece;
Disp(y, x); /// 顯示空棋盤(中央一黑子)
POINT p;
int i;
for(i = 1; i <= 40; i ++) {
/// 白棋落子
OutputMsg("等待白棋落子 ...", 47, TIPS_COLOR);
if(ifperson2) { /// 人執白子
p = GetClickOnChessboard(y, x);
chessboard[p.y][p.x] = WhitePiece;
}else { /// 電腦執白子
int X = 0, Y = 0;
AIsolve::FindWay(chessboard, WhitePiece, X, Y);
chessboard[X][Y] = WhitePiece;
}
Disp(y, x);
if(CheckWin()) {
OutputMsg("白棋勝利!", 47, SUCC_COLOR);
break;
}
/// 黑棋落子
OutputMsg("等待黑棋落子 ...", 47, BLUE_COLOR);
if(ifperson1) {
p = GetClickOnChessboard(y, x); /// 人執黑棋
chessboard[p.y][p.x] = BlackPiece;
}else { /// 電腦執黑棋
int X = 0, Y = 0;
AIsolve::FindWay(chessboard, BlackPiece, X, Y);
chessboard[X][Y] = BlackPiece;
}
Disp(y, x);
if(CheckWin()) {
OutputMsg("黑棋勝利!", 47, SUCC_COLOR);
break;
}
}
if(i == 41) {
OutputMsg("平局!", 47, SUCC_COLOR);
}
}
}
#include <ctime>
int testbd[11][11] = { /// 測試專用棋盤
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, /// line 0
{-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1}, /// line 1
{-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1}, /// line 2
{-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1}, /// line 3
{-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1}, /// line 4
{-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1}, /// line 5
{-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1}, /// line 6
{-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1}, /// line 7
{-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1}, /// line 8
{-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1}, /// line 9
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, /// line 10
};
int main() {
srand(time(NULL));
AIsolve::InitPoXY();
//Gobang::Init(); Gobang::RndTest(); Gobang::Disp(0, 0); /// 輸出測試
Gobang::Game(0, 0, false, false);
while(1);
//int tmp = AIsolve::GetScore(testbd, BlackPiece);
//printf("%d\n", tmp);
return 0;
}
Chessboard.cpp at 2019.6.15
15*15的人人對戰平臺(後期可以把人工智障嵌入,由於我現在的人工智障太慢了,以後再嵌入)
效果大概這樣:
/// 用於五子棋對戰平臺
/// 2019.6.14
#ifndef __CHESSBOARD_CPP__
#define __CHESSBOARD_CPP__
#include "cursorope.cpp" /// 鼠標控制頭文件
#include "winshow.cpp" /// 控制檯顯示頭文件
#include <cstring>
namespace ChessBoard { /// 對戰平臺
const int _MaxLength = 15; /// 棋盤的大小爲 _MaxLength * _MaxLength
const int _BlackPiece = 1, _WhitePiece = 2, _RedPiece = 3, _Empty = 0, _Outside = -1; /// 棋子類型
int statue[_MaxLength + 2][_MaxLength + 2]; /// 描述棋盤上的信息
void InitStatue() { /// 初始化棋盤
memset(statue, 0x00, sizeof(statue)); /// 清空棋盤
for(int i = 0; i <= _MaxLength + 1; i ++) { /// 設置邊界
statue[i][0] = statue[0][i] = _Outside;
statue[i][_MaxLength + 1] = statue[_MaxLength + 1][i] = _Outside;
}
/// 在中間位置放一粒黑子
int middle = (_MaxLength + 1)/2;
statue[middle][middle] = _BlackPiece;
}
wshow::dot GetPiece(int item) { /// 找到棋子對應的字符
switch(item) {
case _BlackPiece: return wshow::dot("●", makecol(clr::YELLOW, clr::BLACK));
case _WhitePiece: return wshow::dot("●", makecol(clr::YELLOW, clr::WHITE|clr::LIGHT));
case _Empty: return wshow::dot("┼", makecol(clr::YELLOW, clr::WHITE));
case _RedPiece: return wshow::dot("●", makecol(clr::YELLOW, clr::RED|clr::LIGHT));
default: return wshow::dot("□", makecol(clr::BLACK, clr::YELLOW));
}
}
void Display() { /// 顯示棋盤至控制檯
for(int i = 0; i <= _MaxLength + 1; i ++) {
for(int j = 0; j <= _MaxLength + 1; j ++) {
wshow::output(GetPiece(statue[i][j]), i, j);
}
}
}
POINT GetClick(int y = 0, int x = 0) { /// 等待棋盤上的單擊左鍵
POINT p;
while(1) {
p = cursorope::WaitCursorLeftClick();
p.x /= 2; /// 注意橫向放縮的問題
#define Rin(A, B, C) (((A)<=(B))&&((B)<=(C))) /// 判斷 A<=B<=C
if(Rin(x+1, p.x, x+_MaxLength) && Rin(y+1, p.y, y+_MaxLength)) {
p.x -= x; p.y -= y; /// 獲得相對位置
if(statue[p.y][p.x] == 0) break;
}
}
return p;
}
int tmpx, tmpy, tmpdx, tmpdy;
bool CheckWinInDir(int x, int y, int dirx, int diry) { /// 檢測某一點開始某一方向上是否有五連珠
tmpx = x; tmpy =y; tmpdx = dirx; tmpdy = diry;
int tmp = statue[x][y];
if(tmp <= 0) return false; /// 空地不可能成爲五連珠的起始
for(int i = 1; i <= 5; i ++) {
if(statue[x][y] != tmp) return false; /// 發現不同, 不可能出現五連珠
x += dirx;
y += diry;
}
return true; /// 未發現不同 說明存在五連珠
}
void MakeSuc() { /// 將五連珠染紅
for(int i = 1; i <= 5; i ++) {
statue[tmpx][tmpy] = 3;
tmpx += tmpdx;
tmpy += tmpdy;
}
}
bool CheckWinOnPos(int x, int y) { /// 檢測某一點開始是否有五連珠
/// 我們只需要檢查 左下, 右下, 右, 下 這四個方向
return CheckWinInDir(x, y, 0, 1) || /// 右
CheckWinInDir(x, y, 1, 0) || /// 下
CheckWinInDir(x, y, 1, -1) || /// 左下
CheckWinInDir(x, y, 1, 1); /// 右下
}
int CheckWin() { /// 檢測是否有人勝利
for(int i = 1; i <= _MaxLength; i ++) {
for(int j = 1; j <= _MaxLength; j ++) {
if(CheckWinOnPos(i, j)) {
int tmp = statue[i][j];
MakeSuc();
Display();
return tmp; /// 返回勝利玩家
}
}
}
return 0; /// 返回遊戲未結束
}
int MsgCnt = 0;
void OutputMsg(const char* str, int pos = 47, int col = clr::DEFAULT) { /// 用於輸出遊戲信息
wnd::gotoxy(MsgCnt%20+1, pos);
for(int i = pos; i <= 78; i ++) putchar(' ');
wnd::gotoxy(MsgCnt%20, pos);
for(int i = pos; i <= 78; i ++) putchar(' ');
wnd::gotoxy(MsgCnt%20, pos);
wnd::color(col);
printf("%3d: %s", MsgCnt+1, str);
wnd::color(clr::DEFAULT);
MsgCnt ++;
}
const int WARN_COLOR = makecol(clr::BLACK, clr::RED|clr::LIGHT);
const int SUCC_COLOR = makecol(clr::BLACK, clr::GREEN|clr::LIGHT);
const int TIPS_COLOR = makecol(clr::BLACK, clr::YELLOW|clr::LIGHT); /// 定義幾種顏色
const int BLUE_COLOR = makecol(clr::BLACK, clr::PURPLE|clr::LIGHT);
int Game() { /// 進行一輪遊戲
InitStatue(); /// 初始化棋盤
Display();
for(int i = 1; i <= _MaxLength*_MaxLength-1; i ++) {
int piece = (i%2==0) ? _BlackPiece : _WhitePiece;
OutputMsg((piece==_BlackPiece) ? "等待黑棋落子 ..." : "等待白棋落子 ...", 47,
(piece==_BlackPiece) ? TIPS_COLOR : BLUE_COLOR);
POINT p = GetClick();
statue[p.y][p.x] = piece; /// 從鼠標獲取落子
Display();
int tmp;
if(tmp=CheckWin()) { /// 判斷是否有人勝利
OutputMsg((tmp==_BlackPiece) ? "黑棋勝利!" : "白棋勝利!", 47, WARN_COLOR);
return tmp;
}
}
OutputMsg("平局!", 47, SUCC_COLOR);
return _Empty;
}
}
#include <ctime>
int main() {
srand(time(NULL)); /// 重置隨機種子
ChessBoard::Game();
while(1);
return 0;
}
#else
/// 文件已經被引用過
#endif
cursorope.cpp 鼠標操作頭文件
/// 2019.6.8 鼠標操作頭文件
#ifndef __CURSOROPE_CPP__
#define __CURSOROPE_CPP__
#include <windows.h>
#include <cstdio>
namespace hwndforcursor { /// hfc
HWND hwnd=GetForegroundWindow(); /// 當前窗口句柄
bool checkupon() { /// 判斷當前窗口是否在最頂層
HWND hwndn=GetForegroundWindow(); return hwndn == hwnd;
}
#define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME) & 0x8000) ? 1:0)
}
#define hfc hwndforcursor
namespace cursorope { /// 支持鼠標操作
POINT GetCursorPos() { /// 獲取鼠標座標
POINT p;
GetCursorPos(&p); /// windows API 獲取鼠標座標
return p; /// 橫x 縱y 左上角是(0, 0)
}
void OutputPoint(POINT p) { /// 輸出點座標
printf("(%5d, %5d)", p.x, p.y);
}
RECT GetConsolePos() { /// 獲取當前窗口的像素座標
RECT rect;
GetWindowRect(hfc::hwnd,&rect); /// windows API 獲取控制檯窗口的座標
return rect;
}
POINT GetCursorPosInConsole() { /// 獲得鼠標相對於控制檯的相對位置
POINT p = GetCursorPos();
RECT rect = GetConsolePos();
p.x -= rect.left;
p.y -= rect.top;
return p;
}
POINT GetCursorCharPosInConsole() { /// 找到鼠標在控制檯上對應字符位置的座標
POINT p = GetCursorPosInConsole();
p.x -= 5; p.x /= 8;
p.y -= 31; p.y /= 16;
return p;
}
#define Rin(A, B, C) (((A)<=(B))&&((B)<=(C))) /// 判斷 A<=B<=C
POINT WaitCursorLeftClick() { /// 等待鼠標點擊控制檯然後返回點擊的字符位置
POINT p;
while(1) {
if(KEY_DOWN(VK_LBUTTON) && hfc::checkupon()) {
p = GetCursorCharPosInConsole();
if(Rin(0, p.x, 79) && Rin(0, p.y, 24)) {
break;
}
}
Sleep(20);
}
Sleep(100);
return p;
}
}
/*
#include "winshow.cpp"
int main() {
while(1) {
POINT p = cursorope::WaitCursorLeftClick();
wshow::output(wshow::dot("●"), p.y, p.x/2);
}
return 0;
}*/
#undef hfc
#undef Rin
#else
/// 頭文件已經被定義過了
#endif
winshow.cpp 控制檯輸出頭文件
#ifndef __WINSHOW_CPP__
#define __WINSHOW_CPP__
/// 2019.6.6 控制檯輸出
/// 粘的 Tetris 原來的板子
#include <windows.h>
#include <cstdlib>
#include <cstdio>
namespace hwndset {
HWND hwnd=GetForegroundWindow(); //當前窗口句柄
bool checkupon() { /// 判斷當前窗口是否在最頂層
HWND hwndn=GetForegroundWindow(); return hwndn == hwnd;
}
#define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME) & 0x8000) ? 1:0) /// 檢測字符是否按下
///222: #define VK_LEFT 0x25
///223: #define VK_UP 0x26
///224: #define VK_RIGHT 0x27
///225: #define VK_DOWN 0x28 -- from <windows.h>
#define SHAKE (5)
void shake(){ /// 屏幕晃動, 懶得寫了, 粘的板子
RECT rect;
/// HWND hwnd=GetForegroundWindow();
GetWindowRect(hwnd,&rect);
MoveWindow(hwnd,rect.left+SHAKE,rect.top,rect.right-rect.left,rect.bottom-rect.top,TRUE);
Sleep(28);
MoveWindow(hwnd,rect.left+SHAKE,rect.top-SHAKE,rect.right-rect.left,rect.bottom-rect.top,TRUE);
Sleep(28);
MoveWindow(hwnd,rect.left,rect.top-SHAKE,rect.right-rect.left,rect.bottom-rect.top,TRUE);
Sleep(28);
MoveWindow(hwnd,rect.left,rect.top,rect.right-rect.left,rect.bottom-rect.top,TRUE);
}
#undef SHAKE
}
int RND(int L, int R) {
#define RND ((rand()<<15)+rand())
return RND%(R - L + 1) + L;
#undef RND
}
namespace clr { /// 關於顏色的一些常量
const int BLACK=0, BLUE=1, GREEN=2, CYAN=3, RED=4, PURPLE=5, YELLOW=6, WHITE=7, LIGHT=8;
const int DEFAULT = (clr::BLACK<<4)|clr::WHITE;
int makecol(int background, int letter) { /// 裝載一種顏色
return (background << 4) | letter;
}
}
using clr::makecol; /// 開放一個對外函數 makecol
namespace wnd { /// 關於 Windows API 的使用
void gotoxy(int y, int x) { /// 更改屏幕輸出位置
COORD pos; pos.X = x; pos.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),pos);
}
void color(int a) { /// 更改輸出指針顏色
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),a);
}
}
namespace wshow {
struct dot { /// 屏幕上的基本單位
char ch[2], col; /// 兩個字節內容,以及其顏色
dot() {
col = makecol(clr::BLACK, clr::WHITE); // 默認"黑底白字"
ch[0] = ch[1] = 0; /// 默認沒有任何字符
}
dot(const char* s, int c = clr::DEFAULT) { /// 通過字符串構造 dot
ch[0] = s[0], ch[1] = s[1]; col = c; /// 初始化一個字符
if(ch[0]<' ' && ch[0]>=-2) ch[0] = 0;
if(ch[1]<' ' && ch[1]>=-2) ch[1] = 0; /// 避免 tab '\n' 之類的不必要的控制字符的出現
}
} Screen[24][39]; /// 用這個數組去描述屏幕中"受控制(被記錄)"的位置
//int store[24][39]; /// 這是俄羅斯方塊數組的"固化版"
bool operator == (dot A, dot B) { /// 判斷兩個 dot 是否相同
return A.ch[0]==B.ch[0] && A.ch[1]==B.ch[1] && A.col==B.col; /// 考慮顏色
}
//dot transcol(dot A,int ncol) {
// return dot(A.ch, ncol); /// 返回一個內容相同顏色不同的 dot
//}
void print(dot A) { /// 輸出一個 dot
wnd::color(A.col); /// 先把輸入指針變到對應的顏色
printf("%c%c", A.ch[0], A.ch[1]); /// 輸出這個字符
wnd::color(clr::DEFAULT); /// 及時恢復到默認顏色
}
void output(dot A, int y, int x) { /// 唯一的一個新寫的函數
if(!(Screen[y][x] == A)) { /// 簡單粗暴地指定一個位置, 並輸出字符
wnd::gotoxy(y, x*2);
print(A);
}
}
}
/*
int main() {
return 0;
}
*/
#else
/// 說明頭文件已經被引用過了
#endif