22. 五子棋小游戏

文章目录

1. 概要

🔊 JackQiao 对 米粒 说:"今天咱们玩个五子棋小游戏,电脑与你轮流在一个 n×n 的网格上放置棋子(XO),网格由你输入的正整数n决定,谁先连成五个相同的棋子(横、竖或斜)即获胜。如果棋盘被填满且无人获胜,则游戏以平局结束"。
😇 米粒想到:

✅ 让用户输入棋盘的大小。

✅ 创建并初始化棋盘。

✅ 开始游戏循环,交替让玩家和电脑下棋。

✅ 每次下完棋后检查是否有人赢了或者棋盘是否满了。

✅ 如果有玩家赢了或棋盘满了,则结束游戏。


2. 整体架构流程

2.1. 包含的库

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

📶 stdio.h:用于输入输出操作,比如打印信息和读取用户输入。

📶stdlib.h:包含了一些有用的函数,比如随机数生成。

📶time.h:用于获取当前时间,这里是为了设置随机数种子。

2.2. 定义符号常量

复制代码
#define EMPTY ' '
#define PLAYER1 'X'  // 黑棋
#define PLAYER2 'O'  // 白棋

📶 EMPTY:表示空格,即没有棋子的地方。PLAYER1 和 PLAYER2 :分别代表两个玩家的棋子,一个是**'X',另一个是'O'**。

2.3. 初始化棋盘

复制代码
void init_board(char board[], int n) 
{
    for (int i = 0; i < n * n; i++)
 {
        board[i] = EMPTY;
    }
}

📶 这个函数用来初始化棋盘。它会把棋盘上的所有位置都设为空格(即没有棋子)。n 是棋盘的大小,比如如果n=5,那么就是一个5x5的棋盘。

2.4. 打印棋盘

复制代码
void print_board(char board[], int n) 
{
    // 打印列号
    printf("   ");
    for (int i = 0; i < n; i++) 
    {
        printf("%2d  ", i);
    }
    printf("\n");

    // 打印上边框
    printf("  +");
    for (int i = 0; i < n; i++) 
    {
        printf("---+");
    }
    printf("\n");

    // 打印棋盘内容及行号
    for (int i = 0; i < n; i++) 
    {
        printf("%2d|", i);
        for (int j = 0; j < n; j++) 
        {
            printf(" %c |", board[i * n + j]);
        }
        printf("\n");

        // 打印行间的分隔线
        printf("  +");
        for (int j = 0; j < n; j++) 
        {
            printf("---+");
        }
        printf("\n");
    }
}
✅ 效果如下 :
2.4.1. 打印列号
复制代码
printf("   ");
for (int i = 0; i < n; i++) 
{
    printf("%2d  ", i);
}
printf("\n");

📶**printf(" ");:**这行代码打印了三个空格,用于对齐后续的行号。

📶for (int i = 0; i < n; i++) { ... }: 这个循环遍历棋盘的每一列,并为每一列打印一个编号。📶printf("%2d ", i); :这里使用了格式化字符串 %2d 来确保每个数字占用至少两个字符的空间(如果数字是一位数,则前面会补一个空格),并在其后加上两个空格以增加可读性**。** 📶printf("\n");:打印完所有列号后,换行以便开始打印棋盘的上边框。

2.4.2. 打印上边框
复制代码
printf("  +");
for (int i = 0; i < n; i++)
{
    printf("---+");
}
printf("\n");

📶printf(" +"); :打印两个空格和一个加号**(+),** 作为左边界的起点**。**

📶for (int i = 0; i < n; i++) { ... } :这个循环遍历棋盘的每一列,并为每一列打印一个由三个连字符**(---)** 组成的分隔线,最后用一个加号结束**。**

📶printf("\n");: 打印完上边框后换行,准备打印棋盘内容**。**

2.4.3. 打印棋盘内容及行号
复制代码
for (int i = 0; i < n; i++) 
{
    printf("%2d|", i);
    for (int j = 0; j < n; j++) 
{
        printf(" %c |", board[i * n + j]);
    }
    printf("\n");

    // 打印行间的分隔线
    printf("  +");
    for (int j = 0; j < n; j++) 
{
        printf("---+");
    }
    printf("\n");
}

📶 行号与棋盘内容

  • **for (int i = 0; i < n; i++) { ... }:**这个外层循环遍历棋盘的每一行。

  • printf("%2d|", i); :为当前行打印行号(同样使用 %2d 确保两位宽) 然后打印竖线**(|)** 表示该行的开始**。**

  • 内层循环**for (int j = 0; j < n; j++) { ... }**遍历每一行中的每一个单元格:

  • printf(" %c |", board[i * n + j]);:打印当前单元格的内容(即棋子或空格),并用竖线将其与其他单元格分隔开。
    📶 行间分隔线

  • 每一行内容打印完毕后,紧接着打印该行下方的分隔线。

  • 这个部分与打印上边框的部分几乎相同,只是它位于每两行之间,而不是顶部**。**

2.5. 检查位置是否为空

复制代码
int is_valid_move(char board[], int n, int x, int y) 
{
    if (x < 0 || x >= n || y < 0 || y >= n) return 0;
    return board[x * n + y] == EMPTY;
}

📶 这个函数检查给定的位置 **(x, y)**是否在棋盘范围内并且是否为空。如果是的话,返回 1 表示可以下棋;否则返回 0

2.6. 放置棋子

复制代码
void make_move(char board[], int n, int x, int y, char player) 
{
    board[x * n + y] = player;
}

📶 这个函数会在指定的位置 (x, y) 上放置玩家的棋子。

2.7. 检查是否有玩家获胜

复制代码
int check_win(char board[], int n, int x, int y, char player) 
{
    int directions[8][2] = { {0, 1}, {1, 0}, {1, 1}, {1, -1}, {0, -1}, {-1, 0}, {-1, -1}, {-1, 1} };

    for (int d = 0; d < 8; d++) 
    {
        int count = 1;
        for (int i = 1; i < 5; i++) 
        {
            int nx = x + directions[d][0] * i;
            int ny = y + directions[d][1] * i;
            if (nx >= 0 && nx < n && ny >= 0 && ny < n && board[nx * n + ny] == player) 
            {
                count++;
            }
            else 
            {
                break;
            }
        }
        for (int i = 1; i < 5; i++) 
        {
            int nx = x - directions[d][0] * i;
            int ny = y - directions[d][1] * i;
            if (nx >= 0 && nx < n && ny >= 0 && ny < n && board[nx * n + ny] == player) 
            {
                count++;
            }
            else 
            {
                break;
            }
        }
        if (count >= 5) return 1;
    }
    return 0;
}
2.7.1. 定义搜索方向
复制代码
int directions[8][2] = { {0, 1}, {1, 0}, {1, 1}, {1, -1}, {0, -1}, {-1, 0}, {-1, -1}, {-1, 1} };
  • 📶 directions 是一个二维数组,存储了八个方向的增量值。
  • 📶 **{0, 1}**表示向右移动(横向)。
  • 📶 {1, 0} 表示向下移动(纵向)。
  • 📶**{1, 1}**表示右下对角线。
  • 📶**{1, -1}**表示左下对角线。
  • 📶**{0, -1}**表示向左移动(横向)。
  • 📶**{-1, 0}**表示向上移动(纵向)。
  • 📶**{-1, -1}**表示左上对角线。
  • 📶**{-1, 1}**表示右上对角线。
2.7.2. 检查方向
复制代码
for (int i = 1; i < 5; i++)
 {
    int nx = x + directions[d][0] * i;
    int ny = y + directions[d][1] * i;
    if (nx >= 0 && nx < n && ny >= 0 && ny < n && board[nx * n + ny] == player) 
{
        count++;
    } else 
{
        break;
    }
}
  • 内层第一个循环沿着当前方向的正方向(即增加方向)检查最多4个位置。
  • nxny 计算了在当前位置 (x, y) 沿着当前方向移动 i 步后的新坐标。
  • 如果新坐标在棋盘范围内,并且该位置的棋子与当前玩家相同,则增加计数器 count
  • 如果遇到边界或不同棋子,则停止检查该方向

2.8. 电脑玩家随机移动

复制代码
void make_computer_move(char board[], int n, char player) 
{
    int x, y;
    do 
{
        x = rand() % n;
        y = rand() % n;
    } while (!is_valid_move(board, n, x, y));

    printf("电脑玩家 %c 下在 (%d, %d)\n", player, x, y);
    make_move(board, n, x, y, player);
}

2.9. 主函数

复制代码
int main() 
{
    int n;
    printf("请输入棋盘大小 n: ");
    scanf("%d", &n);

    char* board = (char*)malloc(n * n * sizeof(char));
    if (!board) 
    {
        printf("内存分配失败\n");
        return 1;
    }

    srand(time(NULL));  // 初始化随机数种子

    init_board(board, n);

    char current_player = PLAYER1;
    int x, y;

    while (1) 
    {
        print_board(board, n);

        if (current_player == PLAYER1) 
        {
            printf("玩家 %c,请输入坐标 (x y): ", current_player);
            scanf("%d %d", &x, &y);

            if (!is_valid_move(board, n, x, y)) 
            {
                printf("无效的移动,请重新输入。\n");
                continue;
            }
            make_move(board, n, x, y, current_player);
        }
        else 
        {
            make_computer_move(board, n, current_player);
        }

        if (check_win(board, n, x, y, current_player)) 
        {
            print_board(board, n);
            printf("玩家 %c 获胜!\n", current_player);
            free(board);
            return 0;
        }

        if (is_board_full(board, n)) 
        {
            print_board(board, n);
            printf("平局!\n");
            free(board);
            return 0;
        }

        current_player = (current_player == PLAYER1) ? PLAYER2 : PLAYER1;
    }

    free(board);
    return 0;
}

2.10. 程序运行如下:


3. 技术名词解释

🔔 directions 是一个二维数组,存储了八个方向的增量值。
  • 📶 directions 是一个二维数组,存储了八个方向的增量值。
  • 📶 **{0, 1}**表示向右移动(横向)。
  • 📶 {1, 0} 表示向下移动(纵向)。
  • 📶**{1, 1}**表示右下对角线。
  • 📶**{1, -1}**表示左下对角线。
  • 📶**{0, -1}**表示向左移动(横向)。
  • 📶**{-1, 0}**表示向上移动(纵向)。
  • 📶**{-1, -1}**表示左上对角线。
  • 📶**{-1, 1}**表示右上对角线。

4. 技术细节

检查相应方

复制代码
for (int i = 1; i < 5; i++)
 {
    int nx = x - directions[d][0] * i;
    int ny = y - directions[d][1] * i;
    if (nx >= 0 && nx < n && ny >= 0 && ny < n && board[nx * n + ny] == player)
 {
        count++;
    } else 
{
        break;
    }
}
✅ 整体代码
复制代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define EMPTY ' '
#define PLAYER1 'X'  // 黑棋
#define PLAYER2 'O'  // 白棋

// 初始化棋盘
void init_board(char board[], int n) 
{
    for (int i = 0; i < n * n; i++)
    {
        board[i] = EMPTY;
    }
}

// 打印棋盘
void print_board(char board[], int n) 
{
    // 打印列号
    printf("   ");
    for (int i = 0; i < n; i++) 
    {
        printf("%2d  ", i);
    }
    printf("\n");

    // 打印上边框
    printf("  +");
    for (int i = 0; i < n; i++) 
    {
        printf("---+");
    }
    printf("\n");

    // 打印棋盘内容及行号
    for (int i = 0; i < n; i++) 
    {
        printf("%2d|", i);
        for (int j = 0; j < n; j++) 
        {
            printf(" %c |", board[i * n + j]);
        }
        printf("\n");

        // 打印行间的分隔线
        printf("  +");
        for (int j = 0; j < n; j++) 
        {
            printf("---+");
        }
        printf("\n");
    }
}

// 检查位置是否为空
int is_valid_move(char board[], int n, int x, int y) 
{
    if (x < 0 || x >= n || y < 0 || y >= n) return 0;
    return board[x * n + y] == EMPTY;
}

// 放置棋子
void make_move(char board[], int n, int x, int y, char player) 
{
    board[x * n + y] = player;
}

// 检查是否有玩家获胜
int check_win(char board[], int n, int x, int y, char player) 
{
    int directions[8][2] = { {0, 1}, {1, 0}, {1, 1}, {1, -1}, {0, -1}, {-1, 0}, {-1, -1}, {-1, 1} };

    for (int d = 0; d < 8; d++) 
    {
        int count = 1;
        for (int i = 1; i < 5; i++) 
        {
            int nx = x + directions[d][0] * i;
            int ny = y + directions[d][1] * i;
            if (nx >= 0 && nx < n && ny >= 0 && ny < n && board[nx * n + ny] == player) 
            {
                count++;
            }
            else 
            {
                break;
            }
        }
        for (int i = 1; i < 5; i++) 
        {
            int nx = x - directions[d][0] * i;
            int ny = y - directions[d][1] * i;
            if (nx >= 0 && nx < n && ny >= 0 && ny < n && board[nx * n + ny] == player) 
            {
                count++;
            }
            else 
            {
                break;
            }
        }
        if (count >= 5) return 1;
    }
    return 0;
}

// 检查棋盘是否已满
int is_board_full(char board[], int n) 
{
    for (int i = 0; i < n * n; i++) 
    {
        if (board[i] == EMPTY) return 0;
    }
    return 1;
}

// 电脑玩家随机移动
void make_computer_move(char board[], int n, char player) 
{
    int x, y;
    do 
    {
        x = rand() % n;
        y = rand() % n;
    } 
    while (!is_valid_move(board, n, x, y));

    printf("电脑玩家 %c 下在 (%d, %d)\n", player, x, y);
    make_move(board, n, x, y, player);
}

int main() 
{
    int n;
    printf("请输入棋盘大小 n: ");
    scanf("%d", &n);

    char* board = (char*)malloc(n * n * sizeof(char));
    if (!board) 
    {
        printf("内存分配失败\n");
        return 1;
    }

    srand(time(NULL));  // 初始化随机数种子

    init_board(board, n);

    char current_player = PLAYER1;
    int x, y;

    while (1) 
    {
        print_board(board, n);

        if (current_player == PLAYER1) 
        {
            printf("玩家 %c,请输入坐标 (x y): ", current_player);
            scanf("%d %d", &x, &y);

            if (!is_valid_move(board, n, x, y)) 
            {
                printf("无效的移动,请重新输入。\n");
                continue;
            }
            make_move(board, n, x, y, current_player);
        }
        else 
        {
            make_computer_move(board, n, current_player);
        }

        if (check_win(board, n, x, y, current_player)) 
        {
            print_board(board, n);
            printf("玩家 %c 获胜!\n", current_player);
            free(board);
            return 0;
        }

        if (is_board_full(board, n)) 
        {
            print_board(board, n);
            printf("平局!\n");
            free(board);
            return 0;
        }

        current_player = (current_player == PLAYER1) ? PLAYER2 : PLAYER1;
    }

    free(board);
    return 0;
}

5. 小结

✅ 针对该小游戏,米粒做出以下知识点总结:

🌷 数据结构:使用一维数组**char board[]来表示二维棋盘,通过索引计算[x * n + y]**来访问特定位置。

🌷 算法逻辑:通过遍历八个方向来检查每次落子后是否形成五子连珠,使用方向增量数组 **directions[8][2]**来简化不同方向上的搜索。

🌷 用户交互:程序包含输入输出功能,如读取用户输入坐标、打印当前棋盘状态,并处理非法输入和游戏结束条件(胜利或平局)。

相关推荐
belldeep1 小时前
如何阅读、学习 Tcc (Tiny C Compiler) 源代码?如何解析 Tcc 源代码?
c语言·开发语言
Trent19852 小时前
影楼精修-肤色统一算法解析
图像处理·人工智能·算法·计算机视觉
feifeigo1232 小时前
高光谱遥感图像处理之数据分类的fcm算法
图像处理·算法·分类
北上ing3 小时前
算法练习:19.JZ29 顺时针打印矩阵
算法·leetcode·矩阵
.格子衫.4 小时前
真题卷001——算法备赛
算法
XiaoyaoCarter4 小时前
每日一道leetcode
c++·算法·leetcode·职场和发展·二分查找·深度优先·前缀树
Hygge-star4 小时前
【数据结构】二分查找5.12
java·数据结构·程序人生·算法·学习方法
June`6 小时前
专题二:二叉树的深度搜索(二叉树剪枝)
c++·算法·深度优先·剪枝
小狗祈祷诗6 小时前
day22-数据结构之 栈&&队列
c语言·数据结构
AI+程序员在路上7 小时前
XML介绍及常用c及c++库
xml·c语言·c++