4
\$\begingroup\$

I'm in the alpha stages of development for my console RPG, and I need some input.

My main question is how should I handle attacks? (You'll see what I mean if you look through my world, item, and enemy classes code.)

Here are some code snippets and a link to my full code. Please note that my latest revision has some unfinished code.

Full code: http://code.google.com/p/escape-text-rpg/source/browse/Escape/

Main Class:

using System; using System.IO; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Diagnostics; using System.Reflection; namespace Escape { class Program { #region Declarations private const int Width = 73; private const int Height = 30; private const string saveFile = "save.dat"; public enum GameStates { Start, Playing, Battle, Quit, GameOver }; public static GameStates GameState = GameStates.Start; private static bool run = true; private static bool isError = false; private static List<string> errors = new List<string>(); private static bool isNotification = false; private static List<string> notifications = new List<string>(); public static Random Rand = new Random(); #endregion #region Main public static void Main(string[] args) { Console.WindowWidth = Width; Console.WindowHeight = Height; Console.BufferWidth = Width; Console.BufferHeight = Height; World.Initialize(); while(run) { if (!isError) { if (Player.Health <= 0) { GameState = GameStates.GameOver; } switch (GameState) { case GameStates.Start: StartState(); break; case GameStates.Playing: PlayingState(); break; case GameStates.Battle: BattleState(); break; case GameStates.Quit: QuitState(); break; case GameStates.GameOver: GameOverState(); break; } } else { DisplayError(); } } } #endregion #region GameState Methods private static void StartState() { Text.WriteLine("Hello adventurer! What is your name?"); Player.Name = Text.SetPrompt("> "); Text.Clear(); GameState = GameStates.Playing; } private static void PlayingState() { if (isNotification) { DisplayNotification(); } World.LocationHUD(); string temp = Text.SetPrompt("[" + World.Map[Player.Location].Name + "] > "); Text.Clear(); Player.Do(temp); } private static void BattleState() { if (isNotification) { DisplayNotification(); } BattleCore.BattleHUD(); } private static void QuitState() { Console.Clear(); Text.WriteColor("`r`/-----------------------------------------------------------------------\\", false); Text.WriteColor("|`w` Are you sure you want to quit? (y/n) `r`|", false); Text.WriteColor("\\-----------------------------------------------------------------------/`w`", false); ConsoleKeyInfo quitKey = Console.ReadKey(); if (quitKey.KeyChar == 'y') { run = false; } else { Text.Clear(); GameState = GameStates.Playing; } } private static void GameOverState() { Console.Clear(); Text.WriteColor("`r`/-----------------------------------------------------------------------\\", false); Text.WriteColor("|`w` Game Over! `r`|", false); Text.WriteColor("|`w` Try again? (y/n) `r`|", false); Text.WriteColor("\\-----------------------------------------------------------------------/`w`", false); ConsoleKeyInfo quitKey = Console.ReadKey(); if (quitKey.KeyChar == 'y') { Process.Start(Assembly.GetExecutingAssembly().Location); run = false; } else { run = false; } } #endregion #region Notification Handling private static void DisplayNotification() { Console.CursorTop = Console.WindowHeight - 1; Text.WriteColor("`g`/-----------------------------------------------------------------------\\", false); foreach (string notification in notifications) { List<string> notificationLines = Text.Limit(string.Format("`g`Alert: `w`" + notification), Console.WindowWidth - 4); foreach (string line in notificationLines) { Text.WriteColor("| `w`" + line + Text.BlankSpaces(Console.WindowWidth - Regex.Replace(line, @"`.`", "").Length - 4, true) + "`g` |", false); } } Text.Write("\\-----------------------------------------------------------------------/"); Console.SetCursorPosition(0, 0); UnsetNotification(); } public static void SetNotification(string message) { notifications.Add(message); isNotification = true; } private static void UnsetNotification() { notifications.Clear(); isNotification = false; } #endregion #region Error Handling private static void DisplayError() { Console.CursorTop = Console.WindowHeight - 1; Text.WriteColor("`r`/-----------------------------------------------------------------------\\", false); foreach (string error in errors) { List<string> errorLines = Text.Limit(string.Format("`r`Error: `w`" + error), Console.WindowWidth - 4); foreach (string line in errorLines) { Text.WriteColor("| `w`" + line + Text.BlankSpaces(Console.WindowWidth - Regex.Replace(line, @"`.`", "").Length - 4, true) + "`r` |", false); } } Text.Write("\\-----------------------------------------------------------------------/"); Console.SetCursorPosition(0, 0); UnsetError(); } public static void SetError(string message) { errors.Add(message); isError = true; } private static void UnsetError() { errors.Clear(); isError = false; } #endregion #region Save and Load public static void Save() { SaveGame saveGame = new SaveGame(); try { using (Stream stream = File.Open(saveFile, FileMode.Create)) { BinaryFormatter bin = new BinaryFormatter(); bin.Serialize(stream, saveGame); } Program.SetNotification("Save Successful!"); } catch (AccessViolationException) { Program.SetError("Save Failed! File access denied."); } catch (Exception) { Program.SetError("Save Failed! An unspecified error occurred."); } } public static void Load() { try { using (Stream stream = File.Open(saveFile, FileMode.Open)) { BinaryFormatter bin = new BinaryFormatter(); SaveGame saveGame = (SaveGame)bin.Deserialize(stream); saveGame.Load(); } Program.SetNotification("Load Successful!"); } catch (FileNotFoundException) { Program.SetError("No savegame exists!"); } catch (AccessViolationException) { Program.SetError("Load failed! File access denied."); } catch (Exception) { Program.SetError("Load failed! An unspecified error occurred."); } } #endregion } } 

Player Class:

using System; using System.Collections.Generic; namespace Escape { static class Player { #region Declarations public static string Name; public static int Location = 0; public static int MaxHealth = 100; public static int Health = MaxHealth; public static int MaxMagic = 100; public static int Magic = MaxMagic; public static List<int> Inventory = new List<int>(); #endregion #region Public Methods public static void Do(string aString) { if(aString == "") return; string verb = ""; string noun = ""; if (aString.IndexOf(" ") > 0) { string[] temp = aString.Split(new char[] {' '}, 2); verb = temp[0].ToLower(); noun = temp[1].ToLower(); } else { verb = aString.ToLower(); } switch(Program.GameState) { case Program.GameStates.Playing: switch(verb) { case "help": case "?": WriteCommands(); break; case "exit": case "quit": Program.GameState = Program.GameStates.Quit; break; case "move": case "go": MoveTo(noun); break; case "examine": Examine(noun); break; case "take": case "pickup": Pickup(noun); break; case "drop": case "place": Place(noun); break; case "use": Use(noun); break; case "items": case "inventory": case "inv": DisplayInventory(); break; case "attack": //attack command break; case "hurt": Player.Health -= Convert.ToInt32(noun); break; case "save": Program.Save(); break; case "load": Program.Load(); break; default: InputNotValid(); break; } break; case Program.GameStates.Battle: switch(verb) { case "attack": //attack command break; case "flee": case "escape": //flee command break; case "use": //use command break; case "items": case "inventory": case "inv": //items command break; default: InputNotValid(); break; } break; } } public static void RemoveItemFromInventory(int itemId) { if (ItemIsInInventory(itemId)) { Inventory.Remove(itemId); } } #endregion #region Command Methods private static void WriteCommands() { Text.WriteColor("`g`Available Commands:`w`"); Text.WriteColor("help/? - Display this list."); Text.WriteColor("exit/quit - Exit the game."); Text.WriteColor("move/go <`c`location`w`> - Move to the specified location."); Text.WriteColor("examine <`c`item`w`> - Show info about the specified item."); Text.WriteColor("take/pickuip <`c`item`w`> - Put the specified item in your inventory."); Text.WriteColor("drop/place <`c`item`w`> - Drop the specified item from your inventory and place it in the world."); Text.WriteColor("items/inventory/inv - Display your current inventory."); Text.WriteColor("use <`c`item`w`> - Use the specified item."); Text.WriteColor("save/load - saves/loads the game respectively."); Text.BlankLines(); } private static void MoveTo(string locationName) { if (World.IsLocation(locationName)) { int locationId = World.GetLocationIdByName(locationName); if (World.Map[Location].ContainsExit(locationId)) { Location = locationId; World.Map[Location].CalculateRandomBattle(); } else if (Player.Location == locationId) { Program.SetError("You are already there!"); } else { Program.SetError("You can't get there from here!"); } } else { Program.SetError("That isn't a valid location!"); } } private static void Examine(string itemName) { int itemId = World.GetItemIdByName(itemName); if (World.IsItem(itemName)) { if (World.Map[Location].ContainsItem(itemId) || ItemIsInInventory(itemId)) { World.ItemDescription(itemId); } else { Program.SetError("That item isn't here!"); } } else { Program.SetError("That isn't a valid item!"); } } private static void Pickup(string itemName) { if (World.IsItem(itemName)) { int itemId = World.GetItemIdByName(itemName); if (World.Map[Location].ContainsItem(itemId)) { World.Map[Location].Items.Remove(itemId); Inventory.Add(itemId); Program.SetNotification("You put the " + World.Items[itemId].Name + " in your bag!"); } else { Program.SetError("That item isn't here!"); } } else { Program.SetError("That isn't a valid item!"); } } private static void Place(string itemName) { if (World.IsItem(itemName)) { int itemId = World.GetItemIdByName(itemName); if (ItemIsInInventory(itemId)) { Inventory.Remove(itemId); World.Map[Location].Items.Add(itemId); Program.SetNotification("You placed the " + World.Items[itemId].Name + " in the room!"); } else { Program.SetError("You aren't holding that item!"); } } else { Program.SetError("That isn't a valid item!"); } } private static void Use(string itemName) { if (World.IsItem(itemName)) { int itemId = World.GetItemIdByName(itemName); if (ItemIsInInventory(itemId)) { World.Items[itemId].Use(); } else { Program.SetError("You aren't holding that item!"); } } else { Program.SetError("That isn't a valid item!"); } } private static void DisplayInventory() { if (Inventory.Count <= 0) { Program.SetNotification("You aren't carrying anything!"); return; } Text.WriteColor("`m`/-----------------\\"); Text.WriteColor("|`w` Inventory `m`|"); Text.WriteLine(">-----------------<"); for (int i = 0; i < Inventory.Count; i++) { string name = World.Items[Inventory[i]].Name; Text.WriteColor("|`w` " + name + Text.BlankSpaces(16 - name.Length, true) + "`m`|"); } Text.WriteColor("\\-----------------/`w`"); Text.BlankLines(); } #endregion #region Helper Methods private static void InputNotValid() { Program.SetError("That isn't a valid command!"); } private static bool ItemIsInInventory(int itemId) { if (Inventory.Contains(itemId)) return true; else return false; } #endregion } } 

World Class:

using System; using System.Collections.Generic; namespace Escape { static class World { #region Declarations public static List<Location> Map = new List<Location>(); public static List<Item> Items = new List<Item>(); public static List<Enemy> Enemies = new List<Enemy>(); #endregion #region Initialization public static void Initialize() { GenerateWorld(); GenerateItems(); GenerateEnemies(); } #endregion #region World Generation Methods private static void GenerateWorld() { Map.Add(new Location( "Room 1", "This is a room.", new List<int>() {1}, new List<int>() {0, 2})); Map.Add(new Location( "Room 2", "This is another room.", new List<int>() {0, 2}, new List<int>() {1}, new List<int>() {0}, 50)); Map.Add(new Location( "Room 3", "This is yet another room.", new List<int>() {1}, new List<int>(), new List<int>() {0, 1}, 75)); Map.Add(new Location( "Secret Room", "This is a very awesome secret room.", new List<int>() {2})); } private static void GenerateItems() { Items.Add(new Key( "Brass Key", "Just your generic key thats in almost every game.", 2, 3, true)); Items.Add(new ShinyStone( "Shiny Stone", "Its a stone, and its shiny, what more could you ask for?")); Items.Add(new Rock( "Rock", "It doesn't do anything, however, it is said that the mystical game designer used this for testing.")); } private static void GenerateEnemies() { Enemies.Add(new Rat( "Rat", "Its just a pwesious wittle wat that will KILL YOU!", new List<int>() {10, 3, 5}, new List<int>() {0, 1})); Enemies.Add(new Hawk( "Hawk", "It flies around looking for prey to feed on.", new List<int>() {15, 5, 0}, new List<int>() {2, 3})); } #endregion #region Public Location Methods public static bool IsLocation(string locationName) { for (int i = 0; i < Map.Count; i++) { if (Map[i].Name.ToLower() == locationName.ToLower()) return true; } return false; } public static int GetLocationIdByName(string locationName) { for (int i = 0; i < Map.Count; i++) { if (Map[i].Name.ToLower() == locationName.ToLower()) return i; } return -1; } public static void LocationHUD() { Text.WriteColor("`c`/-----------------------------------------------------------------------\\", false); List<string> locationDesctiption = Text.Limit(Map[Player.Location].Description, Console.WindowWidth - 4); foreach (string line in locationDesctiption) { Text.WriteColor("| `w`" + line + Text.BlankSpaces((Console.WindowWidth - line.Length - 4), true) + "`c` |", false); } Text.WriteColor(">-----------------v-----------------v-----------------v-----------------<", false); Text.WriteColor("| `w`Exits`c` | `w`Items`c` | `w`People`c` | `w`Stats`c` |", false); Text.WriteColor(">-----------------#-----------------#-----------------#-----------------<`w`", false); int currentY = Console.CursorTop; int i; int longestList = 0; for (i = 0; i < Map[Player.Location].Exits.Count; i++) { string name = Map[Map[Player.Location].Exits[i]].Name; Text.WriteColor(" " + name); } longestList = (i > longestList) ? i : longestList; Console.SetCursorPosition(18, currentY); for (i = 0; i < Map[Player.Location].Items.Count; i++) { string name = Items[Map[Player.Location].Items[i]].Name; Text.WriteColor(" " + name); } longestList = (i > longestList) ? i : longestList; Console.SetCursorPosition(36, currentY); for (i = 0; i < Map[Player.Location].Enemies.Count; i++) { string name = Enemies[Map[Player.Location].Enemies[i]].Name; Text.WriteColor(" " + name); } longestList = (i > longestList) ? i : longestList; Console.SetCursorPosition(54, currentY); Text.WriteColor(" HP [`r`" + Text.ToBar(Player.Health, Player.MaxHealth, 10) + "`w`]"); Text.WriteColor(" MP [`g`" + Text.ToBar(Player.Magic, Player.MaxMagic, 10) + "`w`]"); longestList = (2 > longestList) ? 2 : longestList; Console.SetCursorPosition(0, currentY); for (i = 0; i < longestList; i++) { for (int j = 0; j < 4; j++) { Text.WriteColor("`c`|", false); Console.CursorLeft += 17; } Text.Write("|"); Console.CursorLeft = 0; } Text.WriteColor("\\-----------------^-----------------^-----------------^-----------------/`w`"); } #endregion #region Public Item Methods public static bool IsItem(string itemName) { for (int i = 0; i < Items.Count; i++) { if (Items[i].Name.ToLower() == itemName.ToLower()) return true; } return false; } public static int GetItemIdByName(string itemName) { for (int i = 0; i < Items.Count; i++) { if (Items[i].Name.ToLower() == itemName.ToLower()) return i; } return -1; } public static void ItemDescription(int itemId) { Text.WriteLine(Items[itemId].Description); Text.BlankLines(); } #endregion } } 
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

Well I have a few comments on the code, specifically dealing with the Player class.

  1. I would break up the Do method into a few methods. You could add a static method to parse the action/verb string.
  2. You could consider changing the verb data into an enum and then user Enum.TryParse to convert the string to the Enum values. That would make the switch statement a little more readable (IMHO).
  3. Currently the MaxHealth and MaxMagic are public static ints, which means they could be changed later. Now if the player can level up that would be okay, except that these values are static and so if they change for one player, they change for all. They seem more like instance values.
  4. Player class has a static location, this means when one player moves, all players move to that location. This should be instance data.
  5. Speaking of player, you may want to make a base character class that can be shared between players and enemies. Containing HP/MP and Inventory/weapons/etc. This will allow more code reuse. Enemies will have location/HP/MP, Players have them, if you have other characters that will interact they will have some of that information (maybe not HP/MP but they will have location).

Edit - Code sample added. Note. I think it would be better to store a list of Items for the player in their inventory as opposed to just an ID. If you want to go the ID route then you should really create an enum for the items and then in the world class or some other more global class maintain a Dictionary that maps the Enum to the Item object (to make easy retrieval of item information). I also added a ParseCommand method that you can call from the Do method. I am sorry that I don't have more time to work on this.:

[DataContract] [KnownType(typeof(Player))] [KnownType(typeof(Enemy))] abstract class Entity { #region Declarations [DataMember] public string Name { get; protected set; } [DataMember] public string Description { get; protected set; } [DataMember] public int Health { get; protected set; } [DataMember] public int Magic { get; protected set; } #endregion #region Constructor public Entity(string name, string description, int health, int magic) { Name = name; Description = description; Health = health; Magic = magic; } #endregion } [DataContract] public class Player : Entity { #region Constants private const DefaultMaxHealth = 100; private const DefatulMaxMagic = 100; #endregion #region Declarations [DataMember] public int Location {get; protected set;} [DataMember] public int MaxHealth {get; protected set; } [DataMember] public int MaxMagic {get; protected set; } [DataMember] public List<Item> Inventory = new List<Item>(); #endregion #region Constructor public Player(string name) : base(name, "This is the player", DefaultMaxHealth, DefaultMaxMagic) { MaxHealth = DefaultMaxHealth; MaxMagic = DefaultMaxMagic; } #endregion private static Tuple<string, string> ParseCommand(string command) { var commands = command.Split(new char[] {' '}, 2, StringSplitOptions.RemoveEmptyEntries); var verb = commands[0].ToLower(); var noun = string.Empty; if (commands.Length > 1) { noun = commands[1].ToLower(); } return new Tuple<string, string>(verb, noun); } 

Edit 2 - Some comments on the Location class/code in general There are lots of Magic Numbers (see also this SO post. Instead of a Location maintaining a List<int> for the possible exits, consider maintaining a List<Location> for possible exits. Instead of a List<int> of items, consider a List<Item> for the items. This will make the code easier to maintain in the long run. When debugging you won't be going back and forth trying to match up the int with the Item it corresponds to.

The locations all have a name and it appears in the World class that you lookup valid locations via a string match. You should consider storing all the valid locations in a Dictionary that uses a string as the key. You could even define a case insensitive string comparison for the key and then all you need to do to validate a location would be to check if the dictionary contains the key. This will be more efficient than walking a List each time. You can also validate not only that it is an actual location, but you could then change the Location class to store a HashSet of Locations and then pass that into the validation method and use the TryGet method of the dictionary class to not only get the location, but then check if the HashSet contains it thus returning true if the location exists and is reachable from the current location.

\$\endgroup\$
3
  • \$\begingroup\$ All these are great points. In terms of the player class' static fields, there will never be more than one player in the game, so it doesn't matter. And yes, the player's max's can be leveled up. I will probably add the location/HP/MP stats to the entity class, and make them optional parameters. \$\endgroup\$ Commented Jun 22, 2012 at 21:14
  • \$\begingroup\$ Oh also, I'll most likely accept your answer, but I don't want to mark this question as answered yet so I can get more feedback. :) \$\endgroup\$ Commented Jun 22, 2012 at 21:15
  • 2
    \$\begingroup\$ @ryansworld10 no worries. I would definitely leave it open for a little while to get more feedback. If I have some time over the weekend I will try to add some more suggestions with code samples. \$\endgroup\$ Commented Jun 22, 2012 at 21:17

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.