“剪刀剪纸,岩石覆盖纸,
岩石压碎蜥蜴,蜥蜴毒害Spock,
Spock砸碎剪刀,剪断斩首蜥蜴,
蜥蜴吃纸,纸反驳Spock, br /> Spock蒸发了岩石。岩石一如既往地破碎剪刀。”

-Sheldon Cooper博士


所以这些是规则。

积木

我想到的第一件事是“我需要一种比较可能的选择的方法”-听起来像IComparable<T>,所以我首先在SelectionBase类中实现了该接口。现在,因为我知道可以从中派生RockPaperScissorsLizardSpock类,所以我决定包括一个返回类型名称的Name属性,并且由于我还需要一种根据对手的选择,我还包括一个GetWinningVerb方法:

public abstract class SelectionBase : IComparable<SelectionBase>
{
    public abstract int CompareTo(SelectionBase other);
    public string Name { get { return GetType().Name; } }
    public abstract string GetWinningVerb(SelectionBase other);
}


基类由每个可能的选择实现:

public class Rock : SelectionBase
{
    public override string GetWinningVerb(SelectionBase other)
    {
        if (other is Scissors) return "crushes";
        if (other is Lizard) return "crushes";

        throw new InvalidOperationException("Are we playing the same game?");
    }

    public override int CompareTo(SelectionBase other)
    {
        if (other is Rock) return 0;
        if (other is Paper) return -1;
        if (other is Scissors) return 1;
        if (other is Lizard) return 1;
        if (other is Spock) return -1;

        throw new InvalidOperationException("Are we playing the same game?");
    }
}

public class Paper : SelectionBase
{
    public override string GetWinningVerb(SelectionBase other)
    {
        if (other is Rock) return "covers";
        if (other is Spock) return "disproves";

        throw new InvalidOperationException("Are we playing the same game?");
    }

    public override int CompareTo(SelectionBase other)
    {
        if (other is Rock) return 1;
        if (other is Paper) return 0;
        if (other is Scissors) return -1;
        if (other is Lizard) return -1;
        if (other is Spock) return 1;

        throw new InvalidOperationException("Are we playing the same game?");
    }
}

public class Scissors : SelectionBase
{
    public override string GetWinningVerb(SelectionBase other)
    {
        if (other is Paper) return "cuts";
        if (other is Lizard) return "decapitates";

        throw new InvalidOperationException("Are we playing the same game?");
    }

    public override int CompareTo(SelectionBase other)
    {
        if (other is Rock) return -1;
        if (other is Paper) return 1;
        if (other is Scissors) return 0;
        if (other is Lizard) return 1;
        if (other is Spock) return -1;

        throw new InvalidOperationException("Are we playing the same game?");
    }
}

public class Lizard : SelectionBase
{
    public override string GetWinningVerb(SelectionBase other)
    {
        if (other is Paper) return "eats";
        if (other is Spock) return "poisons";

        throw new InvalidOperationException("Are we playing the same game?");
    }

    public override int CompareTo(SelectionBase other)
    {
        if (other is Rock) return -1;
        if (other is Paper) return 1;
        if (other is Scissors) return -1;
        if (other is Lizard) return 0;
        if (other is Spock) return 1;

        throw new InvalidOperationException("Are we playing the same game?");
    }
}

public class Spock : SelectionBase
{
    public override string GetWinningVerb(SelectionBase other)
    {
        if (other is Rock) return "vaporizes";
        if (other is Scissors) return "smashes";

        throw new InvalidOperationException("Are we playing the same game?");
    }

    public override int CompareTo(SelectionBase other)
    {
        if (other is Rock) return 1;
        if (other is Paper) return -1;
        if (other is Scissors) return 1;
        if (other is Lizard) return -1;
        if (other is Spock) return 0;

        throw new InvalidOperationException("Are we playing the same game?");
    }
}


用户输入

然后我需要一种获取用户输入的方法。我知道我将要制作一个简单的控制台应用程序,但是为了运行单元测试,我决定创建一个IUserInputProvider接口-第一遍在接口中包含所有3种方法,但是由于我没有全部使用它们,所以我只保留一个;我认为摆脱GetUserInput(string)不会造成伤害:

public interface IUserInputProvider
{
    string GetValidUserInput(string prompt, IEnumerable<string> validValues);
}

public class ConsoleUserInputProvider : IUserInputProvider
{
    private string GetUserInput(string prompt)
    {
        Console.WriteLine(prompt);
        return Console.ReadLine();
    }

    private string GetUserInput(string prompt, IEnumerable<string> validValues)
    {
        var input = GetUserInput(prompt);
        var isValid = validValues.Select(v => v.ToLower()).Contains(input.ToLower());

        return isValid ? input : string.Empty;
    }

    public string GetValidUserInput(string prompt, IEnumerable<string> validValues)
    {
        var input = string.Empty;
        var isValid = false;
        while (!isValid)
        {
            input = GetUserInput(prompt, validValues);
            isValid = !string.IsNullOrEmpty(input) || validValues.Contains(string.Empty);
        }

        return input;
    }
}


实际程序

class Program
{
    /*
        "Scissors cuts paper, paper covers rock, 
         rock crushes lizard, lizard poisons Spock, 
         Spock smashes scissors, scissors decapitate lizard, 
         lizard eats paper, paper disproves Spock, 
         Spock vaporizes rock. And as it always has, rock crushes scissors." 

                                                          -- Dr. Sheldon Cooper
    */

    static void Main(string[] args)
    {
        var consoleReader = new ConsoleUserInputProvider();
        var consoleWriter = new ConsoleResultWriter();

        var game = new Game(consoleReader, consoleWriter);
        game.Run();
    }
}


实际游戏


我并不在意构建复杂的AI-我们正在与Sheldon Cooper对抗

public interface IResultWriter
{
    void OutputResult(int comparisonResult, SelectionBase player, SelectionBase sheldon);
}

public class ConsoleResultWriter : IResultWriter
{
    public void OutputResult(int comparisonResult, SelectionBase player, SelectionBase sheldon)
    {
        var resultActions = new Dictionary<int, Action<SelectionBase, SelectionBase>>
        {
            { 1, OutputPlayerWinsResult },
            { -1, OutputPlayerLosesResult },
            { 0, OutputTieResult }
        };

        resultActions[comparisonResult].Invoke(player, sheldon);
    }

    private void OutputPlayerLosesResult(SelectionBase player, SelectionBase sheldon)
    {
        Console.WriteLine("\n\tSheldon says: \"{0} {1} {2}. You lose!\"\n", sheldon.Name, sheldon.GetWinningVerb(player), player.Name);
    }

    private void OutputPlayerWinsResult(SelectionBase player, SelectionBase sheldon)
    {
        Console.WriteLine("\n\tSheldon says: \"{0} {1} {2}. You win!\"\n", player.Name, player.GetWinningVerb(sheldon), sheldon.Name);
    }

    private void OutputTieResult(SelectionBase player, SelectionBase sheldon)
    {
        Console.WriteLine("\n\tSheldon says: \"Meh. Tie!\"\n");
    }



输出



蒸发岩石。您输了!” br />Rock:“蜥蜴毒害了Spock。您赢了!”

Paper:“ Meh。Tie!”

#1 楼

让我们从可扩展性的角度来看您的代码:

如果Sheldon决定向游戏中添加新项目,那么您必须进入n类来调整比较和获胜动词。我通常会尽量避免这样的设计,因为每当您要求开发人员在添加新内容的情况下更改n位置中的内容时,他/她就必定会忘记一个位置。 ?嗯,一个游戏似乎很适合规则方法,特别是因为在这种情况下规则非常简单并且结构始终相同:

enum Item
{
    Rock, Paper, Scissors, Lizard, Spock
}

class Rule
{
    public readonly Item Winner;
    public readonly Item Loser;
    public readonly string WinningPhrase;

    public Rule(item winner, string winningPhrase, item loser)
    {
        Winner = winner;
        Loser = loser;
        WinningPhrase = winningPhrase;
    }

    public override string ToString()
    {
         return string.Format("{0} {1} {2}", Winner, WinningPhrase, Loser);
    }
}


某处的规则列表:

    static List<Rule> Rules = new List<Rule> {
            new Rule(Item.Rock, "crushes", Item.Scissors),
            new Rule(Item.Spock, "vaporizes", Item.Rock),
            new Rule(Item.Paper, "disproves", Item.Spock),
            new Rule(Item.Lizard, "eats", Item.Paper),
            new Rule(Item.Scissors, "decapitate", Item.Lizard),
            new Rule(Item.Spock, "smashes", Item.Scissors),
            new Rule(Item.Lizard, "poisons", Item.Spock),
            new Rule(Item.Rock, "crushes", Item.Lizard),
            new Rule(Item.Paper, "covers", Item.Rock),
            new Rule(Item.Scissors, "cut", Item.Paper)
    }


您现在可以做出决定:

class Decision
{
    private bool? _HasPlayerWon;
    private Rule _WinningRule;

    private Decision(bool? hasPlayerWon, Rule winningRule)
    {
        _HasPlayerWon = hasPlayerWon;
        _WinningRule = winningRule;
    }

    public static Decision Decide(item player, item sheldon)
    {
        var rule = FindWinningRule(player, sheldon);
        if (rule != null)
        {
            return new Decision(true, rule);
        }

        rule = FindWinningRule(sheldon, player);
        if (rule != null)
        {
            return new Decision(false, rule);
        }

        return new Decision(null, null);
    }

    private static Rule FindWinningRule(item player, item opponent)
    {
        return Rules.FirstOrDefault(r => r.Winner == player && r.Loser == opponent);
    }

    public override string ToString()
    {
        if (_HasPlayerWon == null)
        {
            return "Meh. Tie!";
        }
        else if (_HasPlayerWon == true)
        {
            return string.Format("{0}. You win!", _WinningRule);
        }
        else
        {
            return string.Format("{0}. You lose!", _WinningRule);
        }
    }
}


如果要在游戏中添加另一个项目,然后在enum中添加另一个条目,并添加一些其他规则,就可以完成。是隐含的联系,在本游戏的上下文中是有意义的,但可以使其更加明确。

评论


\ $ \ begingroup \ $
@dreza:是的,首先我将构造函数设为(优胜者,失败者,词组),但是随后我编写了规则并思考了一下,如果调换后读起来会更好:)
\ $ \ endgroup \ $
– ChristWue
13年11月30日在8:50

\ $ \ begingroup \ $
这显然是比OP和dreza的答案更好的问题原因。它使决策变得复杂,成为与选择数据分离的操作实体。
\ $ \ endgroup \ $
– Karl Damgaard Asmussen
13年11月30日在11:04

#2 楼


听起来像IComparable<T>


,但事实并非如此。 Compare()的文档指出该关系必须是可传递的:


如果A.CompareTo(B)返回的值x不等于零,并且B.CompareTo(C)返回的值y与x相同,则要求A.CompareTo(C)返回与x和y相同的符号的值。


在您的情况下不正确,这意味着您的类型不应实现IComparable<T>和理想情况下,不应将比较方法称为CompareTo(),以免造成混淆。

#3 楼

我们也许可以通过在基类中实现一些逻辑来消除大量子类代码。

讨论的出发点是:

public abstract class SelectionBase : IComparable<SelectionBase>
{
    private readonly List<WinningPlay> _winsAgainst;

    protected SelectionBase(List<WinningPlay> winsAgainst)
    {
        _winsAgainst = winsAgainst;
    }

    public virtual int CompareTo(SelectionBase other)
    {
        if (GetType() == other.GetType()) return 0; // draws against itself

        if (_winsAgainst.Any(p => p.Winner == other.GetType())) return 1; // wins

        return -1; // otherwise loses.
    }

    public virtual string Name { get { return GetType().Name; } }

    public virtual string GetWinningVerb(SelectionBase other)
    {
        var winner = _winsAgainst.SingleOrDefault(p => p.Winner == other.GetType());

        if (winner == null)
            throw new InvalidOperationException("Are we playing the same game?");
        else
            return winner.Verb;
    }

    protected class WinningPlay
    {
        public Type Winner { get; private set; }
        public string Verb { get; private set; }

        public WinningPlay(Type type, string verb)
        {
            Winner = type;
            Verb = verb;
        }
}


子类变为:

public class Rock : SelectionBase
{

    public Rock() 
        : base(new List<WinningPlay>
            {
                new WinningPlay(typeof(Scissors), "crushes"),
                new WinningPlay(typeof(Lizard), "crushes")
            })
    {
    }
}

public class Paper : SelectionBase
{
    public Paper()
        : base(new List<WinningPlay>
            {
                new WinningPlay(typeof (Rock), "covers"),
                new WinningPlay(typeof (Spock), "disproves")
            })
    {
    }
}

public class Scissors : SelectionBase
{
    public Scissors()
        : base(new List<WinningPlay>
            {
                new WinningPlay(typeof (Rock), "cuts"),
                new WinningPlay(typeof (Spock), "decapitates")
            })
    {
    }
}

public class Lizard : SelectionBase
{
    public Lizard()
        : base(new List<WinningPlay>
            {
                new WinningPlay(typeof (Paper), "eats"),
                new WinningPlay(typeof (Spock), "poisons")
            })
    {
    }
}

public class Spock : SelectionBase
{
     public Spock()
         : base(new List<WinningPlay>
            {
                new WinningPlay(typeof (Rock), "Vaporizes"),
                new WinningPlay(typeof (Scissors), "smashes")
            })
    {
    }
}


评论


\ $ \ begingroup \ $
很棒。我距离提交修改完全可以花点时间回答这个出色的答案只有2秒!我的意思是,这几乎就是我即将提交的代码-这是下一个逻辑重构步骤。实际上,我还摆脱了GetWinningVerb和所有这些无用的异常。你在读我的想法!
\ $ \ endgroup \ $
– Mathieu Guindon♦
13年11月30日在7:55



\ $ \ begingroup \ $
@retailcoder欢呼。这是一个开始。我想关于重构的好主意是做一次,退后一步,然后再做一次。我有点奇怪您甚至需要儿童班。简单的枚举会做吗?
\ $ \ endgroup \ $
– dreza
13年30月30日在8:17

#4 楼

我有些老套,因为我认为OO不是解决每个问题的正确答案。这是我的努力:

void Main()
{
    foreach (var left in Enumerable.Range(0, (int)A.Count).Cast<A>()) {
        foreach (var right in Enumerable.Range(0, (int)A.Count).Cast<A>()) {
            Result result;
            string report;
            Play(left, right, out result, out report);
            Console.WriteLine(left + " vs " + right + ": " + report + " -- " + result);
        }
    }
}

enum A { Rock, Paper, Scissors, Lizard, Spock, Count };

static string[,] Defeats;

static void InitDefeats()
{
    Defeats = new string[(int)A.Count, (int)A.Count];
    Action<A, string, A> rule = (x, verb, y) => { Defeats[(int)x, (int)y] = verb; };
    rule(A.Rock, "crushes", A.Lizard);
    rule(A.Rock, "blunts", A.Scissors);
    rule(A.Paper, "wraps", A.Rock);
    rule(A.Paper, "disproves", A.Spock);
    rule(A.Scissors, "cut", A.Paper);
    rule(A.Scissors, "decapitates", A.Lizard);
    rule(A.Lizard, "poisons", A.Spock);
    rule(A.Lizard, "eats", A.Paper);
    rule(A.Spock, "smashes", A.Scissors);
    rule(A.Spock, "vaporizes", A.Rock);
}

enum Result { LeftWins, Tie, RightWins };

static void Play(A left, A right, out Result result, out string report)
{
    if (Defeats == null) InitDefeats();
    var lr = Defeats[(int)left, (int)right];
    var rl = Defeats[(int)right, (int)left];
    if (lr != null) {
        result = Result.LeftWins;
        report = (left + " " + lr + " " + right).ToLower();
        return;
    }
    if (rl != null) {
        result = Result.RightWins;
        report = (right + " " + rl + " " + left).ToLower();
        return;
    }
    result = Result.Tie;
    report = (left + " vs " + right + " is a tie").ToLower();
}


打印哪些内容:

Rock vs Rock: rock vs rock is a tie -- Tie
Rock vs Paper: paper wraps rock -- RightWins
Rock vs Scissors: rock blunts scissors -- LeftWins
Rock vs Lizard: rock crushes lizard -- LeftWins
Rock vs Spock: spock vaporizes rock -- RightWins
Paper vs Rock: paper wraps rock -- LeftWins
Paper vs Paper: paper vs paper is a tie -- Tie
Paper vs Scissors: scissors cut paper -- RightWins
Paper vs Lizard: lizard eats paper -- RightWins
Paper vs Spock: paper disproves spock -- LeftWins
Scissors vs Rock: rock blunts scissors -- RightWins
Scissors vs Paper: scissors cut paper -- LeftWins
Scissors vs Scissors: scissors vs scissors is a tie -- Tie
Scissors vs Lizard: scissors decapitates lizard -- LeftWins
Scissors vs Spock: spock smashes scissors -- RightWins
Lizard vs Rock: rock crushes lizard -- RightWins
Lizard vs Paper: lizard eats paper -- LeftWins
Lizard vs Scissors: scissors decapitates lizard -- RightWins
Lizard vs Lizard: lizard vs lizard is a tie -- Tie
Lizard vs Spock: lizard poisons spock -- LeftWins
Spock vs Rock: spock vaporizes rock -- LeftWins
Spock vs Paper: paper disproves spock -- RightWins
Spock vs Scissors: spock smashes scissors -- LeftWins
Spock vs Lizard: lizard poisons spock -- RightWins
Spock vs Spock: spock vs spock is a tie -- Tie


编辑2014年7月14日:在为了回应Malachi的评论,我重写了Play方法,以返回一个对象而不是两个out参数。代码长度相同,是否更清晰则存在争议。这是更新的版本:

void Main()
{
    foreach (var left in Enumerable.Range(0, (int)A.Count).Cast<A>()) {
        foreach (var right in Enumerable.Range(0, (int)A.Count).Cast<A>()) {
            var outcome = Play(left, right);
            Console.WriteLine(left + " vs " + right + ": " + outcome.Report + " -- " + outcome.Result);
        }
    }
}

enum A { Rock, Paper, Scissors, Lizard, Spock, Count };

static string[,] Defeats;

static void InitDefeats() 
{
    Defeats = new string[(int)A.Count, (int)A.Count];
    Action<A, string, A> rule = (x, verb, y) => { Defeats[(int)x, (int)y] = verb; };
    rule(A.Rock, "crushes", A.Lizard);
    rule(A.Rock, "blunts", A.Scissors);
    rule(A.Paper, "wraps", A.Rock);
    rule(A.Paper, "disproves", A.Spock);
    rule(A.Scissors, "cut", A.Paper);
    rule(A.Scissors, "decapitates", A.Lizard);
    rule(A.Lizard, "poisons", A.Spock);
    rule(A.Lizard, "eats", A.Paper);
    rule(A.Spock, "smashes", A.Scissors);
    rule(A.Spock, "vaporizes", A.Rock);
}

class Outcome {
    internal string Report;
    internal Result Result;
    internal Outcome(A left, A right, string lr, string rl)
    {
        Report = ( lr != null ? left + " " + lr + " " + right
                 : rl != null ? right + " " + rl + " " + left
                 :              left + " vs " + right + " is a tie"
                 ).ToLower();
        Result = ( lr != null ? Result.LeftWins
                 : rl != null ? Result.RightWins
                 :              Result.Tie
                 );
    }
}

enum Result { LeftWins, Tie, RightWins };

static Outcome Play(A left, A right)
{
    if (Defeats == null) InitDefeats();
    var lr = Defeats[(int)left, (int)right];
    var rl = Defeats[(int)right, (int)left];
    return new Outcome(left, right, lr, rl);
}


评论


\ $ \ begingroup \ $
我+1了,因为我发现您的回答带来了有趣的观点。但是,downvote也很引人注目,因为这个答案也可以作为一个新的评论问题发布-它不是评论,而是重写...而且FWIW我真的不喜欢将枚举类型称为A。
\ $ \ endgroup \ $
– Mathieu Guindon♦
2014年2月4日,下午2:53

\ $ \ begingroup \ $
@ lol.upvote,公正的评论-今天,我在喝咖啡休息时间在该论坛上整理代码,这也许是有道理的!不过,我确实有一点要指出,那就是通常的OO方法(类,接口,设计模式等)可以使您脱离最简单的解决方案。这就是我要证明的。回复:A,尽管我的舌头贴在脸颊上,但我还是选择了A来阅读精美。
\ $ \ endgroup \ $
– Rafe
2014年2月4日,下午3:37

\ $ \ begingroup \ $
@ syb0rg,我在该站点上没有任何指南表明,答复中不应提供其他可供考虑的方法。如果存在这样的准则,而我却错过了,那么,我深表歉意。如果没有,那么我认为我的贡献是有效的。
\ $ \ endgroup \ $
– Rafe
2014年2月4日,下午3:59