Камень, ножницы, бумага — применение принципов ООП

Буду признателен за обзор кода моего первого «настоящего» C # проекта (https://github.com/michaeljsiena/RockPaperScissors). Это простая консольная игра «камень, ножницы, бумага». Вероятно, он слишком сильно спроектирован для того, что есть, но я хотел попробовать применить некоторые аспекты объектно-ориентированного дизайна, которые я изучал, в простом консольном приложении.

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

Программа

using System;

namespace RockPaperScissors
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Title = "ROCK, PAPER, SCISSORS, LIZARD, SPOCK";

            var gameManager = GameManager.Instance;
            gameManager.PlayGame();
        }
    }
}

GameManager

using System;

namespace RockPaperScissors
{
    // Uses a singleton pattern
    public sealed class GameManager
    {
        private GameMode gameMode;

        private IPlayer player1, player2;
                
        private readonly ScoreManager scoreManager = ScoreManager.Instance;

        private static GameManager _instance = null;
        public static GameManager Instance
        {
            get => _instance ??= new GameManager();
        }

        public void PlayGame()
        {
            // game loop
            do
            {
                // set up game
                gameMode = SetGameMode();

                InitialisePlayers(gameMode, ref player1, ref player2);

                scoreManager.SetWinningScore();

                // play game
                int round = 1;
                while (player1.Score < scoreManager.WinningScore && player2.Score < scoreManager.WinningScore)
                {
                    ConsoleHelper.PrintColorTextLine($"nROUND {round}", ConsoleColor.Blue);

                    scoreManager.ShowMoveOutcomes();

                    // make moves
                    player1.MakeMove();
                    player2.MakeMove();

                    // update and showround information
                    scoreManager.UpdateGameScore(player1, player2);
                    scoreManager.ShowGameScore(player1, player2);
                    round++;
                }

                // show end of game information
                scoreManager.ShowWinner(player1, player2);
                scoreManager.ShowGameScore(player1, player2);
            } 
            while (WillPlayAgain());

            static GameMode SetGameMode()
            {
                ConsoleHelper.PrintColorText("GAME MODE ('1' = vs Computer, '2' = Simulation)?: ", ConsoleColor.Blue);

                int playerInput;
                while (!int.TryParse(Console.ReadLine(), out playerInput) || !(playerInput == 1 || playerInput == 2))
                {
                    ConsoleHelper.PrintColorTextLine("Invalid input, try again...", ConsoleColor.Red);
                }
                return (GameMode)playerInput;
            }

            static void InitialisePlayers(GameMode gameMode, ref IPlayer player1, ref IPlayer player2)
            {
                switch (gameMode)
                {
                    case GameMode.HumanVsComputer:
                        // get player name
                        ConsoleHelper.PrintColorText("PLAYER NAME: ", ConsoleColor.Blue);
                        string playerName = Console.ReadLine();

                        player1 = new HumanPlayer(playerName);
                        player2 = new ComputerPlayer("HAL 9000");
                        break;
                    case GameMode.ComputerVsComputer:
                        player1 = new ComputerPlayer("Skynet");
                        player2 = new ComputerPlayer("HAL 9000");
                        break;
                }
            }

            static bool WillPlayAgain()
            {
                ConsoleHelper.PrintColorText("nPLAY AGAIN ('1' = yes, '2' = no)?: ", ConsoleColor.Blue);

                int playerInput;
                while (!int.TryParse(Console.ReadLine(), out playerInput) || !(playerInput == 1 || playerInput == 2))
                {
                    ConsoleHelper.PrintColorTextLine("Invalid input, try again...", ConsoleColor.Red);
                }

                return playerInput == 1;
            }
        }
    }
}

ScoreManager

using System;
using System.Collections.Generic;

namespace RockPaperScissors
{
    // Uses a singleton pattern
    public sealed class ScoreManager
    {
        private static ScoreManager _instance = null;
        public static ScoreManager Instance
        {
            get => _instance ??= new ScoreManager();
        }
        private ScoreManager() { }

        private readonly Dictionary<Move, (Move losingMove1, Move losingMove2)> moveOutcomes = new Dictionary<Move, (Move, Move)>
        {
            { Move.Rock, (Move.Scissors, Move.Lizard)},
            { Move.Paper, (Move.Rock, Move.Spock)},
            { Move.Scissors, (Move.Paper, Move.Lizard)},
            { Move.Lizard, (Move.Paper, Move.Spock)},
            { Move.Spock, (Move.Rock, Move.Scissors)},
        };

        public int WinningScore { get; private set; }
        public int PlayerScore { get; private set; }
        public int ComputerScore { get; private set; }

        public void ShowMoveOutcomes()
        {
            ConsoleHelper.PrintColorTextLine("nMOVES", ConsoleColor.Blue);

            foreach (KeyValuePair<Move, (Move losingMove1, Move losingMove2)> moveOutcome in moveOutcomes)
            {
                ConsoleHelper.PrintColorTextLine($"{moveOutcome.Key} (key: '{(int)moveOutcome.Key}') beats {moveOutcome.Value.losingMove1} and {moveOutcome.Value.losingMove2}", ConsoleColor.DarkGray);
            }
        }

        public void SetWinningScore()
        {
            ConsoleHelper.PrintColorText("WINNING SCORE: ", ConsoleColor.Green);

            int winningScore;
            while (!int.TryParse(Console.ReadLine(), out winningScore) || winningScore < 1)
            {
                ConsoleHelper.PrintColorTextLine("Invalid input, must be a positive whole number...", ConsoleColor.Red);
            }

            WinningScore = winningScore;
        }

        public void UpdateGameScore(IPlayer player1, IPlayer player2)
        {
            if (player1.Move == player2.Move)
            {
                player1.Score += 1;
                player2.Score += 1;
            }
            else if (player2.Move == moveOutcomes[player1.Move].losingMove1 || player2.Move == moveOutcomes[player1.Move].losingMove2)
            {
                player1.Score += 1;
            }
            else
            {
                player2.Score += 1;
            }
        }

        public void ShowGameScore(IPlayer player1, IPlayer player2)
        {
            ConsoleHelper.PrintColorTextLine($"{player1.Name}'s Score: {player1.Score}n{player2.Name}'s Score: {player2.Score}", ConsoleColor.Green);
        }

        public void ShowWinner(IPlayer player1, IPlayer player2)
        {
            string message = (player1.Score == player2.Score)
                ? $"{player1.Name} and {player2.Name} tie!"
                : $"{(player1.Score > player2.Score ? player1.Name : player2.Name)} wins!";

            ConsoleHelper.PrintColorTextLine("n" + new string('*', message.Length), ConsoleColor.Green);
            ConsoleHelper.PrintColorTextLine(message, ConsoleColor.Green);
            ConsoleHelper.PrintColorTextLine(new string('*', message.Length), ConsoleColor.Green);
        }
    }
}

IPlayer

namespace RockPaperScissors
{
    public interface IPlayer
    {
        public string Name { get; }
        public Move Move { get; }
        public int Score { get; set; }

        public void MakeMove();
    }
}

Игрок

namespace RockPaperScissors
{
    public abstract class Player : IPlayer
    {
        public string Name { get; private set; }
        public Move Move { get; protected set; }
        public int Score { get; set; }

        protected Player(string name) => Name = name;

        public abstract void MakeMove();
    }
}

HumanPlayer

using System;

namespace RockPaperScissors
{
    public sealed class HumanPlayer : Player
    {
        public HumanPlayer(string name) : base(name) { }

        public override void MakeMove()
        {
            ConsoleHelper.PrintColorText($"{this.Name}'s move: ", ConsoleColor.White);
            
            int playerInput;
            while (!int.TryParse(Console.ReadLine(), out playerInput) || !Enum.IsDefined(typeof(Move), playerInput))
            {
                ConsoleHelper.PrintColorTextLine("Invalid input, please try again...", ConsoleColor.Red);
            }

            Move = (Move)playerInput;
        }
    }
}

КомпьютерПлеер

using System;

namespace RockPaperScissors
{
    public sealed class ComputerPlayer : Player
    {
        public ComputerPlayer(string name) : base(name) { }
        public override void MakeMove()
        {
            Move = RandomMoveGenerator.MakeRandomMove();

            Console.WriteLine($"{this.Name} made a {this.Move}");
        }
    }
}

RandomMoveGenerator

using System;
using System.Linq;

namespace RockPaperScissors
{
    public static class RandomMoveGenerator
    {
        private readonly static Random random = new Random();
        private static readonly Move[] moves = Enum.GetValues(typeof(Move))
                                                   .Cast<Move>()
                                                   .ToArray();
        public static Move MakeRandomMove() => moves[random.Next(moves.Length)];
    }
}

ConsoleHelper

using System;

// Made this to eliminate some of the clutter in GameManager.PlayGame()
namespace RockPaperScissors
{
    public static class ConsoleHelper
    {
        public static void PrintColorText(string text, ConsoleColor color)
        {
            Console.ForegroundColor = color;
            Console.Write(text);
            Console.ResetColor();
        }

        public static void PrintColorTextLine(string text, ConsoleColor color)
        {
            Console.ForegroundColor = color;
            Console.WriteLine(text);
            Console.ResetColor();
        }
    }
}

Игровой режим

namespace RockPaperScissors
{
    public enum GameMode
    {
        HumanVsComputer = 1,
        ComputerVsComputer // simulation, mainly for testing
    }
}

Двигаться

namespace RockPaperScissors
{
    public enum Move
    {
        Rock = 1,
        Paper,
        Scissors,
        Lizard,
        Spock,
    }
}

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *