I'm back with a hopefully revised and improved version of my RPG pokemon console game! Well, it's more like a randomized pokemon simulation as there isn't really any kind of strategy, it's only about getting lucky lol.I have added more functionalities and made lots of changes and corrections as suggested, but I'm sure there's a lot more room for improvement so here I am. Could anyone please take a look and let me know what they think? I won't leave this project alone until it is (near) perfect. Warning: It's LONG. (sorry)Full code on pastebin: https://pastebin.com/iDZDCgZDConsoleUI.Progam.csusing System;using System.Collections.Generic;using System.Linq;using GameLogic;namespace ConsoleUI{    public static class Program    {        private static readonly Dictionary<string, string> Choices =            new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)            {                {CatchWildPokemon, 1},                {TrainPokemon, 2},                {FightRival, 3},                {Exit, 4}            };        private static readonly Dictionary<string, string> YesOrNo =            new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)            {                {Yes, Y},                {No, N}            };        private static readonly string Y = YesOrNo[Yes];        private static readonly string N = YesOrNo[No];        private static readonly string CatchWildPokemon = Choices[CatchWildPokemon];        private static readonly string TrainPokemon = Choices[TrainPokemon];        private static readonly string FightRival = Choices[FightRival];        private static readonly string Exit = Choices[Exit];        private static readonly IPokemonGenerator PokemonGenerator = new PokemonGenerator();        private static readonly PokemonLevelSystem LevelSystem = new PokemonLevelSystem();        private static readonly IPokemonWorld PokemonWorld = new PokemonWorld();        private static readonly IGameState GameState = new GameState();        private static IRandomPokemonPicker _pokemonPicker;        private static IPlayersCollection _playersCollection;        private static IRivalsCollection _rivalsCollection;        private static readonly ICollection<Pokemon> GymTeam = new List<Pokemon>();        private static readonly ICollection<Pokemon> FightingTeam = new List<Pokemon>();        private static readonly IList<Pokemon> RivalsTeam = new List<Pokemon>();        private static bool _isGameOn;        private static bool _trainPokemon;        private static bool _fightRival;        private static int _wins;        private static int _ties;        private static int _losses;        private static void Main()        {            PokemonWorld.Populate(PokemonGenerator, PokemonGenerator.Generate().Count());            _pokemonPicker = new RandomPokemonPicker(PokemonWorld);            _playersCollection = new PlayersCollection(_pokemonPicker, PokemonWorld);            _rivalsCollection = new RivalsCollection(PokemonWorld);            GameState.LoadGame(_playersCollection, _pokemonPicker);            _isGameOn = true;            Menu();        }        // *** MENU ***        private static void DisplayMenu()        {            Console.WriteLine(\nWhat do you want to do?\n);            Console.WriteLine(${CatchWildPokemon}. Catch wild pokmon);            Console.WriteLine(${TrainPokemon}. Train your pokmon in gym);            Console.WriteLine(${FightRival}. Fight rival [Min level: {_rivalsCollection.MinLevel}]);            Console.WriteLine(${Exit}. Exit);            Console.WriteLine();        }        public static void Menu()        {            while (_isGameOn)            {                DisplayMenu();                var choice = Console.ReadLine();                while (Choices.All(n => n.Value != choice))                {                    Console.WriteLine(                        $\nPlease type '{CatchWildPokemon}', '{TrainPokemon}' or '{FightRival}', or exit typing '{Exit}'.\n);                    choice = Console.ReadLine();                }                if (choice == Exit)                {                    GameState.SaveGame(_playersCollection.GetCollection(), _pokemonPicker.GetCurrentPopulation());                    _isGameOn = false;                    break;                }                PlayerChoice(choice);            }            Console.WriteLine();        }        private static void PlayerChoice(string choice)        {            if (choice == CatchWildPokemon)            {                if (AreRequirementsMetToCatchPokemon())                    ChoiceCatchWildPokemon();            }            else if (choice == TrainPokemon)            {                if (AreRequirementsMetToTrainPokemon())                    ChoiceTrainPokemon();            }            else if (choice == FightRival)            {                if (AreRequirementsMetToFightRival())                    ChoiceFightRival();            }            else            {                throw new Exception(Unexpected error occurred. Player's choice is invalid.);            }        }        // *** CHECK REQUIREMENTS FOR EACH CHOICE ***        private static bool AreRequirementsMetToCatchPokemon()        {            if (!_playersCollection.IsCompleted) return true;            Console.WriteLine(\nYou've caught all wild pokmon!);            return false;        }        private static bool AreRequirementsMetToTrainPokemon()        {            if (_playersCollection.IsEmpty)            {                Console.WriteLine(\nYou haven't caught any pokmon yet!);                return false;            }            if (_playersCollection.GetCollection().Any(p => p.Level < LevelSystem.MaxLevel)) return true;            Console.WriteLine(\nAll your pokmon have reached max level!);            return false;        }        private static bool AreRequirementsMetToFightRival()        {            if (_playersCollection.IsEmpty)            {                Console.WriteLine(\nYou haven't caught any pokmon yet!);                return false;            }            if (_playersCollection.GetCollection().Any(p => p.Level >= _rivalsCollection.MinLevel)) return true;            Console.WriteLine(\nAt least one of your pokmon must meet this rival's minimum level requirement.);            Console.WriteLine(Try leveling up some pokmon at the gym first!);            return false;        }        // *** CATCH WILD POKEMON ***         private static void CatchWildPokemonTextIntro()        {            Console.WriteLine(\nTime to catch some pokmon!);            Console.WriteLine(Let's explore this area.);        }        private static void ChoiceCatchWildPokemon()        {            CatchWildPokemonTextIntro();            Console.ReadKey();            while (true)            {                var wildPokemon = _playersCollection.GetRandomWildPokemon();                Console.WriteLine($\nYou find a wild pokmon. It's a {wildPokemon.Name}. Let's catch it!);                Console.ReadKey();                Console.WriteLine($\nFighting {wildPokemon.Name}...);                Console.ReadKey();                if (IsCatchSuccessful())                {                    _playersCollection.AddPokemonToCollection(wildPokemon);                    _pokemonPicker.RemoveFromCurrentPopulation();                    Console.WriteLine($\nAwesome! You caught {PokemonInfo(wildPokemon)});                }                else                {                    Console.WriteLine($\nOh no! {wildPokemon.Name} escaped!);                }                if (_playersCollection.GetCollection().Count > 0)                    Console.WriteLine($\nPokmon in your collection:\n\n{DisplayOwnedPokemon()});                if (!Confirm(\nDo you want to keep catching pokmons?))                    break;                if (!_playersCollection.IsCompleted) continue;                Console.WriteLine(\nYou've caught all wild pokmon!);                break;            }        }        private static bool IsCatchSuccessful()        {            return new Random().Next(100) >= 50;        }        // *** TRAIN POKEMON ***         private static void TrainPokemonTextIntro()        {            Console.WriteLine(\nTime to head to pokmon gym!);            Console.WriteLine(Let's level up some pokmon.);        }        private static void ChoiceTrainPokemon()        {            TrainPokemonTextIntro();            _trainPokemon = true;            while (_trainPokemon)            {                Console.WriteLine($\nPokemon in your collection:\n\n{DisplayOwnedPokemon()});                if (_playersCollection.GetCollection().Count == 1)                {                    if (!GymTeam.Any()) AddSingleDefaultPokemonToTeam(GymTeam);                }                else                {                    SetTeam(GymTeam, 6);                }                TrainChosenTeam();            }            GymTeam.Clear();        }        private static void TrainChosenTeam()        {            do            {                TrainedTeamText();                if (TrainAgain())                {                    if (GymTeam.Count == 1 && _playersCollection.GetCollection().Count == 1)                        break;                    continue;                }                _trainPokemon = false;                break;            } while (TrainSameTeam() && _playersCollection.GetCollection().Count > 1);        }        private static void TrainedTeamText()        {            Console.WriteLine(\nTraining...\n);            Console.ReadKey();            foreach (var pokemon in GymTeam)            {                LevelSystem.IncreasePokemonExp(pokemon);                if (LevelSystem.HasPokemonReachedMaxLevel(pokemon))                    Console.WriteLine(${pokemon.Name} has reached max level! );                else                    Console.WriteLine(LevelSystem.HasPokemonLeveledUp(pokemon)                        ? ${pokemon.Name} has leveled up!                        : ${pokemon.Name} has gained experience!);            }            Console.WriteLine($\n{DisplayTeam(GymTeam)});        }        private static bool TrainAgain()        {            return Confirm(\nDo you want to train again?);        }        private static bool TrainSameTeam()        {            return Confirm(\nDo you want to train the same pokmon?);        }        // *** FIGHT RIVAL ***        private static void FightRivalTextIntro()        {            Console.WriteLine(\nTime to battle!);            Console.WriteLine(Let's become a Pokmon Master.);        }        private static void ChoiceFightRival()        {            FightRivalTextIntro();            _fightRival = true;            while (_fightRival)            {                Console.WriteLine($\nPokmon in your collection:\n\n{DisplayOwnedPokemon()});                if (_playersCollection.GetCollection().Count == 1)                {                    if (!FightingTeam.Any())                        AddSingleDefaultPokemonToTeam(FightingTeam);                }                else                {                    SetTeam(FightingTeam, 4);                }                FightRivalWithChosenTeam();            }            ResetFightRivalVariables();            _fightRival = false;        }        private static void AssignOpponents()        {            foreach (var pokemon in _rivalsCollection.GetTeam())                RivalsTeam.Add(pokemon);            var random = new Random();            foreach (var pokemon in FightingTeam)            {                var opponentId = random.Next(RivalsTeam.Count);                pokemon.Opponent = RivalsTeam[opponentId];                RivalsTeam.RemoveAt(opponentId);            }        }        private static void SetTeamChanceAgainstTheirOpponents()        {            if (FightingTeam == null)                throw new NullReferenceException(Unexpected error occurred. Fighting team is null.);            var chanceByType = new SuccessChanceByPokemonTypes();            foreach (var pokemon in FightingTeam)                pokemon.SuccessChance = chanceByType.GetChance(pokemon.Type, pokemon.Opponent.Type);        }        private static void FightRivalWithChosenTeam()        {            do            {                AssignOpponents();                SetTeamChanceAgainstTheirOpponents();                BattleStartText();                BattleResults();                if (FightRivalAgain())                {                    if (FightingTeam.Count == 1 && _playersCollection.GetCollection().Count == 1)                    {                        ResetFightRivalVariables(true);                        break;                    }                    if (FightingTeam.Count == 1 && !ArePokemonWithValidLevelLeftInCollection())                    {                        ResetFightRivalVariables(true);                        break;                    }                    if (FightRivalWithSameTeam())                    {                        ResetFightRivalVariables(true);                    }                    else                    {                        ResetFightRivalVariables();                        break;                    }                }                else                {                    _fightRival = false;                    break;                }            } while (true);        }        private static bool ArePokemonWithValidLevelLeftInCollection()        {            var pokemonLeftInCollection = _playersCollection.GetCollection().Select(p => p)                .Except(FightingTeam.Select(p => p));            return pokemonLeftInCollection.Any(p => p.Level >= _rivalsCollection.MinLevel);        }        private static void BattleStartText()        {            Console.WriteLine(\nLet's battle!);            Console.ReadKey();            if (FightingTeam == null)                throw new NullReferenceException(Unexpected error occurred. Fighting team is null.);            foreach (var pokemon in FightingTeam)            {                Console.WriteLine($\n{pokemon.Name} was matched against {pokemon.Opponent.Name}.);                Console.WriteLine(${pokemon.Name} is type {GetFormattedTypes(pokemon)},  +                                  $while {pokemon.Opponent.Name} is type {GetFormattedTypes(pokemon.Opponent)}.);                Console.ReadKey();                DisplaySuccessChanceMessage(pokemon, pokemon.SuccessChance);                Console.ReadKey();            }        }        private static void DisplaySuccessChanceMessage(Pokemon pokemon, int chance)        {            switch (chance)            {                case 50:                    Console.WriteLine(${pokemon.Name} will do average damage to {pokemon.Opponent.Name}.);                    Console.WriteLine(It's a 50/50 match!);                    break;                case -1:                    Console.WriteLine(                        ${pokemon.Name} and {pokemon.Opponent.Name} cannot do any damage to each other.);                    Console.WriteLine(It's a tie!);                    break;                case 0:                    Console.WriteLine(${pokemon.Name} cannot do any damage to {pokemon.Opponent.Name}.);                    Console.WriteLine(${pokemon.Name} loses!);                    break;                case 20:                    Console.WriteLine(${pokemon.Name} is weak against {pokemon.Opponent.Name}.);                    Console.WriteLine(${pokemon.Name} has a small chance of winning.);                    break;                case 80:                    Console.WriteLine(${pokemon.Name} is super effective against {pokemon.Opponent.Name}.);                    Console.WriteLine(${pokemon.Name} has a big chance of winning.);                    break;                default:                    throw new Exception(Unexpected error occurred. Pokmon's success chance is an invalid integer.);            }        }        private static void BattleResults()        {            if (IsMatchAutomaticallyOver())            {                DisplayAutomaticMatchResultMessage();            }            else            {                OngoingBattles();                DisplayMatchResults();                DisplayMatchResultMessage();            }        }        // Automatic match result        private static bool IsMatchAutomaticallyATie()        {            return FightingTeam.All(p => p.SuccessChance == -1);        }        private static bool IsMatchAutomaticallyALoss()        {            return FightingTeam.All(p => p.SuccessChance == 0);        }        private static bool IsMatchAutomaticallyOver()        {            return IsMatchAutomaticallyATie() || IsMatchAutomaticallyALoss();        }        private static void DisplayAutomaticMatchResultMessage()        {            if (IsMatchAutomaticallyATie())                Console.WriteLine(\nYour match against Rival ended in a tie.);            else if (IsMatchAutomaticallyALoss())                Console.WriteLine(\nYou lost the match against Rival.);        }        // Random battles result        private static void OngoingBattles()        {            Console.WriteLine(\nFighting...\n);            foreach (var pokemon in FightingTeam)                if (IsDraw(pokemon))                {                    _ties++;                }                else if (IsWin(pokemon))                {                    Console.WriteLine($Cool! {pokemon.Name} won!);                    _wins++;                }                else                {                    Console.WriteLine($Oh no! {pokemon.Name} lost!);                    _losses++;                }        }        private static bool IsDraw(Pokemon pokemon)        {            if (pokemon == null) throw new NullReferenceException(Unexpected error occurred. Pokmon is null.);            return pokemon.SuccessChance == -1;        }        private static bool IsWin(Pokemon pokemon)        {            if (pokemon == null) throw new NullReferenceException(Unexpected error occurred. Pokmon is null.);            var random = new Random();            return random.Next(100) < pokemon.SuccessChance;        }        // Match result        private static void DisplayMatchResults()        {            if (_wins <= 0 && _losses <= 0 && _ties <= 0)                throw new Exception(Unexpected error occurred. Invalid battle results.);            Console.WriteLine($\nWins: {_wins});            Console.WriteLine($Losses: {_losses});            if (_ties > 0)                Console.WriteLine($Ties: {_ties});        }        private static void DisplayMatchResultMessage()        {            Console.WriteLine(_wins == FightingTeam.Count                ? \nCongrats! You won the match against Rival.                 : \nYou did not win the match against Rival.);        }        private static bool FightRivalAgain()        {            return Confirm(\nDo you want to fight this rival again?);        }        private static bool FightRivalWithSameTeam()        {            return Confirm(\nDo you want to fight with the same pokmon?);        }        private static void ResetFightRivalVariables(bool sameTeam = false)        {            if (!sameTeam)                FightingTeam.Clear();            foreach (var pokemon in FightingTeam)            {                pokemon.SuccessChance = 50;                pokemon.Opponent = null;            }            RivalsTeam.Clear();            _wins = 0;            _losses = 0;            _ties = 0;        }        // **** HELPER METHODS ****         private static bool Confirm(string prompt)        {            do            {                Console.WriteLine(${prompt} ({Y}/{N}));                var input = Console.ReadLine();                if (string.Equals(input, Y, StringComparison.InvariantCultureIgnoreCase))                    return true;                if (string.Equals(input, N, StringComparison.InvariantCultureIgnoreCase))                    return false;            } while (true);        }        private static string PokemonInfo(Pokemon pokemon)        {            return pokemon != null                ? ${pokemon.Name} =>  +                  $Type: {GetFormattedTypes(pokemon)} |  +                  $Level: {pokemon.Level} |  +                  $Exp: {pokemon.Exp}                : Unexpected error occurred. Pokmon is null.;        }        private static string GetFormattedTypes(Pokemon pokemon)        {            return string.Join(, , pokemon.Type.Select(t => t.Name));        }        private static string DisplayOwnedPokemon()        {            return string.Join(\n, _playersCollection.GetCollection().Select(PokemonInfo).ToList());        }        private static string DisplayTeam(IEnumerable<Pokemon> team)        {            return string.Join(\n, team.Select(PokemonInfo).ToList());        }        private static void AddSingleDefaultPokemonToTeam(ICollection<Pokemon> team)        {            team.Add(_playersCollection.GetCollection().First());        }        private static void SetTeam(ICollection<Pokemon> team, int max)        {            var isValidPokemon = false;            do            {                if (team.Count == max || team.Count == _playersCollection.GetCollection().Count)                    break;                if (_fightRival)                    if (!ArePokemonWithValidLevelLeftInCollection())                        break;                if (team.Count > 0 && isValidPokemon)                {                    Console.WriteLine($\nCurrent team:\n\n{DisplayTeam(team)});                    if (_fightRival)                        if (!ArePokemonWithValidLevelLeftInCollection())                            break;                    if (!AddAnotherPokemonToTeam())                        break;                }                do                {                    var chosenPokemon = ChosenPokemon(max);                    isValidPokemon = IsChosenPokemonValid(chosenPokemon, team);                    if (isValidPokemon) AddPokemonToTeam(chosenPokemon, team);                } while (!isValidPokemon);            } while (true);        }        private static string ChosenPokemon(int max)        {            Console.WriteLine(                \nType the name of a pokmon from your collection that you'd like to add to your team.);            Console.WriteLine($You must add between 1 and {max} pokmon.\n);            var chosenPokemon = Console.ReadLine();            if (string.IsNullOrWhiteSpace(chosenPokemon)) return ;            chosenPokemon = FormatChosenPokemon(chosenPokemon);            return chosenPokemon;        }        private static string FormatChosenPokemon(string chosenPokemon)        {            chosenPokemon = chosenPokemon.Trim().ToLowerInvariant();            chosenPokemon = chosenPokemon.First().ToString().ToUpperInvariant() + chosenPokemon.Substring(1);            return chosenPokemon;        }        private static bool IsChosenPokemonValid(string chosenPokemon, ICollection<Pokemon> team)        {            if (string.IsNullOrWhiteSpace(chosenPokemon))                return false;            if (_playersCollection.GetCollection().All(p => p.Name != chosenPokemon))                return false;            if (team.All(p => p.Name != chosenPokemon))            {                if (!_fightRival) return true;                var pokemon = _playersCollection.GetCollection().FirstOrDefault(p => p.Name == chosenPokemon);                if (pokemon == null) throw new NullReferenceException(Unexpected error occurred. Pokmon is null.);                var chosenPokemonLvl = pokemon.Level;                if (chosenPokemonLvl >= _rivalsCollection.MinLevel)                    return true;                Console.WriteLine(\nThis pokmon's level is below Rival's level requirement.);                return false;            }            Console.WriteLine($\nYou already added {chosenPokemon}.);            return false;        }        private static void AddPokemonToTeam(string chosenPokemon, ICollection<Pokemon> team)        {            if (chosenPokemon == null)                throw new NullReferenceException(Unexpected error occurred. Null pokmon string given.);            var chosenPokemonFromCollection = _playersCollection.GetCollection()                .SingleOrDefault(p => p.Name == chosenPokemon);            if (chosenPokemonFromCollection == null)                throw new NullReferenceException(Unexpected error occurred. There is no such pokmon in World.);            team.Add(chosenPokemonFromCollection);        }        private static bool AddAnotherPokemonToTeam()        {            return Confirm(\nDo you want to add another pokmon?);        }    }}
Revised version of pokemon console game
c#;beginner;object oriented;console;pokemon
You have a single class for all your methods. This is not an optimal design. You should move members that are responsible for controlling the same thing into separate classes. Every few methods you put comments like // *** MENU *** which are an indicator that a particual group of methods (or other members) should probably have their own class. Currently everything is connected somehow with everything else so it's virtually impossible to test it. They are also all but one method private so they would not be even accessible to tests. You should strive to develop small units that are good at doing something and nothing more. Later you use these units to compose some larger units that can do more and more util you have a complete game.Other then this I think you use very good names and your methods are nice and  small. I didn't check every one of them but they look like they were responsible for doing only one thing, which is again very good. Now you just have to give them a few smaller classes so that you can either test them separately or exchange them if necessary or even reuse them in other projects. Maybe some classes will be so similar that you can use an interface, who knows. I think this is what you should practice next.