C 语 言 --- 三 子 棋
C 语 言 --- 三 子 棋
- 代 码 全 貌 与 功 能 介 绍
- 游 戏 效 果 展 示
- 游 戏 代 码 详 解
- game.h
- test.c
- game.c
- 总结
💻作 者 简 介:曾 与 你 一 样 迷 茫,现 以 经 验 助 你 入 门 C 语 言
💡个 人 主 页:@笑口常开xpr 的 个 人 主 页
📚系 列 专 栏:C 启新程
✨代 码 趣 语:编 程 是 告 诉 另 一 个 人 你 希 望 计 算 机 做 什 么 的 艺 术。
💪代 码 千 行,始 于 坚 持,每 日 敲 码,进 阶 编 程 之 路。
📦gitee 链 接:gitee
在 编 程 的 世 界 里,每 一 行 代 码 都 可 能 隐 藏 着 无 限 的 可 能 性 。你 是 否 想 过,一 个 小 小 的 程 序 究 竟 能 改 变 什 么?它 可 以 是 解 决 复 杂 问 题 的 工 具 ,也 可 以 是 实 现 梦 想 的 桥 梁。今 天,就 让 我 们 一 起 走 进 C 语 言 三 子 棋 的 世 界,探 索 它 的 无 限 潜 力。
代 码 全 貌 与 功 能 介 绍
整 个 游 戏 项 目 由 三 个 主 要 文 件 构 成:game.h、test.c 和 game.c。这 种 多 文 件 的 架 构 设 计,有 助 于 将 不 同 功 能 模 块 分 离,提 高 代 码 的 可 读 性、可 维 护 性 与 可 扩 展 性 。
game.h
定 义 棋 盘 大 小 ROW 和COL 为 3,声 明 游 戏 核 心 函 数,像 初 始 化 棋 盘 的 init_board、判 断 输 赢 的 is_win 等,为 后 续 二 维 数 组 提 供 初 始 化。
test.c:
控 制 游 戏 流 程。menu 函 数 打 印 含 “ 开 始 ”“ 退 出 ” 选 项 的 菜 单。game 函 数 是 核 心 循 环,先 初 始 化、打 印 棋 盘,接 着 循 环 让 玩 家、电 脑 交 替 下 棋,每 次 下 棋 后 打 印 棋 盘 并 判 断 输 赢,直 至 有 结 果。test 函 数 获 取 玩 家 菜 单 选 择,决 定 游 戏 走 向,main 函 数 调 用 test 启 动 游 戏,srand((unsigned int)time(NULL)); 为 电 脑 随 机 下 棋 设 种 子。
game.c
实 现 具 体 功 能。init_board 将 棋 盘 元 素 初 始 化 为 空 格。print_board 按井 字 棋 格 式 打 印 棋 盘。player_move 获 取 玩 家 合 法 且 对 应 位 置 为 空 的 坐 标,放 置 玩 家 棋 子。computer_move 随 机 生 成 空 位 置 坐 标 放 电 脑 棋 子。is_win 检 查 行、列、对 角 线 判 断 输 赢,平 局 返 回 ’ Q ',未 结 束 返 回 ’ C '。is_full 遍 历 棋 盘 判 断 是 否 全 被 占 用 。
下 面 展 示
完整代码
。
读 者 可 以 将 这 段 代 码 复 制 到 自 己 的 编 译 器 中 运 行:
game.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#define ROW 3
#define COL 3
//头文件中声明函数
//初始化棋盘
void init_board(char board[ROW][COL], int row, int col);
//打印棋盘
void print_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);
//判断平局
int is_full(char board[ROW][COL], int row, int col);
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("*******************************\n");
printf("************ 1.play *********\n");
printf("************ 0.exit *********\n");
printf("*******************************\n");
}
void game()
{
char board[ROW][COL];
char ret = 0;
//初始化棋盘为全空格
init_board(board, ROW, COL);
//打印棋盘
print_board(board, ROW, COL);
while (1)
{
//玩家下棋
player_move(board, ROW, COL);
//打印棋盘
print_board(board, ROW, COL);
//判断输赢
ret = is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
//电脑下棋
computer_move(board, ROW, COL);
//打印棋盘
print_board(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 test()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void init_board(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0;i < row;i++)
{
int j = 0;
for (j = 0;j < col;j++)
{
board[i][j] = ' ';
}
}
}
void print_board(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0;i < row;i++)
{
int j = 0;
for (j = 0;j < col;j++)
{
if (j == col - 1)
{
printf(" %c ", board[i][j]);
}
else
{
printf(" %c |", board[i][j]);
}
}
printf("\n");
if (i < row - 1)
{
for (j = 0;j < col;j++)
{
if (j == col - 1)
{
printf("---");
}
else
{
printf("---|");
}
}
printf("\n");
}
}
}
void player_move(char board[ROW][COL], int row, int col)
{
printf("玩家下棋:>\n");
while (1)
{
printf("请输入要下棋的坐标:>");
int x = 0;
int y = 0;
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("该坐标被占用,请重新输入\n");
}
}
else
{
printf("坐标非法\n");
}
}
}
//电脑下棋
//随机生成坐标,只要没有被占用,就下棋
void computer_move(char board[ROW][COL], int row, int col)
{
printf("电脑下棋:>\n");
while (1)
{
int x = rand() % row;
int y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
char is_win(char board[ROW][COL], int row, int col)
{
int i = 0;
//判断三行
for (i = 0;i < row;i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] !=' ')
{
return board[i][0];
}
}
//判断三列
for (i = 0;i < col;i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')
{
return board[0][i];
}
}
//判断对角线
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
{
return board[1][1];
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
{
return board[1][1];
}
//平局
if (is_full(board, row, col))
{
return 'Q';
}
//继续,没有玩家或者电脑赢,
return 'C';
}
int is_full(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0;i < row;i++)
{
int j = 0;
for (j = 0;j < col;j++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
游 戏 效 果 展 示
游 戏 启 动 后,玩 家 看 到 简 洁 的 主 菜 单,选 择 1 开 始 游 戏,反 之,选 择 0 退 出 游 戏。
此 时,空 棋 盘 呈 现 眼 前,3 × 3 的 棋 盘 格 等 待 着 玩 家 与 电 脑 依 次 落 子。
玩 家 率 先 行 动,在 输 入 下 棋 坐 标 时,程 序 会 验 证 坐 标 合 法 性。若 坐 标 合 法 且 对 应 位 置 为 空,玩 家 棋 子 “ * ” 将 稳 稳 落 在 棋 盘 上;反 之,玩 家 会 收 到 “ 坐 标 非 法 ” 或 “ 该 坐 标 被 占 用,请 重 新 输 入 ” 的 提 示,需 重 新 尝 试。
接 着,电 脑 依 据 随 机 数 落 子 “ # ”。玩 家 和 电 脑 就 这 样 交 替 下 棋,每 一 次 落 子 后,棋 盘 状 态 实 时 更 新 展 示。在 这 个 过 程 中,游 戏 不 断 检 查 棋 局 是 否 分 出 胜 负。若 玩 家 或 电 脑 成 功 实 现 三 子 连 珠,程 序 立 即 宣 告 “ 玩 家 赢 了 ” 或 “ 电 脑 赢 了 ”,游 戏 结 束。若 直 至 棋 盘 所 有 位 置 被 填 满,都 未 出 现 三 子 连 珠 的 情 况,程 序 会 判 定 “ 平 局 ”,并 展 示 最 终 棋 盘 状 态。
游 戏 代 码 详 解
game.h
#pragma once
防 止 重 复 包 含
#pragma once 指 令 确 保 这 个 头 文 件 在 整 个 项 目 中 只 会 被 包 含 一 次。在 大 型 项 目 中,若 多 个 源 文 件 包 含 同 一 个 头 文 件,若 不 加 以 限 制,头 文 件 中 的 内 容 会 被 重 复 编 译,导 致 错 误。使 用 此 指 令 可 避 免 这 种 情 况。
#include <stdio.h>
#include <stdlib.h>
引 入 标 准 库
#include <stdio.h> 引 入 标 准 输 入 输 出 库,使 得 代 码 能 够 使 用 如 printf(用 于 输 出 信 息)和 scanf( 用 于 获 取 用 户 输 入)等 函 数。#include <stdlib.h> 引 入 标 准 库,为 rand 函 数( 电 脑 随 机 下 棋 时 用 到 )等 提 供 支 持。
#define ROW 3
#define COL 3
定 义 棋 盘 大 小:#define ROW 3 和 #define COL 3 通 过 宏 定 义 确 定 了 棋 盘 的 行 数 和 列 数 均 为 3 。宏 定 义 是 一 种 简 单 的 文 本 替 换 机 制,在 预 处 理 阶 段,编 译 器 会 将 代 码 中 所 有 的 ROW 和 COL 替 换 为 3 。这 使 得 在 后 续 代 码 中,若 要 修 改 棋 盘 大 小,只 需 更 改 这 两 处 宏 定 义 即 可,增 强 了 代 码 的 可 维 护 性。
#define ROW 3
#define COL 3
定 义 棋 盘 大 小:#define ROW 3 和 #define COL 3 通 过 宏 定 义 确 定 了 棋 盘 的 行 数 和 列 数 均 为 3 。宏 定 义 是 一 种 简 单 的 文 本 替 换 机 制,在 预 处 理 阶 段,编 译 器 会 将 代 码 中 所 有 的 ROW 和 COL 替 换 为 3 。这 使 得 在 后 续 代 码 中,若 要 修 改 棋 盘 大 小,只 需 更 改 这 两 处 宏 定 义 即 可,增 强 了 代 码 的 可 维 护 性。
//头文件中声明函数
//初始化棋盘
void init_board(char board[ROW][COL], int row, int col);
//打印棋盘
void print_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);
//判断平局
int is_full(char board[ROW][COL], int row, int col);
void init_board(char board[ROW][COL], int row, int col); 声 明 了 初 始 化 棋 盘 的 函 数。它 将 在 game.c 中 实 现,负 责 将 棋 盘 的 每 个 位 置 初 始 化 为 空 格。
void print_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); 在 game.c 中 通 过 rand 函 数 生 成 随 机 坐 标,并 将 电 脑 的 棋 子 放 置 在 空 的 棋 盘 位 置 上。
char is_win(char board[ROW][COL], int row, int col); 声 明 判 断 输 赢 的 函 数。通 过 检 查 棋 盘 的 行 、列 和 对 角 线,判 断 是 否 有 玩 家 或 电 脑 获 胜。
int is_full(char board[ROW][COL], int row, int col); 声 明 判 断 平 局 的 函 数。通 过 遍 历 棋 盘 数 组,判 断 所 有 位 置 是 否 被 占 用,若 全 被 占 用 返 回 1,否 则 返 回 0 。
test.c
#define _CRT_SECURE_NO_WARNINGS 1
消 除 安 全 警 告:#define _CRT_SECURE_NO_WARNINGS 1 用 于 消 除 因 使 用 了 一 些 不 安 全 的 函 数( 如 scanf)而 产 生 的 编 译 警 告。在 较 新 的 编 译 器 中,为 了 安 全 考 虑,对 一 些 函 数 的 使用 会 给 出 警 告 , 通 过 定 义 这 个 宏 可 屏 蔽 这 些 警 告。
void menu()
{
printf("*******************************\n");
printf("************ 1.play *********\n");
printf("************ 0.exit *********\n");
printf("*******************************\n");
}
menu 函 数:void menu() 函 数 用 于 打 印 游 戏 主 菜 单。通 过 printf 函 数 输 出 一 个 包 含“ 1.play ”(开 始 游 戏)和 “0.exit”( 退 出 游 戏 )选 项 的 菜 单,为 玩 家 提 供 操 作 选 择 界 面。
void game()
{
char board[ROW][COL];
char ret = 0;
//初始化棋盘为全空格
init_board(board, ROW, COL);
//打印棋盘
print_board(board, ROW, COL);
while (1)
{
//玩家下棋
player_move(board, ROW, COL);
//打印棋盘
print_board(board, ROW, COL);
//判断输赢
ret = is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
//电脑下棋
computer_move(board, ROW, COL);
//打印棋盘
print_board(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");
}
}
game 函 数:
- 定 义 了 一 个 二 维 字 符 数 组 char board[ROW][COL];用 于 存 储 棋 盘 状 态,以 及 一 个 字 符 变 量 char ret = 0; 用 于 存 储 游 戏 结 果。
- 调 用 init_board(board, ROW, COL); 函 数 初 始 化 棋 盘 ,将 棋 盘 每 个 位 置 设 为 空 格。接 着 调 用 print_board(board, ROW, COL); 打 印 初 始 棋 盘。
- 进 入 一 个 无 限 循 环 while (1),在 循 环 内,首 先 调 用 player_move(board, ROW, COL); 让 玩 家 下 棋,然 后 再 次 调 用 print_board(board, ROW, COL); 打 印 玩 家 下 棋 后 的 棋 盘 状 态,接 着 调 用 is_win(board, ROW, COL); 判 断 游 戏 是 否 结 束。若 ret 不 等 于 ’ C '( 表 示 游 戏 有 结 果 ),则 跳 出 循 环。否 则 ,调 用 computer_move(board, ROW, COL); 让 电 脑 下 棋,再 次 打 印 棋 盘 并 判 断 输 赢。当 循 环 结 束 时,根 据 ret 的 值 判 断 游 戏 结 果 并 输 出 相 应 信 息。
void test()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
}
test 函 数:在 test 函 数 中,先 定 义 int input = 0; 存 储 玩 家 菜 单 选 择,并 用 srand((unsigned int)time(NULL)); 设 置 随 机 种 子。通 过 do - while 循 环,先 调 用 menu() 打 印 菜 单,再 用 scanf(“%d”, &input); 获 取 玩 家 输 入。利 用 switch 语 句,输 入 为 1 时 调 用 game() 开 始 游 戏,为 0 时 输 出 “ 退 出 游 戏 ” 并 结 束 循 环,其 他 情 况 输 出 “ 选 择 错 误”。循 环 以 input 不 为 0 为 条 件,保 证 玩 家 不 选 退 出 时 可 继 续 选 择 。
int main()
{
test();
return 0;
}
main 函 数:int main() 函 数 作 为 程 序 入 口,调 用 test() 函 数 启 动 游 戏 流 程,最 后 return 0; 表 示 程 序 正 常 结 束 。
game.c
void init_board(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0;i < row;i++)
{
int j = 0;
for (j = 0;j < col;j++)
{
board[i][j] = ' ';
}
}
}
init_board 函 数:void init_board(char board[ROW][COL], int row, int col) 函 数 负 责 初 始 化 棋 盘。通 过 两 层 嵌 套 的 for 循 环,外 层 循 环 控 制 行,内 层 循 环 控 制 列。在 循 环 体 中,将 board[i][j]( 棋 盘 的 每 个 位 置)赋 值 为 空 格 ’ ',确 保 棋 盘 在 游 戏 开 始 时 为 空 状 态。
void print_board(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0;i < row;i++)
{
int j = 0;
for (j = 0;j < col;j++)
{
if (j == col - 1)
{
printf(" %c ", board[i][j]);
}
else
{
printf(" %c |", board[i][j]);
}
}
printf("\n");
if (i < row - 1)
{
for (j = 0;j < col;j++)
{
if (j == col - 1)
{
printf("---");
}
else
{
printf("---|");
}
}
printf("\n");
}
}
}
print_board 函 数:void print_board(char board[ROW][COL], int row, int col) 函 数 用 两 层 嵌 套 for 循 环 遍 历 棋 盘 数 组。内 层 循 环 依 列 索 引 j 判 断,若 为 最 后 一 列,直 接 输 出 board[i][j] 两 边 加 空 格 形 式;非 最 后 一 列,输 出 board[i][j] 后 紧 跟 | 分 隔 符,每 行 结 束 换 行。当 i 小 于 row - 1, 打 印 由 - - - 和 - - - | 组 成 的 分 隔 线,以 此 呈 现 井 字 棋 棋 盘 样 式。
void player_move(char board[ROW][COL], int row, int col)
{
printf("玩家下棋:>\n");
while (1)
{
printf("请输入要下棋的坐标:>");
int x = 0;
int y = 0;
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("该坐标被占用,请重新输入\n");
}
}
else
{
printf("坐标非法\n");
}
}
}
player_move 函 数:void player_move(char board[ROW][COL], int row, int col)函 数 负 责 玩 家 下 棋 操 作。先 输 出 “ 玩 家 下 棋 :>”,进 入 while(1) 循 环。循 环 内 提 示 玩 家 输 入 坐 标,用 scanf 获 取 输 入。检 查 坐 标 合 法 性(x >= 1 && x <= row && y >= 1 && y <= col),再 看 board[x - 1][y - 1] 是 否 为 空。为 空 则 放 玩 家 棋 子 * 并 跳 出 循 环,非 法 或 被 占 就 输 出 对 应 提 示,让 玩 家 重 新 输 入。
void computer_move(char board[ROW][COL], int row, int col)
{
printf("电脑下棋:>\n");
while (1)
{
int x = rand() % row;
int y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
computer_move 函 数:void computer_move(char board[ROW][COL], int row, int col) 函 数 实 现 电 脑 下 棋。先 输 出 “ 电 脑 下 棋 :>”,进 入 while(1) 循 环。循 环 内 用 int x = rand() % row; 和 int y = rand() % col; 生 成 0 至 row - 1、0 至 col - 1 间 随 机 整 数 作 为 行 列 索 引。检 查 board[x][y] 是 否 为 空 ,为 空 则 放 电 脑 棋 子 # 并 跳 出 循 环,否 则 持 续 生 成 坐 标 直 至 找 到 空 位 置。
char is_win(char board[ROW][COL], int row, int col)
{
int i = 0;
//判断三行
for (i = 0;i < row;i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] !=' ')
{
return board[i][0];
}
}
//判断三列
for (i = 0;i < col;i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')
{
return board[0][i];
}
}
//判断对角线
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
{
return board[1][1];
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
{
return board[1][1];
}
//平局
if (is_full(board, row, col))
{
return 'Q';
}
//继续,没有玩家或者电脑赢,
return 'C';
}
is_win 函 数:char is_win(char board[ROW][COL], int row, int col) 函 数 判 断 游 戏 输 赢。先 经 for 循 环 检 查 行,若 某 行 三 元 素 相 等 且 非 空,返 回 该 行 首 元 素;再 用 for 循 环 查 列,若 某 列 三 元 素 相 等 且 非 空,返 回 该 列 首 元 素;接 着 检 查 两 条 对 角 线,若 对 角 线 三 元 素 相 等 且 非 空,返 回 中 间 位 置 元 素。最 后,调 用 is_full(board, row, col) 判 断 棋 盘 是 否 已 满,满 则 返 回 ’ Q ’ 表 示 平 局,否 则 返 回 ’ C ’ 表 示 游 戏 继 续。
int is_full(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0;i < row;i++)
{
int j = 0;
for (j = 0;j < col;j++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
is_full函数:int is_full(char board[ROW][COL], int row, int col) 函 数 用 于 判 断 棋 盘 是 否 已 满。通 过 两 层 嵌 套 的 for 循 环 遍 历 棋 盘 数 组,只 要 发 现 有 一 个 位 置 board[i][j] 为 空,就 返 回 0 表 示 棋 盘 未 满;若 遍 历 完 整 个 棋 盘 都 没 有 发 现 空 位 置,则 返 回 1 表 示 棋 盘 已 满 。
总结
通 过 这 个 简 单 的 三 子 棋 游 戏,我 们 不 仅 实 践 了 C 语 言 中 的 基 本 语 法,如 变 量 定 义、循 环 结 构、条 件 判 断 和 函 数 调 用,还 学 习 了 如 何 使 用 随 机 数 生 成 函 数 和 输 入 输 出 函 数。希 望 这 篇 博 客 能 帮 助 你 更 好 地 理 解 C 语 言 编 程,激 发 你 在 编 程 世 界 中 不 断 探 索 和 创 新 的 热 情 。