前言
通过前一段时间的C语言初阶的学习,已经初步掌握了C语言基本的语法知识。在学习完初阶的数组内容之后,就可以用数组简单做一些控制台小程序了,三子棋便是其中之一。
一、三子棋的逻辑?
三子棋每个人都不陌生,能一笔划下连续的三个棋子则为获胜的条件。我们规定当棋盘上的格子为空时用空格字符占位,玩家下的棋用字符 ‘*’ 提示,电脑下的棋用字符 ‘#’ 提示。先让玩家下棋,再让电脑下棋,并且每一步下完棋之后都要判断棋盘当前的状况:是否有哪一方获胜?有的话提示我们哪一方。游戏是否平局?当棋盘上没有空格字符且没有哪一方获胜的时候,为平局。游戏继续?当棋盘上有空格字符且没有哪一方获胜时,游戏继续。
二、文件说明。
game.h
我们将一切需要包含的头文件和需要定义的符号常量以及函数声明放在该文件下。其他的文件则包含这个头文件即可。但需要注意的是(至少楼主知道嘿嘿),自己定义的头文件用" “,C语言自带的头文件用<>以及” "都可但推荐用前者。
我们需要用到的头文件、符号常量以及函数声明如下:
#pragma once
#define ROW 5
#define COL 5
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//初始化棋盘
void Initboard(char board[ROW][COL], int row, int col);
//打印棋盘
void Printboard(char board[ROW][COL], int row, int col);
//玩家下棋
void Playermove(char board[ROW][COL], int row, int col);
//电脑下棋
void Computermove(char board[ROW][COL], int row, int col);
//判断是否有哪一方获胜
char Is_win(char board[ROW][COL], int row, int col);
test.c
在这个文件下,我们将调用整个函数的逻辑放在其中。
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void game()
{
char board[ROW][COL];
char ret = 0;
Initboard(board, ROW, COL);
Printboard(board, ROW, COL);
while (1)
{
Playermove(board, ROW, COL);
Printboard(board, ROW, COL);
ret = Is_win(board, ROW, COL); //每下一步棋,都要判断输赢
if (ret != 'C')
{
break;
}
Computermove(board, ROW, COL);
Printboard(board, ROW, COL);
Is_win(board, ROW, COL); //每下一步棋,都要判断输赢
ret = Is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
}
if (ret == '*') printf("玩家获胜!\n");
else if (ret == '#') printf("电脑获胜!\n");
else if(ret=='Q') printf("平局了!\n");
}
void menu()
{
printf("*************************\n");
printf("********1.to play********\n");
printf("********1.to quit********\n");
printf("*************************\n");
}
void test()
{
srand((unsigned int)time(NULL));
menu();
int input = 0;
do
{
printf("请选择:>(1 or 0)");
scanf("%d", &input);
switch (input)
{
case 1:
printf("游戏开始!\n");
game();
break;
case 0:
printf("游戏结束!\n");
break;
default:
printf("输入错误,请重新输入!\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
game.c
这个才是我们三子棋程序的重中之重,包含所有的函数定义。
其中的函数有:
1、初始化棋盘
2、打印棋盘
3、玩家下棋
4、电脑下棋
5、判断输赢(在判断输赢的过程中要穿插着判断期盼是否满的函数)
并且我们规定:
玩家获胜返回字符 ‘*’
电脑获胜返回字符 ‘#’
平局返回字符 ‘Q’
游戏继续返回 ‘C’
楼主有强迫症,所以判断输赢的函数写的具有一定的普遍性,使其不仅仅可以用来判断三子棋!
初始化棋盘
初始化棋盘就将期盼当中的所有格子初始化为空格字符即可。
//初始化棋盘,即将棋盘全部初始化为空格字符
void Initboard(char board[ROW][COL], int row, int col)
{
for(int i = 0;i < row;i++)
{
for (int j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
打印棋盘
打印棋盘稍微有点讲究,因为为了美观,其实也没什么的,简单的循环和逻辑判断就可以了。
//打印棋盘
void Printboard(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
if (i < row - 1 )
{
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
{
printf("|");
}
}
}
printf("\n");
}
}
玩家下棋
对于玩家下棋,我们要注意玩家输入的坐标是否合法,即坐标要大于等于一且不能超过棋盘的最大空间。否则我们就提示玩家输入非法,重新输入。
//玩家下棋
void Playermove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("玩家下棋:\n 请选择坐标:>");
scanf("%d %d", &x, &y);
if (x <= row && y <= col && x >= 1 && y >= 1)
{
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("坐标被占用,请重新输入!\n");
}
}
else
{
printf("坐标非法,请重新输入!\n");
}
}
}
对于玩家来说,玩家不一定是程序员,他可能不明白下标从0开始,所以我们需要提前考虑好。
电脑下棋
对于电脑来说,我们让其在棋盘上随机下棋,但它下的位置不能是被占用过的棋格。之前的猜数字游戏当中我们已经知道了如何随机生成数字,调用srand函数,参数用time函数传空指针,并将time(NULL)强制转化为unsigned int,之后调用rand函数即可随机生成数字了。因为srand函数只需要调用一次就行,我们就将其放在了test.c文件下的test函数里了。
//电脑下棋
void Computermove(char board[ROW][COL], int row, int col)
{
printf("电脑下棋:\n");
while (1)
{
int x = rand() % 3;
int y = rand() % 3;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
判断输赢
这个应该算是整个程序里面逻辑最复杂的了,因为楼主想写的有点普适性嘛,所以花了点功夫~。
char Is_win(char board[ROW][COL], int row, int col)
{
int count1 = 0;
int count2 = 0;
//判断行
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (board[i][j] == '*')
{
count1++;
}
else if (board[i][j] == '#')
{
count2++;
}
}
if (count1 == row)
{
return '*';
}
else if (count2 == row)
{
return '#';
}
count1 = 0;
count2 = 0;
}
//判断列(与判断行类似!)
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (board[j][i] == '*')
{
count1++;
}
else if (board[j][i] == '#')
{
count2++;
}
}
if (count1 == row)
{
return '*';
}
else if (count2 == row)
{
return '#';
}
count1 = 0;
count2 = 0;
}
//判断主对角线
for (int i = 0; i < row; i++)
{
if (board[i][i] == '*')
{
count1++;
}
else if (board[i][i] == '#')
{
count2++;
}
if (count1 == row)
{
return '*';
}
else if (count2 == row)
{
return '#';
}
}
count1 = 0;//注意这里不能放在for里面归为0了!
count2 = 0;
//判断副对角线
for (int i = 0 ; i < row; i++)
{
for (int j = row - 1; j >= 0; j--)
{
if (i + j == row - 1)
{
if (board[i][j] == '*')
{
count1++;
}
else if (board[i][j] == '#')
{
count2++;
}
}
}
if (count1 == row)
{
return '*';
}
else if (count2 == row)
{
return '#';
}
}
count1 = 0;
count2 = 0;
if (Is_full(board, row, col))
{
return 'Q';
}
return 'C';
}
我们定义两个整型变量count1和count2。
对于判断行和列的输赢,我们去遍历数组即可,如果碰到了*,我们让count++,如果碰到了#,我们让count2++,当count1或者count2为row时,我们返回count1对应的字符 ,count2同理。但要注意,我们判断的是行和列的或者#字符,当一行或者一列当最当中没有让count1或者count2为row时,要将count1和count2置为0,否则在判断下一行或者下一列时,count1或者count2保存的是上一行或者上一列的 * 或 # 字符的个数,就发生了逻辑上的错误。
明白了行和列的输赢判断逻辑后,主对角线和副对角线上的逻辑也就清楚了。
总结
三子棋的整个逻辑判断还是挺容易理解的,但整个程序还是有可以优化的地方,比如可以让电脑AI一点。但楼主知识尚未达到那种程度呜呜呜,输赢判断的逻辑也可以优化,但楼主暂时只能写到这种程度了,还要往后学习新的知识,希望大佬们能不吝赐教!虚心接受新知识和倾听别人的思维与想法应当是一个编程学习者的基本品质。
最后附上几张运行截图。