【C语言】超详细的----三子棋游戏---- 实现与解析

【C语言】超详细的----三子棋游戏---- 实现与解析

本次学习写一个三子棋软件,基本上用到了之前所学的所有知识,下面开始解析这个 三子棋游戏。

三子棋从下往上 的组成有:棋盘,我方棋,对方棋。三个部分组成。

棋盘的要素简述

首先棋盘是一个3*3的正方形(所以使用二维数组),每一个格子应该初始化成为 空格 ' ' ,以便放入棋子。

【C语言】超详细的----三子棋游戏---- 实现与解析

第二,方格间应该要有分隔线构成,但是要注意边界没有棋盘的周围没有分割线。

第三,定义棋子实现,我们用 * 来表示我方棋子,用 # 表示电脑棋子。

第四,定义胜利条件,胜利条件,同样的 符号成为 一条对角线,横排或竖排中三个符号成线。

完整游戏展示

在详细的实现游戏之间,我们先完整的看一下我们的代码。

这个游戏总用了 三个文件, game.h, game.c, test.c 。它们三个文件都用在一个工程之中,是不可分割的。

test.c 是主文件,存放着主函数 和 game.c 文件的函数调用。
game.h 是头文件,存放着 game.c 函数声明 和整个程序所引用的头文件常量
game.c 是用来存放函数主体 的文件

test.c

#include "game.h"

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


void game() {
  char board[ROW][COL] = { 0 };
  char ret = 0;
  Initboard(board, ROW, COL);//初始化数组为 ' '
  display_board(board, ROW, COL);//打印棋盘
    
  while (1) {
    player_move(board, ROW, COL);//玩家下棋
    display_board(board, ROW, COL);//下棋以后,也要打印棋盘
    ret = is_win(board,ROW,COL);//判断胜利条件
    if (ret != 'C') {
      break;
    }
    computer_move(board, ROW, COL);//电脑下棋
    display_board(board, ROW, COL);//下棋以后,也要打印棋盘
    ret = is_win(board,ROW,COL);
    if (ret != 'C') {
      break;
    }
  }
    
    //判断胜利条件
  if (ret == 'Q') {
    printf("平局!\n");
  }
  if (ret == '#') {
    printf("电脑赢!\n");
  }
  if (ret == '*') {
    printf("玩家赢!\n");
  }
}

//主函数
int main() {
  int input = 0;
  srand((unsigned int)time(NULL));
  menu();
  do {
    printf("请输入:(1 or 0) >  ");
    scanf("%d", &input);
    switch (input) {
    case 1:
      game();
      break;
    case 0:
      printf("退出游戏\n");
      break;
    default:
      printf("输入错误。请重新输入\n");
      break;
    }
  } while (input);
  return 0;
}

game.c (存放函数主体的文件)

#include "game.h"

//初始化棋盘为 ' '
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 display_board(char board[ROW][COL], int row, int col) {
  int i = 0;
  int j = 0;
  for (i = 0; i < row; i++) {
    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 player_move(char board[ROW][COL], int row, int col) {
  printf("玩家走:> ");
  int x = 0;
  int y = 0;
  while (1) {
    scanf("%d %d", &x, &y);
    if (x >= 1 && x <= row && y >= 1 && y <= col) {
      if (board[x - 1][y - 1] == ' ') {
        board[x - 1][y - 1] = '*';
        break;
      }
      else {
        printf("该坐标已被占用");
      }
    }
    else {
      printf("输入错误,请重新输入");
    }
  }
}

//电脑下棋布置。
void computer_move(char board[ROW][COL], int row, int col) {
  int x = 0;
  int y = 0;
  while (1) {
    x = rand() % row;
    y = rand() % col;
    if (board[x][y] == ' ') {
      board[x][y] = '#';
      break;
    }
  }
}

//is_full()函数是判断棋盘是否被占满的,因为只用在is_win()函数中,所以不需要在头文件game.h中声明。
int is_full(char board[ROW][COL], int row, int col) {
  for (int i = 0; i < row; i++) {
    for (int j = 0; j < col; j++) {
      if (board[i][j] == ' ') {
        return 0;
      }
    }
  }
  return 1;
}

//判断胜利条件
char is_win(char board[ROW][COL], int row, int col) {
  int i = 0;
  int j = 0;
  for (i = 0; i < row; i++) {
    if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][2] != ' ') {
      return board[i][1];
    }
  }
  for (j = 0; j < col; j++) {
    if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[2][j] != ' ') {
      return board[1][j];
    }
  }
  if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[2][2] != ' ') {
    return board[1][1];
  }
  if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[2][0] != ' ') {
    return board[1][1];
  }
    //如果棋盘满了,就是平局,is_full()函数是判断棋盘是否被占满的
  if (is_full(board, row ,col) == 1) {
    return 'Q';
  }
  return 'C';
}

game.h(头文件)

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define ROW 3
#define COL 3
void Initboard(char board[ROW][COL], int row, int col);

void display_board(char board[ROW][COL], int row, int col);

void player_move(char board[ROW][COL], int row, int col);

void computer_move(char board[ROW][COL], int row, int col);

char is_win(char board[ROW][COL], int row, int col);

详细解析

采用分段式解析,完整代码请看上面的代码。

主要解析game.c文件,也就是 函数主体文件!!!

**注意:本次解析的所有函数,都会在头文件 ** game.h 中声明。

在解析主体函数之前,我们先解析一下头文件

game.h

#define _CRT_SECURE_NO_WARNINGS 1

//这三个头文件是整个工程需要用到的
#include <stdio.h> //这是我们最熟悉的 输入输出函数。
#include <time.h>//这个函数表示用来得到系统的时间,本次主要用来配合 <stdlib.h>这个头文件来构成时间戳。
#include <stdlib.h>//主要用到其中的 rand函数 和srand函数,它们是随机数生成器。


#define ROW 3 //定义一个常量 ROW表示 行
#define COL 3 //定义一个常量 COL表示 列

//下面的这些函数都会在之后讲到
void Initboard(char board[ROW][COL], int row, int col);

void display_board(char board[ROW][COL], int row, int col);

void player_move(char board[ROW][COL], int row, int col);

void computer_move(char board[ROW][COL], int row, int col);

char is_win(char board[ROW][COL], int row, int col);

test.c

在我们主文件的game()函数中定义一个二维数组。

void game() {
  char board[ROW][COL] = { 0 };//定义二维数组,注意是使用 我们在头文件中引用的常量来定义 行和列 的,注意我们这里初始化的是 0 。
}

game.c

第一个函数:Initboard() 初始化棋盘坐标

初始化我们定义的二维数组board[][],将数组初始化为 空格 ' ';

//控制所有 行和列 将数组中 0 都初始化成为 ' ' 
void Initboard(char board[ROW][COL], int row, int col) {
//在这个之中定义了变量 i 和 j 分别控制 行 和 列。
  for (int i = 0; i < row; i++) {
    for (int j = 0; j < col; j++) {
      board[i][j] = ' ';  // 将数组中 0 都初始化成为空格 ' ' 
    }
  }
}

第二个函数: displaybaord() 打印棋盘

这个函数是实现对于新手来说,有些难以设计,所以我们 分为两步解析

第一步,先打印棋盘中的坐标位置

void displayboard(char board[ROW][COL] , int row,int col){
  for(int i = 0; i < row; i++){
    for(int j = 0 ; j < col ; j++){
      printf(" %c ",board[i][j]);//注意 %c 的两边都有一个空格。
    }
    printf("\n");//每打印完一行后 换行。
  }
}

第二步添加坐标位置之间的分割线

这也是完整版的棋盘了

void displayboard(char board[ROW][COL] , int row,int col){
  int i = 0;
  int j = 0;
  for(i = 0; i < row; i++)
  {
    for(j = 0 ; j < col ; j++)
    {
      printf(" %c ",board[i][j]);
     
      //下面是新加的代码;列的分割线
      if(j<col-1){  //因为每列之间才有分割线,所以必须是 j<col-1 这个条件
        printf("|");
      }
   
    }
    printf("\n");
    
    //下面这些都是新加的代码,打印每行的分割线
    if(i < row -1) // 因为只是每行之间有,所有条件是 i<row-1;
    {
      for( j= 0; j < col; j++) //打印分割线
      {
        printf("---");
        if (j < col - 1) //注意:行的分割线也有 “|” ;
        {
          printf("|");
        }
      } 
    }
    printf("\n");没打印一行后,换行;
  }
}

第三个函数:player_move 玩家下棋

这个函数是控制玩家的坐标走向的,比如你输入2 2 它就会在第二行第二列下棋。

【C语言】超详细的----三子棋游戏---- 实现与解析

但我们的这个函数的 坐标范围是 3*3 ,毕竟只是一个三字棋,不是 1 ~ 3的数字就会报出我们写的提示。

void player_move(char board[ROW][COL] , int row ,int col){
  int x = 0; //x轴坐标,也就是横坐标
  int y = 0;//y轴坐标,也就是纵坐标
  
  //设立循环, 如果没有正确输入 坐标值,就会一直循环。输入无误的坐标就嫩跳出循环。
  while(1)
  {
   printf("玩家下棋:》 ");
   scanf("%d %d",&x,&y);
   if(x >= 1 && x <= row && y >= 1 && y <= 3)//限定x 和 y 的范围,在这个范围才会生效。
   { //数组的下标是0~2,所以我们的x 和 y 都减去 1 后就能和board[][]数组的下标重合.
    if (board[x-1][y-1] == ' ') //如果我们输入的坐标没有被电脑下棋过,我们就可以用 '*' 替换,代表我方下棋。
    {
      board[x-1][y-1] = '*';
      printf("%c",board[x-1][y-1]);
      break;
    }
    else //如果我们输入的坐标,不是‘ ’,会说明别机器人下了。
    {
      printf("该坐标已被占用,请重新输入坐标");
    }
  }
  else//输入的横纵坐标值大于或小于 我们限定的坐标值了。
  {
    printf("非法坐标");
  }
 }
}

第四个函数:computer_move() 电脑下棋

这个函数需要用到 库函数的<stdlib.h> 中的 randsrandrandsrand 是随机数生成器;还有<time.h> 中的 time函数 ,time函数 是用于 srand函数中的时间戳。

void computer_move(char board[ROW][COL],int row,int col){
   int x = 0;//电脑下棋的坐标,因为是电脑下棋,所以不用坐标-1。
   int y = 0;
   printf("电脑下棋 ")
   while(1){//与上面的函数同理
     x = rand()%row; //rand是随机数生成器,%row可以限制随机数生成的范围,取余row,也就是取余3,,所以随机数的生成范围就是0~2. 
     y = rand()%col;//%col可以限制随机数生成的范围,取余col,也就是取余3,,所以随机数的生成范围就是0~2.
     if (board[x][y] == ' '){
       board[x][y] = '#'; //电脑下棋的坐标就是 '#'
       break; //如果电脑下棋成功,就退出循环
     }
   }
   printf("\n");
}

第五个函数:in_win() 判断输赢

这个函数是用来判断输赢的,相同字符连成一排,或者一列,又或者对角线,则赢。

而我们可以用这个函数的返回值,在主函数中判断输赢,所以这个函数的返回值就不是void 了,而应该是char

如果返回值是 '*' 则玩家赢。

返回值是'#' 电脑赢

返回值是 'Q' 平局,在判断平局中我们也创造了一个变量is_full()

返回值是 'C' 继续下棋,棋盘未满,也还没人胜利。

int is_full(char board[ROW][COL],int row,int col){
  for(int i = 0; i < row; i++){
    for(int j = 0; j < col; j++){
      if(board[i][j] == ' '){ //遍历整个二维数组,如果数组中有' ' 就返回0,说明棋盘没有被占满。
        return 0;
      }
    }
  }
  return 1;//如果没有整个二维数组中没有了' ' ,就返回1,说明棋盘被被占满了。
}

char is_win(char board[ROW][COL] ,int row,int col){
  for(int i = 0; i < row ; i++){
    if(baord[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][2] != ' '){
      return board[i][1]; //如果一行里面的字符相同,就返回这个字符
    }
    for (j = 0; j < col; j++) {
      if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[2][j] != ' ') {
      return board[1][j];//如果一列里面的字符相同,就返回这个字符
      }
    }
    if(board[0][0] == board[1][1] && baord[1][1] == board[2][2] && board[2][2] != ' '){
      return board[1][1]; //对角线里面的字符相同,就返回那个字符
    }
    if(board[0][2] == board[1][1] && baord[1][1] == board[2][0] && board[2][0] != ' '){
      return board[1][1];//对角线里面的字符相同,就返回那个字符
    }
    if(is_full(board[ROW][COL],row,col) == 1){//注意 is_win是个判断棋盘是否被全部占满的函数,但是,不用在game.h头文件中声明。
      return 'Q';  //如果is_win返回的值是1,说明棋盘已经被占满,是平局。
    }
    return 'C';//返回值是 'C' 继续下棋,棋盘未满,也还没人胜利。
  }
}

以上是我在game.c中的所有函数解析,但是伙计们别忘了在game.h中声明,不然在主函数文件中是用不了的。

虽然我们只是解析了game.c中的函数,其他两个文件只是简略带过,但当你能看懂 game.c的时候,就说明能看懂其他两个文件了。

结语

至此我们的三子棋解析也进入了尾声,可能代码中有些许错误,欢迎大家指正。

这个程序并不算难,但基本也是我们所写的第一个程序,本文主要是提供一些鄙人的拙见,如果能带给大家一点点帮助,是我莫大的荣幸。

很期待下次见面,祝大家万事胜意!!!

上一篇:C语言小游戏之·手把手教你做三子棋(简单版)


下一篇:单词搜索