使用C语言实现中国象棋每个棋子规则的算法

使用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 
李
使用注意事项
  1. 乱码问题:

宽字节类型占用更大的空间,可以表示汉字。但是却无法正常的显示汉字。

运行以下代码会出来乱码:

#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;
}
charwchar_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"┗━━┻━━┻━━╩━━┻━━┻━━╩━━┻━━┻━━┛"

获取命令

制定命令格式

来自专业人士的习惯

来自象棋爱好者的习惯:

車一进一
马23
炮八平五
。。。
十进制的巧合

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,行数范围和输入的时候一致。

以初始的位置为例:

输入的位置信息7275
实际的数组下标[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;
}
代码迭代
  1. 枚举类型用typedef定义成数据类型
  2. 增加全局变量player表示【玩家】
  3. 使用控制码显示红色字体,增加输出行号、列号
#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;
}
代码迭代:
  1. ChncGetCmd函数增加cmd参数
  2. 把参数传递给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;
}
只能移动自己的棋子

实现思路分析:

  1. 所有红方棋子都大于010(八进制),所有黑方都小于010(八进制)。
001002003004005006007
011012013014015016017
  1. player的值介于010和0之间
player==RNONE010红方
player==BNONE000黑方

实现思路:

先让棋子和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;
	}

三、判断输赢

实现思路:

将帅失其一,定输赢。

  1. 时刻关注将、帅的位置变化,如果被杀。即宣告游戏结束。
  2. 判断将、帅哪个不存在,判断输赢
  3. 中途退出,表示游戏中断。
实现步骤:
  1. 增加全局变量:jiang、shuai
int jiang_x = 4, jiang_y = 0;
int shuai_x = 4, shuai_y = 9;
  1. 移动棋子以后更新将或帅的位置
	//更新将帅的位置
	if(pie == BJI) { //被移动的是将
		jiang_y = end_y;
		jiang_x = end_x;
	}
	if(pie == RSU) { //被移动的将帅
		shuai_y = end_y;
		shuai_x = end_x;
	}

  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;
	}

  1. 判断并退出游戏
.....
/*
 * 返回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"); //换行
	}
}

规则实现思路

每一个兵种都有不同的规则

帅(将):
- 只能在九宫格内移动
- 每次只能横向或纵向移动一格
- 不能与对方的将(帅)在同一直线上相对,中间无子

士(仕):
- 只能在九宫格内移动
- 每次沿斜线移动一格

象(相):
- "象走田",且不能过河
- 每次沿斜线移动两格
- 不能越过中间的棋子("堵象眼")

马:
- "马走日"形状移动
- 横向两格加纵向一格,或纵向两格加横向一格
- 不能越过直接阻挡的棋子("别马腿")

車:
- “車走一条线”
- 可以横向或纵向移动任意格数
- 不能越过其他棋子

炮:
- “炮翻山”
- 移动时与车相同,可横向或纵向任意格数
- 吃子时必须跳过一个棋子(敌我不限)

兵(卒):
- 未过河时只能向前移动一格
- 过河后可以向前或向左右移动一格
- 不能后退

将、帅不能见面

规则整体的特点

总的来说,所有的规则都是对**棋子位置的约束**,除此以外还有一些其特点:

  1. 红黑双方对称相反
  2. 兵种的特殊属性
  3. 将、帅不能见面

兵种规则的实现

获取位置关系,建立规则判断框架

	//获取位置关系 ,暂时不用所以先注释掉
	//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;
		}
	}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值