【项目】三子棋小游戏(C语言)

【项目】—— 三子棋(C语言)

一、游戏介绍

三子棋就是三颗棋子连成一线就赢的棋,非常简单,我们使用C语言来实现一个人机对战的三子棋。虽然实现的是三子棋,但是咱们的三子棋具有多子棋的逻辑,只要修改头文件的常量就能实现任意子棋。

三子棋的功能

  1. 游戏开始界面有一个 R × C R\times C R×C的棋盘
  2. 玩家可以输入坐标落子
  3. 电脑在玩家落子后自动落子
  4. 每次落子后判断输赢,三颗棋子连成一线则赢

二、设计思路与文件构成

1. 设计思路

  1. 创建一个 r o w × c o l row \times col row×col的二维数组
  2. 玩家输入落子坐标,在该坐标位置赋为字符*
  3. 电脑随机在未落子的位置进行落子,在该位置赋值为字符#
  4. 每次落子判断输赢,遍历数组,若是有三子一线的情况则对局结束

2. 文件构成

  1. game.h:头文件,用于引入头文件,声明常量和声明函数
  2. game.c:源文件,用于实现游戏的功能
  3. test.c:测试文件,用于测试游戏代码的逻辑,并写有main函数启动程序

三、头文件

修改该文件中的ROWCOL常量就可以修改棋盘的大小,修改WIN常量可改变胜利的条件,完成WIN子棋的改变

#pragma once
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <Windows.h>

#define ROW 5	//棋盘行数
#define COL 5	//棋盘宽度
#define WIN 3	//胜利条件


void menu();													//菜单
void Init(char board[ROW][COL], int row, int col);					//初始化棋盘
void DisplayBoard(char board[ROW][COL], int row, int col);			//打印棋盘
void PlayMove(char board[ROW][COL], int row, int col);				//玩家落子
void ComputerMoveAndPrint(char board[ROW][COL], int row, int col);	//电脑落子
char IsWin(char board[ROW][COL], int row, int col);					//判断输赢

四、代码实现

1. 初始化

将棋盘的所有字符初始化为空格

void Init(char board[ROW][COL], const int row, const int col)
{
    
    
	int i = 0;
	for (i = 0; i < row; i++)
	{
    
    
		int j = 0;
		for (j = 0; j < col; j++)
		{
    
    
			board[i][j] = ' ';
		}
	}
}

2. 打印棋盘

打印一个 r o w × c o l row\times col row×col的棋盘,并输出棋盘中的字符

void DisplayBoard(char board[ROW][COL], const int row, const int col)
{
    
    
	//打印列坐标
	printf("   ");
	for (int j = 0; j < col; j++)
	{
    
    
		printf(" %d  ", j + 1);
	}
	printf("\n");

	int i = 0;
	for (i = 0; i < row; i++)
	{
    
    
		printf("%d- ", i + 1);		//打印行坐标
		int j = 0;
		for (j = 0; j < col; j++)
		{
    
    
			if (j < col - 1)
			{
    
    
				printf(" %c |", board[i][j]);
			}
			else
			{
    
    
				printf(" %c \n", board[i][j]);
			}
		}

		if (i < row - 1)
		{
    
    
			printf("   ");
			for (j = 0; j < col; j++)
			{
    
    
				if (j < col - 1)
				{
    
    
					printf("---|");
				}
				else
				{
    
    
					printf("---\n");
				}
			}	
		}
	}
}

3. 玩家落子

玩家输入坐标,判断坐标的正确性和是否被占用,若是没有将该位置赋值为*

void PlayMove(char board[ROW][COL], int row, int col)
{
    
    
	printf("玩家下棋:>\n");
	int x = 0, y = 0;
	while (1)
	{
    
    
		printf("请输入坐标(用空格隔开):>");
		scanf("%d %d", &x, &y);
		if ((x >= 1) && (x <= row) && (y >= 1) && (y <= col))
		{
    
    
			x--;
			y--;
			if (board[x][y] != ' ')
			{
    
    
				printf("坐标被占用\n");
			}
			else
			{
    
    
				board[x][y] = '*';
				break;
			}
		}
		else
		{
    
    
			printf("坐标输入有误,请重新输入");
		}
	}
	
}

4. 电脑落子

电脑随机生成一个坐标,将该位置赋值为#,并打印棋盘,并打印电脑下棋坐标

void ComputerMoveAndPrint(char board[ROW][COL], int row, int col)
{
    
    
	int x = 0, y = 0;
	while (1)
	{
    
    
		x = rand() % row;
		y = rand() % col;

		if (' ' == board[x][y])
		{
    
    
			board[x][y] = '#';
			DisplayBoard(board, ROW, COL);
			printf("电脑下棋>%d %d\n", x + 1, y + 1);
			break;
		}
	}
}

5. 判断输赢

  1. 三子棋的胜利条件是某一行、某一列或者对角线上实现三子一线。若是将三子棋写死为固定三子,则可以使用穷举法将所有条件判断完。但是作为一个有理想有抱负没水平的学者,我们的三子棋要具备实现多子棋的拓展性。
  2. 判断输赢的方式分开为判断行赢、列赢和对角线赢。
  3. 若是棋盘满了还没有连成一线,则平局。
  4. 我们使用遍历二维数组,计数连续的相同棋子的方式实现。

5.1 判断行赢

​ 遍历每一行,使用conut记录每一行指定棋子c的个数,边记录边判断连续的个数是否达到胜利条件WIN的个数,若是中途遇到其他棋子则将count置为0

_Bool XofV(char board[ROW][COL], int row, int col, char c)
{
    
    
	int i = 0;
	for (i = 0; i < row; i++)
	{
    
    
		int count = 0;
		int j = 0;
		for (j = 0; j < col; j++)
		{
    
    
			if (c == board[i][j])
			{
    
    
				count++;
				if (count >= WIN)	//判断是否赢
				{
    
    
					return 1;
				}
			}
			else
			{
    
    
				count = 0;
			}
		}
	}

	return 0;
}

5.2 判断列赢

根判断行赢的逻辑一样,所以我们直接将行列交换完成实现

_Bool YofV(char board[ROW][COL], int row, int col, char c)
{
    
    
	int i = 0;
	for (i = 0; i < col; i++)
	{
    
    
		int count = 0;
		int j = 0;
		for (j = 0; j < row; j++)
		{
    
    
			if (c == board[j][i])
			{
    
    
				count++;
				if (count >= WIN)
				{
    
    
					return 1;
				}
			}
			else
			{
    
    
				count = 0;
			}
		}
	}

	return 0;
}

5.3 判断对角线赢

​ 正对角和斜对角都要遍历,每个遍历分为2个步骤,分别是上三角和下三角

_Bool XandYofV(char board[ROW][COL], int row, int col, char c)
{
    
    
	//正对角
	int i = 0;
	for (i = 0; i < row; i++)
	{
    
    
		int count = 0;
		int j = 0;
		int x = i;
		for (j = 0; j < col && x < row; j++, x++)
		{
    
    
			if (board[x][j] == c)
			{
    
    
				count++;
				if (count >= WIN)
				{
    
    
					return 1;
				}
			}
			else
			{
    
    
				count = 0;
			}
			
		}
	}

	int j = 0;
	for (j = 1; j < col; j++)	//上三角已经把对角线遍历过了,所以下三角不用遍历0对角线
	{
    
    
		int count = 0;
		int i = 0;
		int y = j;
		for (i = 0; i < row && y < col; i++, y++)
		{
    
    
			if (board[i][y] == c)
			{
    
    
				count++;
				if (count >= WIN)
				{
    
    
					return 1;
				}
			}
			else
			{
    
    
				count = 0;
			}
		}
	}

	//斜对角
	for (j = col-1; j >= 0; j--)
	{
    
    
		int count = 0;
		int y = j;
		int i = 0;
		for (i = 0; y >= 0 && i < row; y--, i++)
		{
    
    
			if (board[i][y] == c)
			{
    
    
				count++;
				if (count >= WIN)
				{
    
    
					return 1;
				}
			}
			else
			{
    
    
				count = 0;
			}

		}
	}

	for (i = 1; i < row; i++)
	{
    
    
		int count = 0;
		int x = i;
		int j = col - 1;
		for (j = col - 1; j >= 0 && x < row; x++, j--)
		{
    
    
			if (board[x][j] == c)
			{
    
    
				count++;
				if (count >= WIN)
				{
    
    
					return 1;
				}
			}
			else
			{
    
    
				count = 0;
			}

		}
	}

	return 0;
}

5.4 判断棋盘满

​ 遍历棋盘,若是没有空格则棋盘满了

_Bool IsFull(char board[ROW][COL], int row, int col)
{
    
    
	int i = 0;
	for (i = 0; i < row; i++)
	{
    
    
		int j = 0;
		for (j = 0; j < col; j++)
		{
    
    
			if (' ' == board[i][j])
			{
    
    
				return 0;
				break;
			}
		}
	}
	return 1;
}

5.5 判断棋子输赢

将玩家或电脑棋子表示的字符作为参数,比较行、列、对角线,若是都没有赢的,判断棋盘满了,则为平局

  1. 玩家赢返回*
  2. 电脑赢返回#
  3. 棋盘满了为平局,返回q
  4. 若是没有人赢且棋盘没满则继续,返回c
char IsWin(char board[ROW][COL], int row, int col)
{
    
    
    //玩家赢
	if (XofV(board, row, col, '*') || YofV(board, row, col, '*') || XandYofV(board, row, col, '*'))
	{
    
    
		return '*';
	}
    //电脑赢
	else if (XofV(board, row, col, '#') || YofV(board, row, col, '#') || XandYofV(board, row, col, '#'))
	{
    
    
		return '#';
	}
    //棋盘满
	else if(IsFull(board, row, col))
	{
    
    
		return 'q';
	}
    //对局继续
	else
	{
    
    
		return 'c';
	}
}

五、测试文件

1. 菜单

让用户选择的界面

void menu()
{
    
    
	printf("******************************\n");
	printf("********    1. play    *******\n");
	printf("********    0. exit    *******\n");
	printf("******************************\n");
}

2. 游戏实现

void game()
{
    
    
	//创建棋盘
	char board[ROW][COL] = {
    
     0 };
	char win = ' ';

	Init(board, ROW, COL);
	DisplayBoard(board, ROW, COL);

	//双方对局
	while (1)
	{
    
    
		PlayMove(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		win = IsWin(board, ROW, COL);
		if (win != 'c')
		{
    
    
			break;
		}
		
		system("cls");
		ComputerMoveAndPrint(board, ROW, COL);
		win = IsWin(board, ROW, COL);
		if (win != 'c')
		{
    
    
			break;
		}
	}
	
	//判定结果
	system("cls");
	DisplayBoard(board, ROW, COL);
	if ('*' == win)
	{
    
    
		printf("玩家胜利\n");
	}
	else if ('#' == win)
	{
    
    
		printf("电脑胜利\n");
	}
	else
	{
    
    
		printf("平局\n");
	}
}

3. main函数

启动游戏,并调用菜单供用户选择参考

int main(void)
{
    
    
	int input = 0;
	srand(time(NULL));
	do
	{
    
    
		menu();
		printf("请操作>");
		scanf("%d", &input);

		switch (input)
		{
    
    
		case 1:
			system("cls");
			game();
			break;

		case 0:
			printf("欢迎下次光临~\n");
			break;

		default:
			printf("数字输入错误\n");
			break;
		}

	} while (0 != input);

	return 0;
}

六、完整代码

代码存储于gitee中:点击查看完整代码

猜你喜欢

转载自blog.csdn.net/weixin_52811588/article/details/126695277