当前位置: 首页 > article >正文

22. 五子棋小游戏

文章目录

    • 概要
    • 整体架构流程
    • 技术名词解释
    • 技术细节
    • 小结

1. 概要

🔊 JackQiao 对 米粒 说:“今天咱们玩个五子棋小游戏,电脑与你轮流在一个 n×n 的网格上放置棋子(X O),网格由你输入的正整数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个位置。
  • nx 和 ny 计算了在当前位置 (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] 来简化不同方向上的搜索。

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


http://www.kler.cn/a/429122.html

相关文章:

  • UI自动化测试:异常截图和page_source
  • vscode 设置
  • opencv3.4 ffmpeg3.4 arm-linux 交叉编译
  • 【鸿蒙】0x02-LiteOS-M基于Qemu RISC-V运行
  • 个人vue3-学习笔记
  • Android渲染Latex公式的开源框架比较
  • 阿里云PolarDB 如何进行数据恢复,文档总结
  • 【Qt】QMainWindow、QWidget和QDialog的区别?
  • Oracle 19C RU补丁升级,从19.7to19.25 -单机
  • 5G模组AT命令脚本-关闭模组的IP过滤功能
  • 驱动断链的研究
  • 【C++AVL树】枝叶间的旋律:AVL树的和谐之道
  • H5游戏出海如何获得更多增长机会?
  • 2024年12月9日Github流行趋势
  • Yocto bitbake and codeSonar
  • 【5G】Spectrum 频谱
  • 关于网页自动化工具DrissionPage进行爬虫的使用方法
  • flink终止提交给yarn的任务
  • 什么是CSS盒模型?box-sizing又是什么?
  • 架构09-可靠通信
  • Unity 策略游戏地图上的网格是如何实现的
  • 游戏引擎学习第38天
  • css定义多个延时动画案例代码
  • 基于单片机的智能农田灌溉节水系统设计及应用
  • centos部署SkyWalking并在springcloud项目中用法举例
  • 在AWS上可以使用什么和人工智能相关的服务?