使用C语言实现中国象棋每个棋子规则的算法
棋盘
+
与制表符(┌┬┐└┴┘├┼┤)
棋盘分析
棋盘大小:9列10行
棋子有7种,红、黑双方各自拥有16个棋子,共计32个。
使用加号绘制棋盘
+
一个字节占一个字符,是最简单的一种绘制棋盘的方式。
+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+
代码实现:
#include <stdio.h>
int main(int argc, const char *argv[])
{
for(int i = 0; i < 10; i++) {
printf("+");
for(int j = 0; j < 8; j++) {
printf("-+");
}
printf("\n");
}
return 0;
}
制表符
比之前的美观一点,但是注意:这里的每个字符并不只占一个字节。
┌─┬─┬─┬─┬─┬─┬─┬─┐
├─┼─┼─┼─┼─┼─┼─┼─┤
├─┼─┼─┼─┼─┼─┼─┼─┤
├─┼─┼─┼─┼─┼─┼─┼─┤
├─┴─┴─┴─┴─┴─┴─┴─┤
├─┬─┬─┬─┬─┬─┬─┬─┤
├─┼─┼─┼─┼─┼─┼─┼─┤
├─┼─┼─┼─┼─┼─┼─┼─┤
├─┼─┼─┼─┼─┼─┼─┼─┤
└─┴─┴─┴─┴─┴─┴─┴─┘
#include <stdio.h>
int main(int argc, const char *argv[])
{
printf("%ld\n", sizeof("┼"));
printf("%ld\n", sizeof("┼─"));
return 0;
}
打印结果:
$ ./test
4
7
很明显一个制表符会占用三个字节,算上’\0’,第一次输出是占用了4个字节,第二次则是占用了7个字节。
最主要的是不能像+那样以单字节的方式进行操作:
char ch = '+';
char ch = '┼'; //出现警告
//overflow in conversion from ‘int’ to ‘char’ changes value from ‘14849212’ to ‘-68’
//从 'int' 到 'char' 的转换溢出会将值从 '14849212' 更改为 '-68'
为了解决这个问题,需要引入一个新的数据类型————wchar_t
关于宽字节字符
宽字节可以实现汉字按照单个字符的方式处理的功能
示例:
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
int main(int argc, const char *argv[])
{
setlocale(LC_ALL, ""); //本地化设置
wchar_t wch = L'李';
wprintf(L"%lc\n", wch);
return 0;
}
运行结果:
$ ./test
李
使用注意事项
- 乱码问题:
宽字节类型占用更大的空间,可以表示汉字。但是却无法正常的显示汉字。
运行以下代码会出来乱码:
#include <stdio.h>
#include <wchar.h>
int main(int argc, const char *argv[])
{
wchar_t wch = L'李';
wprintf(L"%lc\n", wch);
return 0;
}
运行结果:
$ ./test
?
解决方法:本地化设置
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
int main(int argc, const char *argv[])
{
setlocale(LC_ALL, ""); //本地化设置
wchar_t wch = L'李';
wprintf(L"%lc\n", wch);
return 0;
}
运行结果:
$ ./test
李
注意:在进行本地化设置之前需要确保系统支持中文语言。
关于宽字节数据类型的数据宽度
#include <stdio.h>
#include <wchar.h>
int main(int argc, const char *argv[])
{
wchar_t wch = L'S';
wchar_t wch1 = L'a';
wchar_t wstr[] = {L"Sa"};
wprintf(L"%lc\n", wch);
wprintf(L"%lc\n", wch1);
wprintf(L"%ls\n", wstr);
wprintf(L"size:%ld\n", sizeof(wch));
wprintf(L"size:%ld\n", sizeof(wch1));
wprintf(L"size:%ld\n", sizeof(wstr));
return 0;
}
char | wchar_t |
---|---|
1个字节 | 4个字节 |
C标准支持 | 包含wchar.h |
本地化配置:不需要 | 本地化配置:需要 |
以宽字节的方式绘制棋盘
字符串数组
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
int main(int argc, const char *argv[])
{
setlocale(LC_ALL, "");
const wchar_t* boardstr[] = {
L"┌─┬─┬─┬─┬─┬─┬─┬─┐",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┴─┴─┴─┴─┴─┴─┴─┤",
L"├─┬─┬─┬─┬─┬─┬─┬─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"└─┴─┴─┴─┴─┴─┴─┴─┘"
};
for (int i = 0; i < sizeof(boardstr) / sizeof(boardstr[0]); i++) {
wprintf(L"%ls\n", boardstr[i]);
}
return 0;
}
以位置为单位输出
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
#define HEIGHT 10
#define WIDTH 9
int main(int argc, const char *argv[])
{
setlocale(LC_ALL, "");
const wchar_t* boardstr[] = {
L"┌─┬─┬─┬─┬─┬─┬─┬─┐",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┴─┴─┴─┴─┴─┴─┴─┤",
L"├─┬─┬─┬─┬─┬─┬─┬─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"└─┴─┴─┴─┴─┴─┴─┴─┘"
};
for (int i = 0; i < HEIGHT; i++) {
for (int j = 0; j < WIDTH; j++) {
wprintf(L"%lc%lc", boardstr[i][j*2], boardstr[i][j*2+1]);
}
wprintf(L"\n");
}
return 0;
}
棋子
棋子的特点
红、黑双方;各有7个兵种。每个兵种都有多个,例如:红方的兵,和黑方的卒和自都有5个,各自都有两个車、两个馬、两个象(相)、两个士(仕)、两个炮,还有一个将(帅)。
棋子关系示意图
红方 黑方
┌─────┬─────┼─────┬...┐ ┌─────┬─────┼....
車 馬 相 仕 .... 車 馬 象....
┌─┴┐ ┌─┴─┐ ┌┴─┐ ┌┴─┐ ....┌─┴┐ ┌─┴─┐ ┌┴─┐ ┌┴─┐
車 車 馬 馬 相 相 仕 仕 車 車 馬 馬 象 象士 士
红、黑双方各自只有7种棋子,如果把空
算到棋子的概念里,那么正好是八个,可以用八进制表示
0 1 2 3 4 5 6 7
空 車 馬 象 士 将 砲 卒
宽字节字符串表示棋子
const wchar_t* pieces_str[2] = {
L" 車馬象士将砲卒",
L" 车马相仕帅炮兵"
};
二维数组表示棋盘
使用二维数组表示,10行,9列。
int board[10][9] = {};
输出带棋子的棋盘
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
#define HEIGHT 10
#define WIDTH 9
int main(int argc, const char *argv[])
{
setlocale(LC_ALL, "");
const wchar_t* boardstr[] = {
L"┌─┬─┬─┬─┬─┬─┬─┬─┐",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┴─┴─┴─┴─┴─┴─┴─┤",
L"├─┬─┬─┬─┬─┬─┬─┬─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"└─┴─┴─┴─┴─┴─┴─┴─┘"
};
const wchar_t* pieces_str[2] = {
L" 車馬象士将砲卒",
L" 车马相仕帅炮兵"
};
int board[10][9] = {
{001, 002, 003, 004, 005, 004, 003, 002, 001},
{000, 000, 000, 000, 000, 000, 000, 000, 000},
{000, 006, 000, 000, 000, 000, 000, 006, 000},
{007, 000, 007, 000, 007, 000, 007, 000, 007},
{000, 000, 000, 000, 000, 000, 000, 000, 000},
};
for (int i = 0; i < HEIGHT; i++) {
for (int j = 0; j < WIDTH; j++) {
if(board[i][j]) {
wprintf(L"%lc", pieces_str[0][ board[i][j] ] );
} else {
wprintf(L"%lc%lc", boardstr[i][j*2], boardstr[i][j*2+1]);
}
}
wprintf(L"\n");
}
return 0;
}
并没有放置红方的棋子,原因是001、002、003这种数字很容易混淆。所以需要一个类似宏定义的东西,把它具像化:
#define BJU 001
#define RMA 002
#define BXI 003
...
这处呈现一定规律的数据也可以用枚举类型表示:
enum {
BNONE, BJU, BMA, BXI, BSH, BJI, BPA, BZU,
RNONE, RJU, RMA, RXI, RSH, RSU, RPA, RBI
};
有了枚举类型以后的代码:
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
#define HEIGHT 10
#define WIDTH 9
enum {
BNONE, BJU, BMA, BXI, BSH, BJI, BPA, BZU,
RNONE, RJU, RMA, RXI, RSH, RSU, RPA, RBI
};
int main(int argc, const char *argv[])
{
setlocale(LC_ALL, "");
const wchar_t* boardstr[] = {
L"┌─┬─┬─┬─┬─┬─┬─┬─┐",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┴─┴─┴─┴─┴─┴─┴─┤",
L"├─┬─┬─┬─┬─┬─┬─┬─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"└─┴─┴─┴─┴─┴─┴─┴─┘"
};
const wchar_t* pieces_str[2] = {
L" 車馬象士将砲卒",
L" 车马相仕帅炮兵"
};
int board[10][9] = {
{BJU, BMA, BXI, BSH, BJI, BSH, BXI, BMA, BJU},
{},
{000, BPA, 000, 000, 000, 000, 000, BPA, 000},
{BZU, 000, BZU, 000, BZU, 000, BZU, 000, BZU},
{},
{},
{RBI, 000, RBI, 000, RBI, 000, RBI, 000, RBI},
{000, RPA, 000, 000, 000, 000, 000, RPA, 000},
{},
{RJU, RMA, RXI, RSH, RSU, RSH, RXI, RMA, RJU}
};
for (int i = 0; i < HEIGHT; i++) {
for (int j = 0; j < WIDTH; j++) {
if(board[i][j]) {
wprintf(L"%lc", pieces_str[ board[i][j]/8 ][ board[i][j]%8 ] );
} else {
wprintf(L"%lc%lc", boardstr[i][j*2], boardstr[i][j*2+1]);
}
}
wprintf(L"\n");
}
return 0;
}
作业:
棋盘除了可以竖着展示以外,其实也可以横向展示,如图:
┏━━┳━━┳━━╦━━┳━━┳━━╦━━┳━━┳━━┓
┣━━╋━━╬━━╋━━┫ ┣━━╋━━╬━━╋━━┫
┣━━╋━━╋━━╬━━┫ ┣━━╬━━╋━━╋━━┫
┣━━╋━━╋━━╋━━┫ ┣━━╋━━╋━━╋━━┫
┣━━╋━━╋━━╬━━┫ ┣━━╬━━╋━━╋━━┫
┣━━╋━━╋━━╋━━┫ ┣━━╋━━╋━━╋━━┫
┣━━╋━━╋━━╬━━┫ ┣━━╬━━╋━━╋━━┫
┣━━╋━━╬━━╋━━┫ ┣━━╋━━╬━━╋━━┫
┗━━┻━━┻━━╩━━┻━━┻━━╩━━┻━━┻━━┛
画出类似以上方式的棋盘
L"┏━━┳━━┳━━╦━━┳━━┳━━╦━━┳━━┳━━┓",
L"┣━━╋━━╬━━╋━━┫ ┣━━╋━━╬━━╋━━┫",
L"┣━━╋━━╋━━╬━━┫ ┣━━╬━━╋━━╋━━┫",
L"┣━━╋━━╋━━╋━━┫ ┣━━╋━━╋━━╋━━┫",
L"┣━━╋━━╋━━╬━━┫ ┣━━╬━━╋━━╋━━┫",
L"┣━━╋━━╋━━╋━━┫ ┣━━╋━━╋━━╋━━┫",
L"┣━━╋━━╋━━╬━━┫ ┣━━╬━━╋━━╋━━┫",
L"┣━━╋━━╬━━╋━━┫ ┣━━╋━━╬━━╋━━┫",
L"┗━━┻━━┻━━╩━━┻━━┻━━╩━━┻━━┻━━┛"
获取命令
制定命令格式
来自专业人士的习惯
来自象棋爱好者的习惯:
車一进一
马2进3
炮八平五
。。。
十进制的巧合
9+1
就是十进制:
0 1 2 3 4 5 6 7 8 9
0 ┌─┬─┬─┬─┬─┬─┬─┬─┐
1 ├─┼─┼─┼─┼─┼─┼─┼─┤
2 ├─┼─┼─┼─┼─┼─┼─┼─┤
3 ├─┼─┼─┼─┼─┼─┼─┼─┤
4 ├─┴─┴─┴─┴─┴─┴─┴─┤
5 ├─┬─┬─┬─┬─┬─┬─┬─┤
6 ├─┼─┼─┼─┼─┼─┼─┼─┤
7 ├─┼─┼─┼─┼─┼─┼─┼─┤
8 ├─┼─┼─┼─┼─┼─┼─┼─┤
9 └─┴─┴─┴─┴─┴─┴─┴─┘
如果用两位的十进制表示棋盘的每一个位置的话,这个数字的位置,将在1~99之间,而且10,20,30这种可以被10整除的位置是不存在的。
也就是说,输入的时候,以十进制表示棋盘位置。个位19的区间表示棋盘列数,十位09 表示棋盘行号。
实际的数组下标位置列数范围0~8,行数范围和输入的时候一致。
以初始炮
的位置为例:
输入的位置信息 | 72 | 75 |
---|---|---|
实际的数组下标 | [7][1] | [7][4] |
scanf函数的死亡循环
常见的代码
#include <stdio.h>
int main(int argc, char *argv[]) {
int a = 0;
while(1) {
scanf("%d", &a);
printf("a = %d\n", a);
}
return 0;
}
这里一个简单的scanf
函数示例代码,这里如果输入一个字符就会发生无限循环的悲剧。原因分析
规规矩矩的输入:
非法输入导致的死亡循环
解决方案
#include <stdio.h>
int main(int argc, char *argv[]) {
int a = 0;
while(1) {
scanf("%d", &a);
printf("a = %d\n", a);
while(getchar() != '\n');
}
return 0;
}
ChncGetCmd函数的实现
ChncGetCmd函数
int ChncGetCmd() {
int cmd;
wprintf(L"请【%ls】输入命令 > ", player ? L"红方" : L"黑方");
scanf("%d", &cmd);
wprintf(L"cmd:%d\n", cmd);
while(getchar() != '\n');
return cmd;
}
代码迭代
- 枚举类型用typedef定义成数据类型
- 增加全局变量player表示【玩家】
- 使用控制码显示红色字体,增加输出行号、列号
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
#define HEIGHT 10
#define WIDTH 9
//枚举数据类型
typedef enum {
BNONE, BJU, BMA, BXI, BSH, BJI, BPA, BZU,
RNONE, RJU, RMA, RXI, RSH, RSU, RPA, RBI
}pieces_t;
//用于显示棋子的宽字节字符串指针数组
const wchar_t* pieces[2] = {
L" 車馬象士将砲卒",
L" 车马相仕帅炮兵"
};
//棋盘
pieces_t board[10][9] = {
{BJU, BMA, BXI, BSH, BJI, BSH, BXI, BMA, BJU},
{},
{000, BPA, 000, 000, 000, 000, 000, BPA, 000},
{BZU, 000, BZU, 000, BZU, 000, BZU, 000, BZU},
{},
{},
{RBI, 000, RBI, 000, RBI, 000, RBI, 000, RBI},
{000, RPA, 000, 000, 000, 000, 000, RPA, 000},
{},
{RJU, RMA, RXI, RSH, RSU, RSH, RXI, RMA, RJU}
};
//用于判断玩家将红方(RNONE)还是黑方(BNONE)
pieces_t player = RNONE;
//主要函数
void ChncShow();
int ChncGetCmd();
int main(int argc, const char *argv[])
{
//本地化设置
setlocale(LC_ALL, "");
while(1) {
ChncShow();
if (ChncGetCmd() < 1) {
break;
}
}
return 0;
}
//获取命令
int ChncGetCmd() {
int cmd;
wprintf(L"请【%ls】输入命令 > ", player ? L"红方" : L"黑方");
scanf("%d", &cmd);
wprintf(L"cmd:%d\n", cmd);
while(getchar() != '\n');
return cmd;
}
//显示棋局
void ChncShow() {
//用于显示的棋盘
const wchar_t* boardstr[] = {
L"┌─┬─┬─┬─┬─┬─┬─┬─┐",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┴─┴─┴─┴─┴─┴─┴─┤",
L"├─┬─┬─┬─┬─┬─┬─┬─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"└─┴─┴─┴─┴─┴─┴─┴─┘"
};
//打印列号
wprintf(L" 1 2 3 4 5 6 7 8 9\n");
for (int i = 0; i < sizeof(boardstr) / sizeof(boardstr[0]); i++) {
wprintf(L"%d ", i); //打印行号
for(int j = 0; j < 9; j++) {
if(board[i][j]) { //该位置有棋子,则显示棋子
if( board[i][j] & 010 ) {
wprintf(L"\033[1;31;31m"); //控制码,显示红色
wprintf(L"%lc", pieces[board[i][j]/8][ board[i][j] % 8]); //红方棋子
wprintf(L"\033[0m"); //控制码,恢复默认
} else {
wprintf(L"%lc", pieces[board[i][j]/8][ board[i][j] % 8]); //黑方棋子
}
} else { //没有棋子,则显示棋盘
wprintf(L"%lc%lc", boardstr[i][j*2], boardstr[i][j*2+1]);
}
}
wprintf(L"\n"); //换行
}
}
移动棋子
【位置】到数组下标board[y][x]
db
棋盘到ChncMove函数
0 ┌─┬─┬─┬─┬─┬─┬─┬─┐
1 ├─┼─┼─┼─┼─┼─┼─┼─┤
2 ├─┼─┼─┼─┼─┼─┼─┼─┤
3 ├─┼─┼─┼─┼─┼─┼─┼─┤
4 ├─┴─┴─┴─┴─┴─┴─┴─┤
5 ├─┬─┬─┬─┬─┬─┬─┬─┤
6 ├─┼─┼─┼─┼─┼─┼─┼─┤
7 ├─┼─┼─┼─┼─┼─┼─┼─┤
8 ├─┼─┼─┼─┼─┼─┼─┼─┤
9 └─┴─┴─┴─┴─┴─┴─┴─┘
1 2 3 4 5 6 7 8 9
void ChncMove (int cmd) {
int start_y = cmd/1000;
int start_x = cmd/100 % 10 - 1;//标注的列数(1~9)和下标(0~8)错一位
int end_y = cmd%100 / 10;
int end_x = cmd % 10 - 1; //标注的列数(1~9)和下标(0~8)错一位
board[end_y][end_x] = board[start_y][start_x];
board[start_y][start_x] = 0;
player = player : BNONE ? RNONE;
}
代码迭代:
ChncGetCmd
函数增加cmd
参数- 把参数传递给ChncMove函数
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
#define HEIGHT 10
#define WIDTH 9
//枚举数据类型
typedef enum {
BNONE, BJU, BMA, BXI, BSH, BJI, BPA, BZU,
RNONE, RJU, RMA, RXI, RSH, RSU, RPA, RBI
}piece_t;
//用于显示棋子的宽字节字符串指针数组
const wchar_t* pieces[2] = {
L" 車馬象士将砲卒",
L" 车马相仕帅炮兵"
};
//棋盘
piece_t board[10][9] = {
{BJU, BMA, BXI, BSH, BJI, BSH, BXI, BMA, BJU},
{},
{000, BPA, 000, 000, 000, 000, 000, BPA, 000},
{BZU, 000, BZU, 000, BZU, 000, BZU, 000, BZU},
{},
{},
{RBI, 000, RBI, 000, RBI, 000, RBI, 000, RBI},
{000, RPA, 000, 000, 000, 000, 000, RPA, 000},
{},
{RJU, RMA, RXI, RSH, RSU, RSH, RXI, RMA, RJU}
};
//用于判断玩家将红方(RNONE)还是黑方(BNONE)
piece_t player = RNONE;
//主要函数
void ChncShow();
int ChncGetCmd();
void ChncMove (int cmd);
int main(int argc, const char *argv[])
{
//本地化设置
setlocale(LC_ALL, "");
while(1) {
ChncShow();
int cmd = ChncGetCmd();
if (cmd < 1) {
break;
}
ChncMove (cmd);
}
return 0;
}
//移动棋子
void ChncMove (int cmd) {
int start_y = cmd/1000;
int start_x = cmd/100 % 10 - 1; //标注的列数(1~9)和下标(0~8)错一位
int end_y = cmd%100 / 10;
int end_x = cmd % 10 - 1; //标注的列数(1~9)和下标(0~8)错一位
board[end_y][end_x] = board[start_y][start_x];
board[start_y][start_x] = 0;
player = player ? BNONE : RNONE;
}
//获取命令
int ChncGetCmd() {
int cmd;
wprintf(L"请【%ls】输入命令 > ", player ? L"红方" : L"黑方");
scanf("%d", &cmd);
wprintf(L"cmd:%d\n", cmd);
while(getchar() != '\n');
return cmd;
}
//显示棋局
void ChncShow() {
//用于显示的棋盘
const wchar_t* boardstr[] = {
L"┌─┬─┬─┬─┬─┬─┬─┬─┐",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┴─┴─┴─┴─┴─┴─┴─┤",
L"├─┬─┬─┬─┬─┬─┬─┬─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"└─┴─┴─┴─┴─┴─┴─┴─┘"
};
//打印列号
wprintf(L" 1 2 3 4 5 6 7 8 9\n");
for (int i = 0; i < sizeof(boardstr) / sizeof(boardstr[0]); i++) {
wprintf(L"%d ", i); //打印行号
for(int j = 0; j < 9; j++) {
if(board[i][j]) { //该位置有棋子,则显示棋子
if( board[i][j] & 010 ) {
wprintf(L"\033[1;31;31m"); //控制码,显示红色
wprintf(L"%lc", pieces[board[i][j]/8][ board[i][j] % 8]); //红方棋子
wprintf(L"\033[0m"); //控制码,恢复默认
} else {
wprintf(L"%lc", pieces[board[i][j]/8][ board[i][j] % 8]); //黑方棋子
}
} else { //没有棋子,则显示棋盘
wprintf(L"%lc%lc", boardstr[i][j*2], boardstr[i][j*2+1]);
}
}
wprintf(L"\n"); //换行
}
}
错误的位置可能带来的隐患
无效的位置可能会出现越界的行为
检查错误位置
//1. 数组下标的位置必须真实有效
if(start_y < 0 || start_y > 9 || start_x < 0 || start_x > 8) {
wprintf(L"位置错误\n");
return -1;
}
if(end_y < 0 || end_y > 9 || end_x < 0 || end_x > 8) {
wprintf(L"位置错误\n");
return -1;
}
可能出现的棋子选择失误
位置上没有棋子的情况
piece_t pie = board[start_y][start_x]; //获取棋子位置
//2.位置上没有棋子的情况下pie的值为0
if(!pie) {
wprintf(L"没有移动任何棋子\n");
return -1;
}
只能移动自己的棋子
实现思路分析:
- 所有红方棋子都大于010(八进制),所有黑方都小于010(八进制)。
車 | 馬 | 象 | 士 | 将 | 砲 | 卒 |
---|---|---|---|---|---|---|
001 | 002 | 003 | 004 | 005 | 006 | 007 |
车 | 马 | 相 | 仕 | 帅 | 炮 | 兵 |
---|---|---|---|---|---|---|
011 | 012 | 013 | 014 | 015 | 016 | 017 |
- player的值介于010和0之间
player==RNONE | 010 | 红方 |
---|---|---|
player==BNONE | 000 | 黑方 |
实现思路:
先让棋子和010按位与
,如果黑方就为0,反之则为010
结果再和player做异或
运算。
例:
代码:
//3.只能移动自己的棋子
if( (pie & 010) ^ player ) {
wprintf(L"请使用自己的棋子\n");
return -1;
}
不能吃自己的棋子
实现思路:
沿用之前的思路,棋子和010相与,可以得知是红方还是黑方,但要考虑目标位置没有棋子的情况。没有棋子的可能会被误判为黑方棋子
//4.不能吃自己的棋子
if ((board[end_y][end_x] & 010) == (pie & 010) && board[end_y][end_x] != 0) {
wprintf(L"不能吃自己的棋子\n");
return -1;
}
三、判断输赢
实现思路:
将帅失其一,定输赢。
- 时刻关注将、帅的位置变化,如果被杀。即宣告游戏结束。
- 判断将、帅哪个不存在,判断输赢
- 中途退出,表示游戏中断。
实现步骤:
- 增加全局变量:jiang、shuai
int jiang_x = 4, jiang_y = 0;
int shuai_x = 4, shuai_y = 9;
- 移动棋子以后更新将或帅的位置
//更新将帅的位置
if(pie == BJI) { //被移动的是将
jiang_y = end_y;
jiang_x = end_x;
}
if(pie == RSU) { //被移动的将帅
shuai_y = end_y;
shuai_x = end_x;
}
- 如果移动棋子之前,发现被杀,更新状态:
//将或者帅被杀
if(board[end_y][end_x] == BJI) {
jiang_y = 0;
jiang_x = 0;
}
if(board[end_y][end_x] == RSU) {
shuai_y = 0;
shuai_x = 0;
}
- 判断并退出游戏
.....
/*
* 返回0,表示游戏继续。
* 返回其他数字,表示游戏结束
* */
int ChncGameOver() {
if(jiang_x == 0) {
wprintf(L"红方胜利\n");
return 1;
} else if(shuai_x == 0) {
wprintf(L"黑方胜利\n");
return -1;
} else
return 0;
return 0;
}
乞丐版本完整代码:
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
#define HEIGHT 10
#define WIDTH 9
//枚举数据类型
typedef enum {
BNONE, BJU, BMA, BXI, BSH, BJI, BPA, BZU,
RNONE, RJU, RMA, RXI, RSH, RSU, RPA, RBI
}piece_t;
//用于显示棋子的宽字节字符串指针数组
const wchar_t* pieces[2] = {
L" 車馬象士将砲卒",
L" 车马相仕帅炮兵"
};
//棋盘
piece_t board[10][9] = {
{BJU, BMA, BXI, BSH, BJI, BSH, BXI, BMA, BJU},
{},
{000, BPA, 000, 000, 000, 000, 000, BPA, 000},
{BZU, 000, BZU, 000, BZU, 000, BZU, 000, BZU},
{},
{},
{RBI, 000, RBI, 000, RBI, 000, RBI, 000, RBI},
{000, RPA, 000, 000, 000, 000, 000, RPA, 000},
{},
{RJU, RMA, RXI, RSH, RSU, RSH, RXI, RMA, RJU}
};
//用于判断玩家将红方(RNONE)还是黑方(BNONE)
piece_t player = RNONE;
//用于判断输赢
int jiang_x = 4, jiang_y = 0;
int shuai_x = 4, shuai_y = 9;
//主要函数
void ChncShow();
int ChncGetCmd();
int ChncMove (int cmd);
int ChncGameOver();
int main(int argc, const char *argv[])
{
//本地化设置
setlocale(LC_ALL, "");
while(1) {
ChncShow();
int cmd = ChncGetCmd();
if (cmd < 1) {
break;
}
ChncMove (cmd);
if( ChncGameOver() )
break;
}
return 0;
}
/*
* 返回0,表示游戏继续。
* 返回其他数字,表示游戏结束
* */
int ChncGameOver() {
if(jiang_x == 0) {
wprintf(L"红方胜利\n");
return 1;
} else if(shuai_x == 0) {
wprintf(L"黑方胜利\n");
return -1;
} else
return 0;
return 0;
}
/*
* * 转换全局变量startp和endp的位置信息为
* * board[10][9]的数组下标,除以10得到行号,与10取余求出列数。
* * 列数减1,才是正确下标
*/
int ChncMove (int cmd) {
int start_y = cmd/1000;
int start_x = cmd/100 % 10 - 1; //标注的列数(1~9)和下标(0~8)错一位
int end_y = cmd%100 / 10;
int end_x = cmd % 10 - 1; //标注的列数(1~9)和下标(0~8)错一位
/* 错误检查 */
//1. 数组下标的位置必须真实有效
if(start_y < 0 || start_y > 9 || start_x < 0 || start_x > 8) {
wprintf(L"位置错误\n");
return -1;
}
if(end_y < 0 || end_y > 9 || end_x < 0 || end_x > 8) {
wprintf(L"位置错误\n");
return -1;
}
//棋子信息
piece_t pie = board[start_y][start_x];
//2.位置上没有棋子的情况下pie的值为0
if(!pie) {
wprintf(L"没有移动任何棋子\n");
return -1;
}
//3.只能移动自己的棋子
if( (pie & 010) ^ player ) {
wprintf(L"请使用自己的棋子\n");
return -1;
}
//4.不能吃自己的棋子
if ((board[end_y][end_x] & 010) == (pie & 010) && board[end_y][end_x] != 0) {
wprintf(L"不能吃自己的棋子\n");
return -1;
}
//将或者帅被杀
if(board[end_y][end_x] == BJI) {
jiang_y = 0;
jiang_x = 0;
}
if(board[end_y][end_x] == RSU) {
shuai_y = 0;
shuai_x = 0;
}
//移动棋子
board[end_y][end_x] = board[start_y][start_x];
board[start_y][start_x] = 0;
//更新将帅的位置
if(pie == BJI) { //被移动的是将
jiang_y = end_y;
jiang_x = end_x;
}
if(pie == RSU) { //被移动的将帅
shuai_y = end_y;
shuai_x = end_x;
}
player = player ? BNONE : RNONE;
return 0;
}
//获取命令
int ChncGetCmd() {
int cmd;
wprintf(L"请【%ls】输入命令 > ", player ? L"红方" : L"黑方");
scanf("%d", &cmd);
wprintf(L"cmd:%d\n", cmd);
while(getchar() != '\n');
return cmd;
}
//显示棋局
void ChncShow() {
//用于显示的棋盘
const wchar_t* boardstr[] = {
L"┌─┬─┬─┬─┬─┬─┬─┬─┐",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┴─┴─┴─┴─┴─┴─┴─┤",
L"├─┬─┬─┬─┬─┬─┬─┬─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"├─┼─┼─┼─┼─┼─┼─┼─┤",
L"└─┴─┴─┴─┴─┴─┴─┴─┘"
};
//打印列号
wprintf(L" 1 2 3 4 5 6 7 8 9\n");
for (int i = 0; i < sizeof(boardstr) / sizeof(boardstr[0]); i++) {
wprintf(L"%d ", i); //打印行号
for(int j = 0; j < 9; j++) {
if(board[i][j]) { //该位置有棋子,则显示棋子
if( board[i][j] & 010 ) {
wprintf(L"\033[1;31;31m"); //控制码,显示红色
wprintf(L"%lc", pieces[board[i][j]/8][ board[i][j] % 8]); //红方棋子
wprintf(L"\033[0m"); //控制码,恢复默认
} else {
wprintf(L"%lc", pieces[board[i][j]/8][ board[i][j] % 8]); //黑方棋子
}
} else { //没有棋子,则显示棋盘
wprintf(L"%lc%lc", boardstr[i][j*2], boardstr[i][j*2+1]);
}
}
wprintf(L"\n"); //换行
}
}
规则实现思路
每一个兵种都有不同的规则
帅(将):
- 只能在九宫格内移动
- 每次只能横向或纵向移动一格
- 不能与对方的将(帅)在同一直线上相对,中间无子
士(仕):
- 只能在九宫格内移动
- 每次沿斜线移动一格
象(相):
- "象走田",且不能过河
- 每次沿斜线移动两格
- 不能越过中间的棋子("堵象眼")
马:
- "马走日"形状移动
- 横向两格加纵向一格,或纵向两格加横向一格
- 不能越过直接阻挡的棋子("别马腿")
車:
- “車走一条线”
- 可以横向或纵向移动任意格数
- 不能越过其他棋子
炮:
- “炮翻山”
- 移动时与车相同,可横向或纵向任意格数
- 吃子时必须跳过一个棋子(敌我不限)
兵(卒):
- 未过河时只能向前移动一格
- 过河后可以向前或向左右移动一格
- 不能后退
将、帅不能见面
规则整体的特点
总的来说,所有的规则都是对**棋子位置的约束**,除此以外还有一些其特点:
- 红黑双方对称相反
- 兵种的特殊属性
- 将、帅不能见面
兵种规则的实现
获取位置关系,建立规则判断框架
//获取位置关系 ,暂时不用所以先注释掉
//int dx = end_x - start_x;
//int dy = end_y - start_y;
wprintf(L"移动的是%lc\n", Pstr(pie) );
switch(pie & 007) {
case BJU:
wprintf(L"--%d--\n", __LINE__ );
break;
case BMA:
wprintf(L"--%d--\n", __LINE__ );
break;
case BXI:
wprintf(L"--%d--\n", __LINE__ );
break;
case BSH:
wprintf(L"--%d--\n", __LINE__ );
break;
case BJI:
wprintf(L"--%d--\n", __LINE__ );
break;
case BPA:
wprintf(L"--%d--\n", __LINE__ );
break;
case BZU:
wprintf(L"--%d--\n", __LINE__ );
break;
default:
wprintf(L"没有对应的规则\n", Pstr(pie) );
break;
}
車与炮
車的规则:“車走直线”
if(dx != 0 && dy != 0) {
wprintf(L"車只能走直线\n");
return;
}
車的规则:中间不能有其它棋子
// 检查路径上是否有其他棋子
if (dy == 0) {
int step = dx > 0 ? 1 : -1;
for (int a = start_x+step; a != end_x; a += step) {
if (board[start_y][a]) {
wprintf(L"車的移动路径上有其他棋子\n");
return;
}
}
}
// 检查路径上是否有其他棋子
if (dy == 0) {
int step = dx > 0 ? 1 : -1;
for (int a = start_x+step; a != end_x; a += step) {
if (board[start_y][a]) {
wprintf(L"車的移动路径上有其他棋子\n");
return;
}
}
} else {
int step = dy > 0 ? 1 : -1;
for (int a = start_y+step; a != end_y; a+=step) {
if (board[a][start_x]) {
wprintf(L"車的移动路径上有其他棋子\n");
return;
}
}
}
炮的规则:炮走直线与“炮翻山”的特殊属性
1 2 3 4 5 6 7 8 9
0 ┌─┬─┬─┬─将┬─┬─┬─┐
1 ├─┼─┼─┼─┼─┼─┼─┼─┤
2 ├─┼─炮+─+─+─+'炮┤ <----dy=0
3 ├─┼─|─┼─┼─┼─┼─┼─┤
4 ├─┴─兵┴─┴─┴─┴─┴─┤
5 ├─┬─|─┬─┬─┬─┬─┬─┤
6 ├─┼─卒┼─┼─┼─┼─┼─┤
7 ├─┼─┼─┼─┼─┼─┼─┼─┤
8 ├─┼─┼─┼─┼─┼─┼─┼─┤
9 └─┴─┴─┴─┴─帅┴─┴─┘
|
“炮翻山” dx=0;
if(dx != 0 && dy != 0) {
wprintf(L"炮只能走直线\n");
return;
}
// 检查路径上是否有其他棋子
int count = 0;
if (dy == 0) {
int step = dx > 0 ? 1 : -1;
for (int a = start_x+step; a != end_x; a += step) {
if (board[start_y][a]) {
count++;
}
}
} else {
int step = dy > 0 ? 1 : -1;
for (int a = start_y+step; a != end_y; a+=step) {
if (board[a][start_x]) {
count++;
}
}
}
if(board[end_y][end_x] && count != 1) {
wprintf(L"炮吃子时必须跳过一个棋子\n");
return;
} else if(board[end_y][end_x] == 0 && count != 0) {
wprintf(L"炮的移动路径上有其他棋子\n");
return;
}
馬与相(象)
馬的规则“馬走日”
1 2 3 4 5 6 7 8 9
0 ┌─+─┬─+─将┬─┬─┬─┐ 马有"八面威风"
1 +─┼─┼─┼─+─┼─┼─┼─┤ 如果以馬原有的位置为原点那它所有能抵达的位置关系如下:
2 ├─┼─馬┼─┼─┼─┼─┼─┤ (-2,-1), (-1,-2), ( 1,-2), ( 2,-1)
3 +─┼─┼─┼─+─┼─┼─┼─┤ (-2, 1), (-1, 2), ( 1, 2), ( 2, 1)
4 ├─+─卒兵-─┴─┴─┴─┤ 绝对值如果一个为1,那另一个必需为2.
5 ├─┬─┬─┬─┬─┬─┬─┬─┤
6 ├─┼─┼─┼─┼─┼─┼─┼─┤
7 ├─┼─┼─┼─┼─┼─┼─┼─┤
8 ├─┼─┼─┼─┼─┼─┼─┼─┤
9 └─┴─┴─┴─┴─帅┴─┴─┘
别马腿的情况分析:
1 2 3 4 5 6 7 8 9
0 ┌─+─┬─+─将┬─┬─┬─┐ abs(dx) == 1
1 ├─┼─┼─┼─┼─┼─┼─┼─┤ “马腿”的位置:board[start_y + dy/2][start_x]
2 ├─┼─馬┼─┼─┼─┼─┼─┤
3 ├─┼─┼─┼─┼─┼─┼─┼─┤
4 ├─+─┴─+─┴─┴─┴─┴─┤
5 ├─┬─┬─┬─┬─┬─┬─┬─┤
6 ├─┼─┼─┼─┼─┼─┼─┼─┤
7 ├─┼─┼─┼─┼─┼─┼─┼─┤
8 ├─┼─┼─┼─┼─┼─┼─┼─┤
9 └─┴─┴─┴─┴─帅┴─┴─┘
1 2 3 4 5 6 7 8 9
0 ┌─┬─┬─┬─将┬─┬─┬─┐ abs(dy) == 1
1 +─┼─┼─┼─+─┼─┼─┼─┤ “马腿”的位置:board[start_y][start_x + dx/2]
2 ├─┼─馬┼─┼─┼─┼─┼─┤
3 +─┼─┼─┼─+─┼─┼─┼─┤
4 ├─┴─┴─┴─┴─┴─┴─┴─┤
5 ├─┬─┬─┬─┬─┬─┬─┬─┤
6 ├─┼─┼─┼─┼─┼─┼─┼─┤
7 ├─┼─┼─┼─┼─┼─┼─┼─┤
8 ├─┼─┼─┼─┼─┼─┼─┼─┤
9 └─┴─┴─┴─┴─帅┴─┴─┘
if ( ( abs(dx) == 1 && abs(dy) == 2) || (abs(dx) == 2 && abs(dy) == 1) ) {
if ( abs(dx) == 1 && board[start_y + dy/2][start_x] ) {
wprintf(L"马腿被挡住了\n");
return;
}
if ( abs(dy) == 1 && board[start_y][start_x + dx / 2] ) {
wprintf(L"马腿被挡住了\n");
return;
}
} else {
wprintf(L"马只能走'日'字\n");
return;
}
相(象)的规则:“象走田”
1 2 3 4 5 6 7 8 9
0 ┌─┬─+─┬─将┬─+─┬─┐
1 ├─┼─┼─┼─┼─┼─┼─┼─┤
2 ├─┼─┼─┼─象┼─┼─┼─┤ dx和dy的绝对值都必须为2!!!
3 ├─┼─┼─车┼─┼─┼─┼─┤
4 ├─┴─+─┴─┴─┴─+─┴─┤
5 ├─┬─┬─┬─┬─┬─┬─┬─┤
6 ├─┼─┼─┼─┼─┼─┼─┼─┤
7 ├─┼─┼─┼─┼─┼─┼─┼─┤
8 ├─┼─┼─┼─┼─┼─┼─┼─┤
9 └─┴─┴─┴─┴─帅┴─┴─┘
if ( ( abs(dx) != 2 || abs(dy) != 2) ) {
wprintf(L"相只能走'田'字\n");
return;
}
if ( board[start_y + dy/2][start_x + dx/2] ) {
wprintf(L"象眼被挡住了\n");
return;
}
if ( !( pie & RNONE ) && end_y > 4) {
wprintf(L"象不能过河\n");
return;
}
if ( ( pie & RNONE ) && end_y < 5) {
wprintf(L"相不能过河\n");
return;
}
将(帅)与士(仕)
将(帅)的规则:只能走一格
将(帅)的规则:不能超出“九宫格”
if ((abs(dx) + abs(dy) != 1) || (dx != 0 && dy != 0)) {
wprintf(L"将只能走一格直线\n");
return -1;
}
// 将/帅必须在九宫格内
if (end_x < 3 || end_x > 5 || ( (pie & 010) == 0 && end_y > 2 ) || ( (pie & 010) != 0 && end_y < 7) ) {
wprintf(L"将必须在九宫格内\n");
return -1;
}
士(仕)的规则
if (abs(dx) != 1 || abs(dy) != 1) {
wprintf(L"仕(士)只能斜走一格\n");
return;
}
if(end_x < 3 || end_x > 5) {
wprintf(L"仕(士)不能离开“九宫格”\n");
return;
}
if((pie & RNONE) && end_y < 7) {
wprintf(L"仕(士)不能离开“九宫格”\n");
return;
}
if((pie & RNONE) == 0 && end_y > 2) {
wprintf(L"仕(士)不能离开“九宫格”\n");
return;
}
兵与卒
if(pie == BZU) {
if(start_y < 5 && (dy != 1 || dx != 0) ) {
wprintf(L"卒没有过河,只能前进一格\n");
return;
}
if(start_y >= 5 && (dy != 1 || dx != 0) && (dy != 0 || abs(dx) != 1)) {
wprintf(L"卒已过河只能移动一格\n");
return;
}
}
if(pie == RBI) {
if(start_y >= 5 && (dy != -1 || dx != 0) ) {
wprintf(L"兵没有过河,只能前进一格\n");
return;
}
if( (start_y < 5) && (dy != -1 || dx != 0) && (dy != 0 || abs(dx) != 1) ) {
wprintf(L"兵已过河只能移动一格\n");
return;
}
}
将帅不能见面的规则实现
移动棋子之前,备份相关位置的棋子信息
....
//备份相关位置的棋子信息
piece_t end_tmp = board[end_y][end_x];
piece_t start_tmp = board[start_y][start_x];
//移动棋子
board[end_y][end_x] = board[start_y][start_x];
board[start_y][start_x] = 0;
....
如果出现将、帅见面的情况,立刻恢复。
//检查将帅是否存在见面的情况
if(jiang_x == shuai_x) {
int i;
for(i=jiang_y+1; i < shuai_y; i++) {
if(board[i][jiang_x]) {
break;
}
}
if(i == shuai_y) {
board[end_y][end_x] = end_tmp;
board[start_y][start_x] = start_tmp;
wprintf(L"将帅不能见面\n");
return;
}
}