昨天聽老師講過一遍三子棋(也就是井字棋)的實現,於是今天便嘗試着寫一下。個人的寫法並不是很好,在結果判斷的那一塊,使用的是窮舉的方法(我比較憨沒辦法,想不出來),所以如果想由此旺達延伸比如變成五子棋的話,個人在這個程序上,認爲這是第一大難關。另外呢,這個程序可能在邏輯上有點難懂(主要是main函數中的調用,和幾個循環寫的比較難看),再次呢就是一堆變量沒有做到見名知意。只能說三子棋的實現,我確實是完成了,但效率不高,邏輯不好,而且個人現在在他的擴大上的關鍵算法沒有思考出來,只能說沒有失敗,但是也沒有成功。嘛~繼續加油吧。
不是很好的需求小分析和功能實現邏輯
這是寫之前我在紙上寫的一點東西,現在回過頭來整理了一下。(就是懶,你就是個懶慫!)
這個紙上是我的整體構思,由於寫完代碼我沒有註釋,所以,可以從這張紙上着手看懂。
函數整合及大致框架搭建·
由上圖可知,
1號和4號功能都是需要輸出一個期盼區域和落子信息的,所以,這兩個功能可以整合成一個顯示函數。
落子的信息由循環遍歷打印二維數組來實現。
電腦棋子規定爲O 玩家棋子規定爲X,未落子區域由空格代替。那麼在最開始棋盤是什麼樣子的呢,如果不給玩家一個棋盤作爲參考的話,玩家就會無從下手。
所以在最開始我們需要打印一遍棋盤供期參考。但是一開始的二維數組裏面萬一有值呢?那打印的東西就會被顯示出來污染顯示的棋盤區域。
所以我們需要在每一次遊戲開始時將二維數組全部賦值爲‘ ’。這裏就需要引入一個能夠出事話整個二維數組的功能既爲初始化函數
初始化整個數組完畢後遊戲就開始了,需要先進行猜先。猜先是具有隨機性的,就和拋硬幣一樣。爲了保證他的隨機性,我使用了隨機數。爲了保證結果的唯二性,我們對這個隨機數%2+1,認爲規定他的範圍爲1-2.
然後載根據這兩個結果再結合我們進行的猜測(switch 語句case1 case2)進行猜先判定。
猜先完畢就可以正式開始遊戲了!
但是猜先的結果會影響落子的順序。所以我們需要根據猜先的結果來決定落子先後,在遊戲開始的第一次,這一點由猜先函數的返回值作爲參數傳送給落子函數,依次來決定誰先走。落子之後,在遊戲結束或者是出現結果之前,落子是交替進行的,所以我們需要交替改變傳給落子函數的參數。
c = (c % 2 + 1) % 2;
以c爲參數傳遞,你可以嘗試使用不同的int數進行計算,你胡發現c的制只有兩種,且交替出現!!!!!!
(重要!!!)
然後根據c的兩種結果,在落子函數中使用if語句將玩家的落子實現,和pc的落子步驟分別實現。
上圖說過,一落子,一檢查,一反饋。我們可以在落子後就直接對落子區域檢查,若合法,則落子完畢,若不合法,則利用循環重新落子。
若落子合法,則我們可以在這一步對落子的結果進行及時的判定,以避免在棋盤已滿之前出現勝負結果,
因爲若勝負提前分出的話,後面的落子是無意義的!!
所以我們需要在落子合法判定紙盒徐蘇的對器具結果記性分析。
如上圖所說的四種情況。
井字棋的勝負判定是根據是否出現三子連珠在這種情況。三字連住的情況有橫連,豎連,和對角線連着三種情況。我們需要做的無非就是循環遍歷檢查遇三種情況相對應的元素是否相同,且不爲空格!!!!
在這裏我使用的判斷方法是一種類似於窮舉的方法,咋該程序上是可以使用的,但是如果需要擴大的話,這種算法是顯然不可取的。希望我好好學習,以後能想出來233.
然後再結果判定之後,如果勝負已分,則可以跳轉處該函數,並反映相應的結果。若勝負未分,則需要查看棋盤的剩餘區域時候還有,如果有,那麼久繼續下期,交換落子之人,如果沒有空餘區域(指的是‘ ’的元素),則表明棋盤用盡,勝負未分,可以從新開始下一句,或者退出遊戲。由此看出,這裏還需要一個判斷棋盤是否滿了的函數。
若勝負未分且棋盤爲滿,則在落子完畢後,我們需要調用打印函數來打印整個棋盤來實現信息的及時更新與對用戶實現反饋。然後交替落子
頭文件
#ifndef SUBCHESS
#define SUBCHESS
#include<stdio.h>
#include<time.h>
#include<Windows.h>
#pragma warning(disable:4996)
#endif
#define ROW 3//二維數組的行
#define COL 3//二維數組的列
void menu();//主菜單
int menu2();//遊戲結束時彈出的是否還要在玩一局的菜單
int guess();//猜先函數
void play(int a,char chess[][COL]);//落子函數
void init(char chess[][COL] ,int b, int c);//初始化二維數組
int judge(char chess[][COL]);//結果判定函數
void show(char a[][COL]);//打印棋盤
int full(char a[][COL]);//判斷棋盤狀態函數
由此看來整個功能實現的大致框架就是
main文件
#include "Subchess.h"
int main()
{
srand((unsigned long)time(NULL));
char chess[ROW][COL];
int b = 1;
do
{
menu();
int a = 0;
printf("\n\t\t請選擇相應的功能\n");
scanf("%d", &a);
switch (a)
{
case 1:
init(chess, ROW, COL);
printf("初始化棋盤\n");
show(chess);
int c = 0, d = 0;
c = guess();
int e = 0;
do
{
play(c, chess);
d=judge(chess);
show(chess);
c = (c % 2 + 1) % 2;
e = full(chess);
switch (d)
{
case 1:printf("你贏了!\n"); goto loop;
case -1:printf("電腦贏了\n"); goto loop;
}
e=full(chess);
switch (e)
{
case 0:printf("繼續下!\n"); break;
case 2:printf("勝負未分\n"); goto loop;
}
} while (!e);
loop: if (menu2())
{
b = 1;
}
else{
b = 0;
}
continue;
case 2:b = 0; break;
default:printf("選項輸入錯誤 請輸入正確的功能選項!\n\n");
}
} while (b);
printf("\t\t\tさようなら!!");
system("pause");
}
函數實現
#include "Subchess.h"
void menu()
{
system("color 04");
printf("\t\t\t 歡迎來到井字棋遊戲\n");
printf("\t\t\t********************\n");
printf("\t\t\t**** ****\n");
printf("\t\t\t####################\n");
printf("\t\t\t***1#開始 2#退出***\n");
printf("\t\t\t********************\n");
}
int menu2()
{
int a = 0;
printf("\t\t\t是否再來一局?\n");
printf("\t\t1.yes\t\t\t2.no\n");
while (1)
{
scanf("%d", &a);
switch (a)
{
case 1:return a = 1;
case 2:
return a = 0;
default:printf("請輸入相應的功能選項!");
}
}
}
int guess()
{
while (1)
{
int a = 0;
a=rand() % 2 + 1;
printf("三秒後硬幣將落地,請猜正反面!\n");
printf(" \t\t 3 \r"); Sleep(1000);
printf(" \t\t 2 \r"); Sleep(1000);
printf(" \t\t 1 \r"); Sleep(1000);
printf("\t\t請猜\t——1.正面 2.反面———\n");
int b = 0;
scanf("%d", &b);
int c = a % 2;
switch (b)
{
case 1:
if (c)
{
printf("硬幣爲%d面,爲正面,你先行!\n", c);
return 1;
}
printf("硬幣爲%d面,爲反面,pc先行!\n", c+2);
return 0;
case 2:
if (c)
{
printf("硬幣爲%d面,爲正面,pc先行!\n", c );
Sleep(5000);
return 0;
}
printf("硬幣爲%d面,爲反面,你先行!\n", c+2);
Sleep(5000);
return 1;
default:
printf("猜先錯誤,從新再來!\n");
}
}
}
void init(char a[][COL], int b, int c)
{
for (int x = 0; x < b; x++)
{
for (int y = 0; y < c; y++)
{
a[x][y] = ' ';
}
}
}
int full(char a[][COL])
{
int fulll = 1;
for (int x = 0; x < ROW; x++)
{
for (int y = 0; y < COL; y++)
{
if (a[x][y] == ' ')
{
fulll = 0;
return 0;//未滿
}
}
}
if (fulll)
{
return 2;
}
}
void play(int a,char chess[][COL])
{
int x = 0, y = 0;
int xp = 0, yp = 0;
int m = 0, n = 0;
int mp = 0, np = 0;
if (a)
{
printf("輪到你了!\t");
do
{
n= 0;
do
{
m = 0;
printf("請輸入你落子的行列位置!(第幾行第幾列)###\n");
scanf("%d,%d", &x,&y);
if (x > 3 || x < 0 || y>3 || y < 0)
{
printf("\n位置輸入錯誤請重新輸入!\n");
m = 1;
}
} while (m);
if (chess[x - 1][y - 1] != ' ')
{
printf("該位置已有落子,不可再落子該處!請重新輸入!\n");
n = 1;
if (full(chess) == 2)
{
break;
}
}
else
{
chess[x - 1][y - 1] = 'X';
}
} while (n);
}
else
{
printf("輪到電腦了!\t");
do
{
np = 0;
mp = 0;
xp = rand() % 3 + 1;
yp = rand() % 3 + 1;
if (chess[xp - 1][yp - 1] != ' ')
{
np = 1;
}
} while (np);
printf("電腦落子於第 %d 行 第 %d 列\n", xp, yp);
chess[xp - 1][yp - 1] = 'O';
}
}
int judge(char a[][COL])
{
int FULL = 0;
int row = 0, col = 0;
for (int row = 0; row < ROW; row++)
{
if ((a[row][1] == a[row][0]) && (a[row][0] == a[row][2]) && a[row][0] != 0)
{
if (a[row][0] == 'X')
{
printf("你贏了");
return 1;
}
if (a[row][0] == 'O')
{
printf("電腦贏了!!\n");
return -1;
}
}
}
for (int col = 0; col < ROW;col++)
{
if ((a[0][col] == a[1][col]) && (a[0][col] == a[2][col]) && a[0][col] != 0)
{
if (a[0][col] == 'X')
{
printf("你贏了");
return 1;
}
if (a[0][col] == 'O')
{
printf("電腦贏了!!\n");
return -1;
}
}
}
if (((a[0][0] == a[1][1]) && (a[1][1] == a[2][2])) || ((a[0][2] == a[1][1]) && (a[1][1] == a[2][0])))
{
if (a[1][1] == 'X'&&a[1][1] != ' ')
{
printf("你贏了");
return 1;
}
if (a[1][1] == 'O'&&a[1][1] != ' ')
{
printf("電腦贏了!!\n");
return -1;
}
}
}
void show(char a[][COL])
{
printf("|| || 1 || 2 || 3 ||\n");
printf("=========================\n");
for (int x = 0; x < ROW; x++)
{
printf("|| %d ||", x + 1);
for (int y = 0; y < COL; y++)
{
printf(" %c ||", a[x][y]);
}
printf("\n");
printf("=========================\n");
}
}
運行結果
這個是菜單和落子過程,主要是最後一句合法性判斷!!!
電腦勝利 遊戲結束,彈出小菜單(我太菜了!qwq)
勝負未分
我贏了哈哈哈
以上就是我的井字棋實現了。雖然邏輯很奇怪,但是畢竟還是實現了,這貌似也是我第一次寫一個將近300行以上的代碼。以往的都是小規模的習題。經過這次,我覺得把,貼近生活實際,有具體的,現實的邏輯的程序還是蠻難的。尤其是功能一多,模塊一多,彼此互相調用,再加上各種循環,就很難控制,有時候寫錯邏輯,在運行是出現死循環,都要找個半天。只能說以後還是要多練習。
最最最重要,複習昨天的一句話!數組傳參發生降維!降維成指向數組內部元素類型的指針!。