三子棋局-挑戰你的邏輯思維


昨晚做了某公司的筆試,遇到一道名為“三子棋”的編程題,花了近一個小時的時間去完成了這道題。最后提交時,有部分測試實例沒有通過。可是已經沒有時間修改了,真是悔啊!現在將原題復述如下:

題目描述:
三子棋是一種大家熟知的游戲,幾乎所有人都會玩。游戲規則相當簡單,兩人一次在一個3X3棋盤上下棋,一個人畫叉,另一個人畫圈。任何一個人畫的三個記號如果形成構成一條水平、垂直或對角的直線則獲勝,游戲結束。畫叉的人先開始游戲,如果所有的棋盤格都畫滿了但兩人都不能獲勝,則游戲平局結束。
游戲在一個3X3的棋盤上進行,每個棋盤格單元處於空白,畫叉或畫圈的狀態一種,你的任務是確定下一輪由誰下棋:
1:輪到先手下棋;
2:輪到后手下棋;
或者是判定游戲的狀態:
x:給定的棋局不是合法的棋局;
1 won:先手獲勝;
2 won:后手獲勝;
Draw:平局;
小東對棋類游戲很有研究,這一次三子棋比賽中,她被邀請作為評判,為了提攜后進,她請你幫忙判定。

輸入:
輸入中有很多組測試數據,每組測試數據包含三行,每行均有字母'.''X''0'構成。'.'代表空白,'X'代表畫圈,'0'代表畫叉。
輸出:
對每組測試數據,在單獨的一行中輸出六中評判結果之一:1,2,x,1 won,2 won,draw。

樣例輸入:
XXX
.0.
0..

XXX
.0.
0.X
樣例輸出:
1 won
x

這道題不算難,主要是考察面試者的邏輯思維能力,尤其是在面臨壓力的情況下。

剛看到這個題時,我就有了思路,但是沒有將該游戲的規則細致地、有條理地梳理成一個框架。再加上時間的限制,於是按照思路動起手來了。可是在編碼的過程中,我發現有這六種評判結果,可以由很多種情況組成。於是邊想邊編碼,時間浪費得很多了。最后離考試結束還有幾分鍾的時候,我才提交。后來,我在原來的思路基礎上,又考慮到了幾種情況,就又添加了幾個測試用例測試一下,結果是wrong answer!

這真是特么郁悶啊!越想越不甘心啊!

今天我又將思路整理了一下,現說明如下,如果還是有問題,希望看到的碼友給出建議。

分析1、在輸入棋譜的時候,就先記錄字符'X','0','.'的數目,然后可以根據這些數目判斷一下棋局是否合法。判斷count(.)的數目,如果為9,這說明該棋局為空局,說明應該由先手先下。從題目中可以知道,先手畫叉,那么字符X的數目和字符0的關系是count(X)-count(0)=0/1。也就是X的數目要么比0的數目大1,要么就相等。如果count(X)-count(0) = 1,則后手下;如果count(X)-count(0) = 0,則先手下,當然這是在棋局合法的情況下。如果count(X)和count(0)不滿足上面的兩種情況,肯定是非法的。還有要保證count(X)+count(0)+count(.)==9,如果不滿足這個條件,則說明棋譜內包含了其它字符,狀態非法。

分析2、判斷贏的狀態,在分析1中是從棋子數目來判斷棋局的合法狀態。當然在贏的狀態的下,也會出現不合法的狀態。因為合法的狀態有:1)三行只有一條水平直線;2)三列只有一條垂直直線;3)兩條對角線可以同時出現;4)一條水平直線和一條垂直直線;5)一條水平直線和一條對角線;6)一條垂直直線和一條對角線。除此之外的直線出現都是不合法的。因為在分析1中已經判斷了棋子數目的關系,所以在此只需要判斷直線的合法狀態即可,例如,出現了一條由字符X組成的水平直線和一條由字符0組成的水平直線,這就是不合法的贏狀態。

分析3、在保證棋局合法並且不出現合法贏的狀態是,如果count(X)==count(0)並且count(.)!=0的情況下,都是先手下。如果是count(X)-count(0)==1並且count(.)!=0的情況下,都是后手下。

分析4、在保證以上狀態都沒有出現時,即可以判斷是否平局的狀態。如果出現平局,則保證沒有空白格的情況下,同時count(X) == count(0)。

上碼

#include <stdio.h>

char chart[3][3];//記錄棋譜

//FirstDo:輪到先手下棋, SecondDo:輪到后手下棋, IllLeagal:棋局不合法, FirstWon:先手贏, SecondWon:后手贏, Draw:平局
enum STATE{ FirstDo = 1, SecondDo, IllLeagal, FirstWon, SecondWon, Draw }gameState;
//記錄字符X,0,.出現的次數
int countsX = 0, countsO = 0, countsPo = 0;

//判斷贏的狀態是否合法
bool isIllOfWon = true;

//三個字符是否相等
bool isMatch(char a, char b, char c)
{
    if (a == b && b == c)
        return true;
    else
        return false;
}

//判斷是否贏,以及贏的狀態是否合法
bool OneIsWon(char &who)
{
    //行相同的數量
    int verCounts = 0;
    //列相同的數量
    int horCounts = 0;
    //贏則返回ture,同時isIllOfWon也為true
    bool flag = false;
    //判斷是否出現兩行或者三行相等的情況,出現則贏狀態不合法
    for (int i = 0; i < 3; i++)
    {
        who = chart[i][0];
        if (isMatch(who, chart[i][1], chart[i][2]))
        {
            verCounts++;
            flag = true;
        }
    }

    //判斷是否出現兩列或者三列相等的情況,出現則贏狀態不合法
    for (int j = 0; j < 3; j++)
    {
        who = chart[0][j];
        if (isMatch(who, chart[1][j], chart[2][j]))
        {
            horCounts++;
            flag = true;
        }
    }
    //是否存在對角線相等情況,記住兩條對角線可以同時相等
    if (isMatch(chart[0][0], chart[1][1], chart[2][2]) || isMatch(chart[0][2], chart[1][1], chart[2][0]))
    {
        who = chart[1][1];
        flag = true;
    }
    //不合法行相等大於或者列相等大於1
    if (verCounts > 1 || horCounts > 1)
    {
        isIllOfWon = false;
    }
    //如果先手贏了,但是后手的棋子數和先手的棋子數相等,則該贏狀態也不合法
    if (who == 'X' && countsO == countsX)
        isIllOfWon = false;

    //返回判斷結果
    return flag;
}

STATE JudgeState()
{
    char who;

    if (countsPo == 9)
        return FirstDo;
    //一旦出現非法狀態,立刻結束
    /*
    從數目上判斷狀態的合法性
    非法狀態:X,0,.的總數目不等於9,或者X與0的差值大於1.
    */
    if (countsO + countsX + countsPo != 9 || countsO - countsX > 1 || countsX - countsO > 1)
        return IllLeagal;
    bool isWin = OneIsWon(who);
    if (isWin && isIllOfWon)
    {
        if (who == 'X')
            return FirstWon;
        else
            return SecondWon;
    }
    if (isWin && isIllOfWon == false)
        return IllLeagal;

    if (countsX > countsO)
        return SecondDo;
    else if (countsO == countsX)
        return FirstDo;

    //判斷是否為平局,滿足數量條件是:1、沒有.字符;2、沒有出現不合法;3、沒有贏得狀態
    if (countsX - countsO == 1 || countsO - countsX == 1 && countsPo == 0)
        return Draw;
}

int main()
{
    while (1)
    {
        char input;

        countsX = 0, countsO = 0, countsPo = 0;
        isIllOfWon = true;
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                scanf("%c", &input);
                chart[i][j] = input;

                if (input == 'X')
                    countsX++;
                else if (input == '0')
                    countsO++;
                else
                    countsPo++;
            }

            getchar();
        }

        gameState = JudgeState();
        switch ((gameState))
        {
        case 1:printf("1\n"); break;
        case 2:printf("2\n"); break;
        case 3:printf("x\n"); break;
        case 4:printf("1 won\n"); break;
        case 5:printf("2 won\n"); break;
        case 6:printf("draw\n"); break;
        default:
            break;
        }
    }
    return 0;
}

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------這個程序雖然實現了題目的要求,但是代碼出現冗余。感謝@水中有淚前輩提出的建議:不僅要實現題目中的要求,更要使代碼編寫得易於維護,可讀,避免冗余。前輩在本人代碼的基礎上給了一些修改建議,現將修改后的代碼附上。如有疑問,請留言交流。

#include <cstdio>
char chart[3][3];//當前擺好的棋譜

//FirstDo:輪到先手下棋, SecondDo:輪到后手下棋, IllLeagal:棋局不合法, FirstWon:先手贏, SecondWon:后手贏, Draw:平局
enum STATE{ FirstDo = 1, SecondDo, IllLeagal, FirstWon, SecondWon, Draw };

//判斷三個字符是否相等,即判斷是否出現一條直線
bool isMatch(char a, char b, char c)
{
    if (a == b && b == c)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

//判斷平局、獲勝的情況,其中也有可能存在不合法的狀態
int OneIsWon()
{
    int i, j;
    //who is won?
    char who = '.';
    char tmp;
    //記錄水平直線的數目
    int verCounts = 0;
    //記錄垂直直線的數目
    int horCounts = 0;
    //出現直線的標志,true表示有直線,反之不是
    bool flag = false;
    //每行進行判斷是否有水平直線產生,以及其數目
    for (i = 0; i < 3; i++)
    {
        tmp = chart[i][0];
        //在空白格無須判斷直線狀態
        if (tmp == '.')
            continue;
        flag = isMatch(chart[i][0], chart[i][1], chart[i][2]);
        if (flag == 1)
        {
            verCounts++;
            who = chart[i][0];
        }
    }
    //一旦有多余1條直線即為非法狀態,直接返回
    if (verCounts >= 2)
    {
        return IllLeagal;
    }

    //列判斷
    for (j = 0; j < 3; j++)
    {
        tmp = chart[0][j];
        if (tmp == '.')
            continue;
        flag = isMatch(tmp, chart[1][j], chart[2][j]);
        if (flag == 1)
        {
            horCounts++;
            who = chart[0][j];
        }
    }
    //一旦有多余1條直線即為非法狀態,直接返回
    if (horCounts >= 2)
    {
        return IllLeagal;
    }

    //判斷對角線是否為直線,可以同時出現兩條對角直線,如果是字符0的兩條對角線(字符0的數目為5),則是非法狀態
    //在此不做判斷,因為已經根據字符數目判斷了棋譜的合法性。
    tmp = chart[1][1];
    if (tmp != '.')
        if (isMatch(chart[0][0], tmp, chart[2][2]) || isMatch(chart[0][2], tmp, chart[2][0]))
        {
            who = chart[1][1];
        }

    if (who == 'X')
    {
        return FirstWon;
    }
    else if (who == '0')
    {
        return SecondWon;
    }

    //假設返回平局狀態(保持平局,必須保證在合法狀態下,countsPo為0)
    return Draw;
}


/*
判斷評定結果
參數:
countsX、counts0、countsP0表示字符X/0/.的數目
*/
int JudgeState(int countsX, int countsO, int countsPo)
{
    int  isWin;
    int  cnt;

    //首先根據棋子數目判斷棋譜是否合法,一旦出現不合法狀態立即返回,無須判斷其它狀態
    //字符X/0/.的數目不等於9,說明有其它字符混入
    //X最多有5個,0最多有4個,超出則不合法
    if (countsO + countsX + countsPo != 9|| countsX >6 || countsO >5)
    {
        return IllLeagal;
    }
    //任何合法情況下,X的數量都比0的數量多0個或1個
    cnt = countsX - countsO;
    if ((cnt != 0) && (cnt != 1))
    {
        return IllLeagal;
    }

    //上面僅從棋子數目進行判斷,棋譜是否合法。在其它狀態中也有不合法的情況出現
    isWin = OneIsWon();
    if (isWin != Draw)
    {

        if ((isWin == FirstWon) && (countsX == countsO))
        {
            return IllLeagal;
        }
        return isWin;
    }
    else
    {
        //合法狀態下,countsPo等於0,即平局
        if (countsPo == 0)
        {
            return Draw;
        }
    }

    //判斷先后手走棋
    if (countsX > countsO)
    {
        return SecondDo;
    }
    else
    {
        return FirstDo;
    }
}

int main()
{
    int i, j;
    int countsX, countsO, countsPo;
    char input;
    int gameState;

    while (1)
    {
        countsX = 0, countsO = 0, countsPo = 0;
        gameState = 0;
        memset(chart, 0, sizeof(chart));

        for (i = 0; i < 3; i++)
        {
            for (j = 0; j < 3; j++)
            {
                scanf("%c", &input);
                chart[i][j] = input;

                if (input == 'X')
                {
                    countsX++;
                }
                else if (input == '0')
                {
                    countsO++;
                }
                else if (input == '.')
                {
                    countsPo++;
                }
                else
                {
                    //一旦出現非法字符立即輸出x,跳出循環
                    printf("x\n");
                    break;
                }
            }
            getchar();
        }

        gameState = JudgeState(countsX, countsO, countsPo);
        switch ((gameState))
        {
        case 1:printf("1\n"); break;
        case 2:printf("2\n"); break;
        case 3:printf("x\n"); break;
        case 4:printf("1 won\n"); break;
        case 5:printf("2 won\n"); break;
        default:printf("draw\n"); break;
        }
    }
    return 0;
} 

測試示例:

X0X
0.X
0X0
X0X
0.X
0X.
XXX
.0.
0..
XXX
.0.
0.X

輸出結果:

棋譜的判斷順序為:1、是否合法;2、贏或者平局;3、先后手走棋。

------------------------------time:2016/4/10-----修改------------------------------------------------------------------


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM