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]**来简化不同方向上的搜索。

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

相关推荐
梁辰兴1 小时前
数据结构:栈和队列
c语言·数据结构·算法·c··队列
孟大本事要学习1 小时前
算法第23天|贪心算法:基础理论、分发饼干、摆动序列、最大子序和
算法·贪心算法
SKYDROID云卓小助手2 小时前
三轴云台之深度学习算法篇
服务器·人工智能·单片机·嵌入式硬件·深度学习·数码相机·算法
CoovallyAIHub2 小时前
超越YOLOv8!无参注意力+动态ROI的YOLO-APD突破复杂道路场景
深度学习·算法·计算机视觉
用户40315986396633 小时前
计算初始化内存总长度
java·算法
DoraBigHead3 小时前
🧠 小哆啦解题记 · 区间“合合乐”
算法
持梦远方3 小时前
探索 Sort.h:多功能排序算法模板库
c语言·数据结构·算法·排序算法·c++模板库·排序算法模板库
EndingCoder3 小时前
算法在前端框架中的集成
前端·javascript·算法·前端框架·排序算法
Once_day3 小时前
代码训练LeetCode(46)旋转图像
算法·leetcode·职场和发展
麻辣长颈鹿Sir4 小时前
【Keil】C/C++混合编程的简单方法
c语言·c++·keil·c/c++融合编程·多语言混合编程