Буду признателен за обзор кода моего первого «настоящего» 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,
}
}