• 三子棋局-挑战你的逻辑思维


    昨晚做了某公司的笔试,遇到一道名为“三子棋”的编程题,花了近一个小时的时间去完成了这道题。最后提交时,有部分测试实例没有通过。可是已经没有时间修改了,真是悔啊!现在将原题复述如下:

    题目描述:
    三子棋是一种大家熟知的游戏,几乎所有人都会玩。游戏规则相当简单,两人一次在一个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
    "); break;
            case 2:printf("2
    "); break;
            case 3:printf("x
    "); break;
            case 4:printf("1 won
    "); break;
            case 5:printf("2 won
    "); break;
            case 6:printf("draw
    "); 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
    ");
                        break;
                    }
                }
                getchar();
            }
    
            gameState = JudgeState(countsX, countsO, countsPo);
            switch ((gameState))
            {
            case 1:printf("1
    "); break;
            case 2:printf("2
    "); break;
            case 3:printf("x
    "); break;
            case 4:printf("1 won
    "); break;
            case 5:printf("2 won
    "); break;
            default:printf("draw
    "); 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-----修改------------------------------------------------------------------

  • 相关阅读:
    【转载】阿里云轻量应用型服务器和ECS服务器比较
    Android -- 启动模式
    Android -- Broadcast接收
    Qt正则表达式提取数据
    Android -- Intent
    Android -- 多线程下载
    Android -- ContentProvider与联系人
    Android -- 内容观察者
    Android -- ContentProvider
    Android -- ListView与ArrayAdapter、SimpleAdapter
  • 原文地址:https://www.cnblogs.com/tgycoder/p/5372228.html
Copyright © 2020-2023  润新知