想要學好一個東西,首先應該會尋找好的資料和資源
五子棋程序
我會將整體的代碼拆分出來給大家進行詳細的一個分析,或許不是最好的,但應該是最適合新手來完成的一個項目了。
首先在整個程序開始之前,所需要書寫的當然是這些繁瑣且又不得不重視的頭文件和宏定義了,因爲也是第一次寫這樣的一個小項目的程序和解析,可能有的地方會出現問題,不對的地方可以指出來哦!
#define _CRT_SECURE_NO_WARNINGS //這是因爲windows系統自身的問題,所以我在vs2013版上我加了這個用於scanf函數的使用
#include <stdio.h>
#include <windows.h>// 調用這個頭文件,是因爲我在後續使用到了清空屏幕的函數
#include <string.h>//字符串函數,相信大家都不會陌生了
#include <time.h>// 利用它我是去產生了一個時間戳,在我做的遊戲之中包含着人機對戰和人人對戰,所以需要使得人機能夠自己下子,爲了不產生同樣的結果則使用一個時間戳,會使得每一秒的值都是不同的
#include <stdlib.h>// 這是我自己爲了能夠將打印出來的窗口一直停留下看的更加清楚而加的
#define MAX_ROW 5//定義五子棋盤的行爲5
#define MAX_COL 5//定義五子棋盤的列爲5
主程序
int main(){
int i=0;
int choice = 0;
MenuUi(1); //打印遊戲界面
//是否進行遊戲的選擇;
{
printf("請輸入您的選擇:\n");
choice= ChoiceStart(i);
if (choice == 1){//當返回進入遊戲的指示後,打印選擇模式的界面
MenuMode(1);
}
}
//選擇遊戲的模式,人機或者人人;
{
printf("請選擇遊戲模式:\n");
{
choice = ChoiceMode(i);// 返回選擇模式的指示後,根據所返回的值進行相對應的模式選擇
if (choice == 1){//人人模式
system("cls");
GamePlay(1);
}
if (choice == 2)
{//人機模式
system("cls");
GameComputer(1);
}
}
}
system("pause");
return 0;
}
因爲也是第一次做這樣對於初學者來說有着那麼一點複雜的程序,所以在主程序上沒用做到非常簡明,也是爲了能夠讓自己在進行調試的時候能夠更加清楚的知道每一步的問題所在。
下面將會把每一個過程的函數給大家書寫清楚
1. 菜單打印頁
對於一個遊戲來說,第一個映入我們眼簾的當然是它的遊戲界面了即使我們只是做一個簡單的控制檯程序,但我們還是儘可能的應該取種追求完美,這也是作爲程序猿應該有的一種習慣;雖然作爲一個初學者,但我還是想要儘可能的將它在我的能力範圍之內做的更好一些。
這個遊戲界面可能有一些繁瑣,只是我在學習最初的時候,看到別人界面的設計而得到的靈感罷了,大家也是可以將自己的界面設計的更完美一些。
void MenuUi(){
printf("\t╔══════════════════════════════════════════════════════════════╗\n");
printf("\t║ ║\n");
printf("\t║ 歡迎使用五子棋對戰程序 祝您玩的愉快挑戰無極限 ║\n");
printf("\t║ ║\n");
printf("\t║ ._______________________. ║\n");
printf("\t║ | _____________________ | ║\n");
printf("\t║ | I I | ║\n");
printf("\t║ | I I | ║\n");
printf("\t║ | I 五 子 棋 I | ║\n");
printf("\t║ | I I | ║\n");
printf("\t║ | I___________________I | ║\n");
printf("\t║ !_______________________! ║\n");
printf("\t║ ._[__________]_. ║\n");
printf("\t║ .___|_______________|___. ║\n");
printf("\t║ |::: ____ | ║\n");
printf("\t║ | ~~~~ [CD-ROM] | ║\n");
printf("\t║ !_____________________! ║\n");
printf("\t║ ║\n");
printf("\t║ ║\n");
printf("\t║ 寒 星 溪 月 疏 星 首,花 殘 二 月 並 白 蓮。 ║\n");
printf("\t║ 雨 月 金 星 追 黑 玉,松 丘 新 宵 瑞 山 腥。 ║\n");
printf("\t║ 星 月 長 峽 恆 水 流,白 蓮 垂 俏 雲 浦 嵐。 ║\n");
printf("\t║ 黑 玉 銀 月 倚 明 星,斜 月 明 月 堪 稱 朋。 ║\n");
printf("\t║ 二 十 六 局 先 棄 二,直 指 遊 星 斜 彗 星。 ║\n");
printf("\t║ ║\n");
printf("\t║ ║\n");
printf("\t║ 1.開始遊戲 0.離開遊戲 ║\n");
printf("\t║ ║\n");
printf("\t╚══════════════════════════════════════════════════════════════╝\n");
}
2. 遊戲選擇函數
和遊戲界面相輝映的,是對遊戲的開始選擇,前面也說道了爲了能夠更好的調試,並且可觀性,我將每一步都寫的比較清楚,這樣也更加方便自己之後檢查和別人的學習。
int ChoiceStart(int i){
while (1){
scanf("%d", &i);
if (i == 1){//開始遊戲的選擇
system("cls");/*清屏*/
return 1;
}
else if (i == 0){//退出遊戲的選擇
system("cls");/*清屏*/
printf("歡迎您的下次挑戰!\n");
break;
}
else {
printf("您的輸入有誤,請您重新輸入:\n");
continue;
}
}
return 0;
}
3.遊戲模式選擇界面
進入遊戲了,肯定不能少得了對於遊戲模式的選擇,如果有的朋友擔心對於人人模式和人機模式自己不能夠熟練的操作的話,那麼也是可以省去雙模式的選擇,直接省去這一個模式選擇界面
其實用我自己的話來說,這些界面其實就是一個個的打印語句拼湊而成的,只要將自己想要打印的東西輸入進去,就可以打印出來我們想要的值
void MenuMode(){
printf("\t╔══════════════════════════════════════════════════════════════╗\n");
printf("\t║ ║\n");
printf("\t║ 歡迎使用五子棋對戰程序 祝您玩的愉快挑戰無極限 ║\n");
printf("\t║ ║\n");
printf("\t║ ._______________________. ║\n");
printf("\t║ | _____________________ | ║\n");
printf("\t║ | I I | ║\n");
printf("\t║ | I I | ║\n");
printf("\t║ | I 五 子 棋 I | ║\n");
printf("\t║ | I I | ║\n");
printf("\t║ | I___________________I | ║\n");
printf("\t║ !_______________________! ║\n");
printf("\t║ ._[__________]_. ║\n");
printf("\t║ .___|_______________|___. ║\n");
printf("\t║ |::: ____ | ║\n");
printf("\t║ | ~~~~ [CD-ROM] | ║\n");
printf("\t║ !_____________________! ║\n");
printf("\t║ ║\n");
printf("\t║ ║\n");
printf("\t║ ║\n");
printf("\t║ ║\n");
printf("\t║ 1.人人模式 2.人機模式 ║\n");
printf("\t║ ║\n");
printf("\t╚══════════════════════════════════════════════════════════════╝\n");
}
4. 遊戲模式的選擇函數
和第一個遊戲開始的選擇函數大同小異,只需要瞭解其中一個,那麼另一個就能夠很好的上手了
int ChoiceMode(int i){
while (1){
scanf("%d", &i);
if (i == 1){//人人模式的選擇
return 1;
}
if (i == 2){//人機模式的選擇
return 2;
}
else {
printf("您的輸入有誤,請重新輸入:\n");
continue;
}
}
}
5. 棋盤的初始化
這一步是爲了將我們所需要的棋盤進行初始化,也就是真正的核心之一,二維數組的組成
在這個程序之中也使用了一個memset函數,也是初始化函數之中,對於一個數組進行整體賦值的一個用法,這也是我目前爲止使用最多的一個。
void Init(char chessBoard[MAX_ROW][MAX_COL]){
srand((unsigned int)time(NULL));
memset(chessBoard, ' ', MAX_ROW * MAX_COL * sizeof(char));//採用了memset函數,可能也是我學習到現在爲數不多裏void* 指針所可以使用的一個函數吧,也將它分享給那些剛剛學習c語言的朋友,可以用它來進行更多的程序選擇
}
6. 打印棋盤
這個函數是較爲簡單的,其實就是我們所經常使用的打印函數罷了
void Print(char chessBoard[MAX_ROW][MAX_COL]){
printf("----------------\n");
for (int row = 0; row < MAX_ROW; row++){//行
for (int col = 0; col < MAX_COL; col++){//列
printf("|%c ", chessBoard[row][col]);//輸出棋盤的格式
}
printf("|\n");
printf("----------------\n");
}
}
7. 玩家落子(one+two)
兩個玩家的落子檢查和落子模式 ,其實大家只需要讀懂一個就完全可以了,另一個完全複製就可以了
void Playone(char chessBoard[MAX_ROW][MAX_COL]){
while (1){
int row = 0;
int col = 0;
printf("請輸入您要落子的位置,棋盤規格爲5x5,如(row col):");
scanf("%d %d", &row, &col);
if (row<0 || row>=MAX_ROW || col<0 || col>=MAX_COL){//超出棋盤的範圍
printf("您的輸入有誤!\n");
continue;
}
if (chessBoard[row][col] != ' ') {//棋盤上已經落子
// 當前位置已經有子了, 不能再重複落子
printf("該位置已經有子了!\n");
continue;
}
chessBoard[row][col] = 'X';//如果經過前兩個的檢查後完全滿足,則將此位置置爲X
break;
}
}
8. 電腦落子
在這裏使用到了時間戳,可能很多人看到了我把srand函數放到了前面,其實那對於後面電腦落子來說是不影響的,大家也是可以將那個函數放置到電腦落子的這個函數之中。
void Computer(char chessBoard[MAX_ROW][MAX_COL]){
while (1){
int row = rand() % MAX_ROW;//生成0-4
int col = rand() % MAX_COL;//生成0-4 對於這個函數的解釋,則是對其求餘的理念
if (chessBoard[row][col] != ' ') {
continue;
}
chessBoard[row][col] = 'O';
break;
}
}
9.判斷棋盤是否滿,也是判斷是否和棋
一開始因爲自己的邏輯問題,沒有思考到這一點,但是幸虧記起了在看過劍指offer第一章裏所說道的對於一個程序需要考慮它的極限情況,或者說是那些最爲邊界的情況,所以多看書,看那些有用的書對於編程來說是真的挺有用的。
int IsFull(char chessBoard[MAX_ROW][MAX_COL]) {
// 查找一下棋盤上是否有空位
for (int row = 0; row < MAX_ROW; row++) {
for (int col = 0; col < MAX_COL; col++) {
if (chessBoard[row][col] == ' ') {
// 找到空位了!!! 棋盤沒滿
return 0;
}
}
}
// 所有位置都找完了, 也沒找到空格, 最終才返回 1
return 1;
}
10. 檢查誰輸誰贏
對於這一個函數來說,它是整個核心函數之中的第二個,因爲如果你瞭解到了棋盤的數組結構的話,那麼想要對它進行檢查的話,是非常簡單的,我們只需要考慮每一行每一列,或者是兩個對角線能否成功就能夠直接判斷出它是否成功。
char Check(char chessBoard[MAX_ROW][MAX_COL]){
for (int row = 0; row < MAX_ROW; row++) {
//檢查所有的行
if (chessBoard[row][0] != ' '
&& chessBoard[row][0] == chessBoard[row][1]
&& chessBoard[row][0] == chessBoard[row][2]
&& chessBoard[row][0] == chessBoard[row][3]
&& chessBoard[row][0] == chessBoard[row][4]) {
return chessBoard[row][0];
}
}
// 2. 再檢查所有的列
for (int col = 0; col < MAX_COL; col++) {
if (chessBoard[0][col] != ' '
&& chessBoard[0][col] == chessBoard[1][col]
&& chessBoard[0][col] == chessBoard[2][col]
&& chessBoard[0][col] == chessBoard[3][col]
&& chessBoard[0][col] == chessBoard[4][col]) {
return chessBoard[0][col];
}
}
// 3. 再檢查對角線
if (chessBoard[0][0] != ' '
&& chessBoard[0][0] == chessBoard[1][1]
&& chessBoard[0][0] == chessBoard[2][2]
&& chessBoard[0][0] == chessBoard[3][3]
&& chessBoard[0][0] == chessBoard[4][4]) {
return chessBoard[0][0];
}
if (chessBoard[0][4] != ' '
&& chessBoard[0][4] == chessBoard[1][3]
&& chessBoard[0][4] == chessBoard[2][2]
&& chessBoard[0][4] == chessBoard[3][1]
&& chessBoard[0][4] == chessBoard[4][0]) {
return chessBoard[0][4];
}
// 4. 判定是否和棋, 棋盤如果滿了, 就算和棋
if (IsFull(chessBoard)) {
return 'Q';
}
return ' ';
}
11.玩家/人機對戰的遊戲函數
這也是最後一個核心函數了,其實不是說它多麼難,而是在主函數的大框架下,能夠將每一步合理的安排在一起,並且進行調用,空函數,或者是整型函數等都需要我們認真的去思考。
可能在人機和電腦執子的過程中會少上一些要給人們看的語句,所以大家也是可以自己思考一番。
void GamePlay(){
char chessBoard[MAX_ROW][MAX_COL] = { 0 };//定義棋盤
Init(chessBoard);//調用初始化棋盤函數
printf("一號玩家執棋!\n");
char winner = ' ';//定義贏家的條件
while (1){
Playone(chessBoard);//一號玩家落子
Print(chessBoard);//進行打印
Check(chessBoard);//檢測其是否贏了
winner = Check(chessBoard);
if (winner != ' ') {//檢測是否贏了
break;
}
printf("二號玩家執棋!\n");
Playtwo(chessBoard);
Print(chessBoard);
Check(chessBoard);
winner = Check(chessBoard);
if (winner != ' ') {
break;
}
}
system("cls");
Print(chessBoard);//在遊戲結束的時候打印最後的棋盤
if (winner == 'X') {
printf("恭喜你, 你贏了!\n");
}
else if (winner == 'O') {
printf("你真菜, 連ta都下不過!\n");
}
else {
printf("你倆真菜,居然五五開!\n");
}
}
各種函數到這裏其實就結束了,看着博客上出現的7000+的字,我才發現其實好像挺多的,這可能也是自己第一個寫出來一個超過300行的代碼吧,完成的那一瞬間,我感覺到自己真的很開心,或者這真的是一種挑戰吧,也是一種對自己的認可。
GitHub鏈接
代碼的整體程序我上傳到了自己的GitHub上,需要的朋友可以自己去進行下載或者學習,連接在下面!
五子棋代碼