1. 程式人生 > >C#-wpf一個簡單的井字棋程式

C#-wpf一個簡單的井字棋程式

前言

好久沒有碰過程式碼了,上次看C#估計還是上個學期233,這次老師突然佈置了一個C#語言編寫的井字棋程式讓我有點措手不及.不過,仗著微軟爸爸的vs的強大的程式碼補全能力,最後還是將這個程式做好了(遇到了很多坑).

要求

該程式的要求還是有點多了,以下一一列舉:
1. 在程式初始化時,要求輸入玩家使用者名稱,輸入後出現提示文字,並選擇是先手還是後手.
這裡寫圖片描述
2. 點選Fight!後,進入遊戲介面,在九宮格中下一步棋後,電腦(敵人)會判斷形勢並下第二部棋,如此直至有三子連成一線(玩家或電腦贏),或者格子被佔滿(和局).旁邊有提示按鈕,點選後會出現提示;得分按鈕會出現具體的得分介面;退出按鈕則會退出程式


這裡寫圖片描述
3. 得分介面,點開後會顯示自己的得分情況.最高得分按鈕會記錄玩家玩這個遊戲以來所得的最高分;列印按鈕則會列印自己的具體得分情況
這裡寫圖片描述

主體

首先製作開啟程式時出現的”登入”介面,即該程式的mainwindow:

樣式:
這裡寫圖片描述

這裡要注意的是,只有當輸入完姓名後才能出現下面的提示文字等控制元件,所以應事先將其屬性定為隱藏,點選”commit”按鈕後觸發事件

private void btm_commit_click(object sender, RoutedEventArgs e)
        {
            if (TBox1.Text
.Length>0&&TBox1.Text.Length<10)//判斷玩家輸入的文字長度,更為嚴謹的做法應該是判斷字母+數字的長度,以及玩家輸入的字串不符合時應出現提示資訊 { storage.name = TBox1.Text;//定義在storage類中的靜態變數,為了儲存玩家名稱,成績等資訊 TBlock2.Text = "Hi " + storage.name + ". Welcome to the battlefield!Now,lets begin the game!!!Hey,do you want to place a bomb first?"
;//其實應該直接在前臺程式碼中寫好內容,不必在後臺再賦值,因多次修改程式碼所致 TBlock2.Visibility = Visibility.Visible;//顯示提示文字塊 RB1.Visibility = Visibility.Visible;//顯示單選按鈕,讓使用者選擇是否先手 RB2.Visibility = Visibility.Visible;//同上 btm_fight.Visibility = Visibility.Visible;//顯示進入遊戲介面的按鈕 btm_commit.IsEnabled = false;//禁止玩家再次點選commit按鈕 } }

在玩家確定好是否先手後,就該點選Fight!按鈕進入遊戲介面了

private void to_main_page_click(object sender, RoutedEventArgs e)
        {
            if(RB1.IsChecked==true)//玩家點選"yes"按鈕時,設定靜態布林型別變數RButton的值並進入遊戲介面"mainpage"
            {
                RButton = true;
                this.Content = new mainpage();
            }
            else if (RB2.IsChecked == true)//玩家點選"no"按鈕後,類同yes
            {
                RButton = false;
                this.Content = new mainpage();
            }
            else//玩家沒有點選單選按鈕的情形,出現提示資訊並不觸發進入遊戲介面的事件,其實想想是否讓程式預先設定好"yes"單選按鈕就沒有這一步了呢?
            {
                TBcheck.Visibility = Visibility.Visible;
            }
        }

其次,開始製作井字棋程式最複雜的遊戲部分,即mainpage

樣式:
這裡寫圖片描述
在拖好控制元件後,自然要去實現他們的功能.首先是九宮格,我的想法是玩家(假設是玩家先手,並永遠下”X”棋)下出第一步棋後,按鈕打印出”X”字母,並且在後臺的對應於該按鈕的值變為1;而電腦(永遠下”O”棋)下棋後,按鈕打印出”O”字母,並且在後臺的對應於該按鈕的值變為-1;而沒有下任何棋的按鈕則不會打印出任何字母,並且在後臺的對應於該按鈕的值為0.

private void Btm0_Click(object sender, RoutedEventArgs e)//按鈕點選觸發事件,即玩家點選後的結果.9個按鈕均與此類似
        {
            if (btm[btm0]==0)//按鈕與後臺的值組成鍵值對的字典,判斷當按鈕後臺的值為0時
            {
                btm0.Content="X";//按鈕btm0的內容變為"X",打印出"X"字母
                btm[btm0] = 1;//按鈕後臺的值變為1
                computer();//電腦的操作,事實上除了下另一步棋外,還執行了判斷是否贏或輸或平局的函式,因此沒有單獨列出
            }
        }

三字連成一線的情況總共有8種,為了更快速的判斷是否出現了特殊情況(兩子一線,三子一線),可以將這些情況列成一個8行3列的陣列以供判斷函式或者電腦使用

private void update()
        {
            array_for_judge[0] = new int[3] { btm[btm0], btm[btm1], btm[btm2] };//第一行3子
            array_for_judge[1] = new int[3] { btm[btm3], btm[btm4], btm[btm5] };//第二行3子
            array_for_judge[2] = new int[3] { btm[btm6], btm[btm7], btm[btm8] };//第三行3子
            array_for_judge[3] = new int[3] { btm[btm0], btm[btm3], btm[btm6] };//第一列3子
            array_for_judge[4] = new int[3] { btm[btm1], btm[btm4], btm[btm7] };//第二列3子
            array_for_judge[5] = new int[3] { btm[btm2], btm[btm5], btm[btm8] };//第三列3子
            array_for_judge[6] = new int[3] { btm[btm0], btm[btm4], btm[btm8] };//右斜3子
            array_for_judge[7] = new int[3] { btm[btm2], btm[btm4], btm[btm6] };//左斜3子
        }

每次玩家或者電腦走完一步棋後,都得判斷是否出現贏或輸或平局的情況,若出現,將重新整理棋盤並記錄資訊

private void judge()//判斷函式
        {
            update();//更新資訊
            foreach (int[] tmp in array_for_judge)
            {
                if (Array.TrueForAll(tmp, isone => (isone == 1)) == true)//判斷3子全為1,即玩家連成3子的情況
                {
                    MessageBox.Show("you win!");//彈出訊息框,玩家贏
                    storage.battle++;//storage類的靜態變數,儲存對局次數
                    storage.win++;//同,儲存玩家贏的次數
                    flush();//重新整理棋盤,即設定按鈕文字全為空,後臺值全為0,在此不單獨列出該函式
                }

                else if (Array.TrueForAll(tmp, isone => (isone == -1)) == true)//判斷3子全為-1,即電腦連成3子的情況
                {
                    MessageBox.Show("you lose!");
                    storage.battle++;
                    storage.lose++;
                    flush();
                }

            }
            if (btm.ContainsValue(0) == false)//最後判斷沒有子為0,即棋盤下滿仍沒有勝負的情況,平局
            {
                MessageBox.Show("draw!");
                storage.battle++;
                storage.tie++;
                flush();
            }
        }

電腦也需要根據棋盤的形勢下棋,在此列出3種情況:
1. 當電腦的棋子已有2子一線,並且該線無第3子時,下第3子取得勝利
2. 當玩家的棋子已有2子一線,並且該線無第3子時,下第3子阻止玩家取得勝利
3. 當無以上情況時,隨機選擇並落子

private int computer()//返回值是為了跳出該函式用的,並無太大意義
        {
            judge();//首先為玩家判斷是否已出現能贏或平局的可能
            if (btm.ContainsValue(1) == false&&btm.ContainsValue(-1)==false)//判斷按鈕的值全為0的情形,即棋盤已被重新整理,跳出該函式
            {
                return 0;
            }
            for (int i = 0; i < array_for_judge.Length; i++)//進行特殊情況判斷
            {
                if (array_for_judge[i].Sum() == -2)//判斷2子為-1,另1子為0的情況,即第一種情況
                {
                    int j = Array.IndexOf(array_for_judge[i], 0);
                    btm[to_dic_int(i, j)] = -1;//這裡我不知道如何從已列出特殊情形的陣列中取得原本按鈕的資訊,只好採取最笨拙的方式--定義函式建立特殊情形陣列與按鈕的對映,設定按鈕的值為-1
                    to_dic_int(i, j).Content()="O";//設定按鈕的內容為"O"
                    judge();//再次進行勝負判斷
                    return 0;
                }
                if (array_for_judge[i].Sum() == 2)//阻止玩家獲勝的情形
                {
                    int j = Array.IndexOf(array_for_judge[i], 0);
                    btm[to_dic_int(i, j)] = -1;
                    to_dic_int(i, j).Background = storage.getImage2();
                    judge();
                    return 1;
                }
            }
            Random random = new Random();//隨機落子的情形
            while (true)無限迴圈直至隨機落子並且該按鈕的值為0
            {
                int tmp = random.Next(24);
                int itmp = tmp % 8;
                int jtmp = tmp % 3;
                if (array_for_judge[itmp][jtmp] == 0)
                {
                    btm[to_dic_int(itmp, jtmp)] = -1;
                    to_dic_int(itmp, jtmp).Background = storage.getImage2();
                    judge();
                    return 2;
                }
            }
        }

還要設定電腦先手的情況

if (MainWindow.RButton == false)//在遊戲介面初始化時進行判斷,當玩家之前選擇false單選按鈕時讓電腦先執行一步
            {
                first_computer();//隨機讓電腦在九宮格中落子
            }

Show Help按鈕

private void btm_tips_click(object sender, RoutedEventArgs e)
        {
            if (text_tips.Text == "")//事先設定好tips文字塊,不過內容為空
            {
                text_tips.Text = "Play The Battle of Waterloo! Start by placing bombs. The enemy will situate a bomb after you.";//顯示提示文字
                btm_tips.Content = "Hide Help";//更改Show Help按鈕的內容
            }
            else//顯示文字塊內容後,再次點選按鈕
            {
                text_tips.Text = "";
                btm_tips.Content = "Show Help";
            }
        }

Show Score按鈕

private void main_to_score(object sender, RoutedEventArgs e)
        {
            score_page score = new score_page();//建立score視窗物件
            score.Show();//顯示score視窗
        }

Exit按鈕

private void Exit_click(object sender, RoutedEventArgs e)
        {
            Application.Current.Shutdown();//退出程式
        }

最後,製作井字棋的得分介面,即score_page

這裡寫圖片描述
拖幾個文字塊的事情…值得注意的是,右邊的分數需事先讀取玩家資料並將其顯示出來

Score_player.Text = storage.name;
Score_battles.Text = storage.battle.ToString();
Score_win.Text = storage.win.ToString();
Score_lose.Text = storage.lose.ToString();
Score_tie.Text = storage.tie.ToString();

Top Score和Print按鈕的功能我還沒有實現.Top Score應該是事先讀取儲存檔案中的最高分的資料並用文字塊顯示出來;而Print則應彈出檔案資源管理器讓玩家選擇儲存資料的位置,然後將其儲存

總結

這個程式我搞了很長時間才讓其具備初始的功能,仍然有許多不完善的地方,比如介面的設計啦,數值的繫結啦等等.不過在這次編寫C#程式後可能我的下一次編寫就又不知道是什麼時候了233,因此我希望在這幾天完善完善比較好.
其實老師的要求是埋下地雷而不是井字棋,遊戲的名稱叫做”滑鐵盧戰役”,實際上和井字棋一摸一樣-.-,附上改”X”“O”為地雷圖片的圖:
這裡寫圖片描述