Рок-Бумага-Ножницы-Lizard-Spock Challenge

  

«Ножницы разрезают бумагу, бумажные чехлы,            камень сокрушает ящерицу, ящерицу яды Спок,            Спок разбивает ножницы, ножницы обезглавливают ящерицу,            ящерица ест бумагу, бумага опровергает Спок,            Спок испаряет скалу. И, как всегда, камень разрушает ножницы ».

     

- Доктор Шелдон Купер

Итак, это правила.

Строительные блоки

Первое, что я подумал: «Мне нужен способ сравнить возможные варианты» - это звучало как IComparable<T>, поэтому я начал с реализации этого интерфейса в SelectionBase. Теперь, потому что я знал, что получим Rock, Paper, Scissors, Lizard и Spock code>, я решил включить свойство 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 метода в интерфейсе, но так как я wasn Не использую их, я оставил только один; Я не думаю, что избавиться от 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();
    }
}

IResultWriter

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");
    }

Фактическая игра

Я не беспокоился о создании сложного ИИ - мы играем против Шелдона Купера здесь, и он систематически играет Спока.

public class Game
{
    private readonly Dictionary<string, SelectionBase> _playable =
        new Dictionary<string, SelectionBase>
            {
                { "1", new Rock() },
                { "2", new Paper() },
                { "3", new Scissors() },
                { "4", new Lizard() },
                { "5", new Spock() }
            };

    private readonly IUserInputProvider _consoleInput;
    private readonly IResultWriter _resultWriter;

    public Game(IUserInputProvider console, IResultWriter resultWriter)
    {
        _consoleInput = console;
        _resultWriter = resultWriter;
    }

    public void Run()
    {
        while (true)
        {
            LayoutGameScreen();

            var player = GetUserSelection();
            if (player == null) return;

            var sheldon = new Spock();
            var result = player.CompareTo(sheldon);

            _resultWriter.OutputResult(result, player, sheldon);
            Pause();
        }
    }

    private void Pause()
    {
        Console.WriteLine("\nPress <ENTER> to continue.");
        Console.ReadLine();
    }

    private void LayoutGameScreen()
    {
        Console.Clear();
        Console.WriteLine("Rock-Paper-Scissors-Lizard-Spock 1.0\n{0}\n", new string('=', 40));

        foreach (var item in _playable)
            Console.WriteLine("\t[{0}] {1}", item.Key, item.Value.Name);

        Console.WriteLine();
    }

    private SelectionBase GetUserSelection()
    {
        var values = _playable.Keys.ToList();
        values.Add(string.Empty); // allows a non-selection

        var input = _consoleInput.GetValidUserInput("Your selection? <ENTER> to quit.", values);
        if (input == string.Empty) return null;

        return _playable[input];
    }
}

Выход

  • Rock: "Спокиспаряет Скалу. Вы проигрываете! »
  • Paper: «Бумага опровергает Спок. Вы побеждаете!»
  • Scissors: «Спок разбивает ножницы. Вы теряете!»
  • Lizard: «Ящерица отравляет Спок. Вы побеждаете!»
  • Spock: «Meh. Tie!»
38 голосов | спросил Mathieu Guindon 30 62013vEurope/Moscow11bEurope/MoscowSat, 30 Nov 2013 08:38:09 +0400 2013, 08:38:09

4 ответа


42

Давайте посмотрим на ваш код с точки зрения расширяемости:

Если Шелдон решает добавить новый элемент в игру, вам нужно перейти в классы 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 и некоторые дополнительные правила, и все будет готово.

Одна вещь, которую нужно улучшить с моей версией, состоит в том, что правила эффективно определяют правила выигрыша, а во всех других случаях подразумеваются связи, которые в контексте этой игры имеют смысл, но могут быть сделаны более явными.

ответил ChrisWue 30 62013vEurope/Moscow11bEurope/MoscowSat, 30 Nov 2013 12:37:46 +0400 2013, 12:37:46
14
  

это звучало как IComparable<T>

Но это не так. В документации по Compare() указано, что отношение должно быть транзитивно :

  

Если A.CompareTo(B) возвращает значение x , которое не равно нулю, а B.CompareTo(C) возвращает значение y того же знака, что и x , тогда A.CompareTo(C) требуется, чтобы вернуть значение того же знака, что и x и y .

Это не так в вашем случае, это означает, что ваши типы не должны реализовывать IComparable<T>, и в идеале метод сравнения не следует называть CompareTo(), чтобы избежать путаницы.

ответил svick 30 62013vEurope/Moscow11bEurope/MoscowSat, 30 Nov 2013 15:50:37 +0400 2013, 15:50:37
8

Мы могли бы покончить с основной частью кода класса детей, реализовав немного логики в базовом классе.

Отправной точкой для обсуждения было бы что-то вроде:

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")
            })
    {
    }
}
ответил dreza 30 62013vEurope/Moscow11bEurope/MoscowSat, 30 Nov 2013 11:51:37 +0400 2013, 11:51:37
5

Я немного старшая школа, потому что я не думаю, что ОО - правильный ответ на каждую проблему. Вот мои усилия:

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

EDIT 14-Jul-2014: в ответ на комментарий Малахи я переписал метод 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);
}
ответил Rafe 4 FebruaryEurope/MoscowbTue, 04 Feb 2014 05:10:37 +0400000000amTue, 04 Feb 2014 05:10:37 +040014 2014, 05:10:37

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132