+1 (315) 557-6473 

How to Creating a Text-Based Dungeon Game in C#

In this guide, welcome to the Text-Based Dungeon Game programming project! On this page, we will delve into a comprehensive analysis of a C# codebase for a text-based dungeon game. Whether you are just starting your journey in C# programming or seeking inspiration for your next project, this code offers valuable insights into game development and software architecture. We'll break down the code block by block, discussing each component's role, and provide a clear understanding of how this captivating game is crafted. By the end of this guide, you'll have not only a deeper knowledge of C# programming but also a newfound appreciation for the intricate world of text-based game development.

Crafting a C# Text-Based Dungeon Adventure

Explore creating-a-text-based-dungeon-game-in-csharp to help your C# assignment. This comprehensive guide provides step-by-step insights into game development and software architecture, making it an invaluable resource for aspiring programmers. Delve into the intricacies of coding a captivating text-based dungeon game, learn how to structure game elements, and acquire practical knowledge that can enhance your C# programming skills. Whether you're a beginner seeking hands-on experience or a game development enthusiast, this guide opens the doors to endless creative possibilities in the world of C# game development.

Block 1: Namespace and Class Declaration

```csharp using System; using System.Linq; using System.IO; using System.Collections.Generic; using static GameDev.Game; namespace GameDev { // Main class definition for the Dungeon Game Application public class Game { // ... } } ```
  • This block includes necessary C# `using` directives for the required libraries.
  • It defines a `Game` class within the `GameDev` namespace.

Block 2: Game Class Variables and Enums

```csharp using System; namespace GameDev { public class Game { public enum PlayerActions { NOTHING, NORTH, EAST, SOUTH, WEST, PICKUP, ATTACK, DROP, QUIT } private PlayerActions action = PlayerActions.NOTHING; private int playerX; private int playerY; public enum GameState { UNKNOWN, STOP, RUN, START, INIT } private GameState status = GameState.INIT; private char[][] originalMap = new char[0][]; private char[][] workingMap = new char[0][]; private bool advanced = false; private string currentMap; private int coins; private int kills; public string ReadUserInput() { return Console.ReadLine() ?? string.Empty; } public int GetStepCounter() { return counter; } public void ProcessUserInput(string input) { var inputSplit = input.Split(' ', StringSplitOptions.RemoveEmptyEntries); var command = inputSplit.FirstOrDefault()?.ToLower().Trim(); var args = inputSplit.Skip(1).Select(x => x.Trim()).ToArray(); switch (command) { case "load": var mapName = args.FirstOrDefault(); LoadMapFromFile(mapName ?? string.Empty); break; case "begin": if (GameIsRunning() == GameState.RUN) { action = PlayerActions.NOTHING; } else { status = GameState.RUN; counter = 0; var removedS = false; foreach (var tilesY in workingMap) { for (var i = 0; i < tilesY.Length; i++) { var tile = tilesY[i]; if (tile != 'S') continue; removedS = true; tilesY[i] = '_'; break; } if (removedS) break; } var placedPlayer = false; for (var y = 0; y < workingMap.Length; y++) { var tilesY = workingMap[y]; for (var x = 0; x < tilesY.Length; x++) { var tile = tilesY[x]; if (tile != 'S') continue; playerX = x; playerY = y; placedPlayer = true; workingMap[y][x] = '_'; break; } if (placedPlayer) break; } } break; case "w": MovementAction(PlayerActions.NORTH); break; case "a": MovementAction(PlayerActions.WEST); break; case "s": MovementAction(PlayerActions.SOUTH); break; case "d": MovementAction(PlayerActions.EAST); break; case "z": action = PlayerActions.PICKUP; break; case "q": action = PlayerActions.ATTACK; break; } } // Other methods and logic for game functionality public GameState GameIsRunning() { return status; } private void InitializeMap() { status = GameState.START; action = PlayerActions.NOTHING; coins = 0; var placedPlayer = false; for (var y = 0; y < workingMap.Length; y++) { var tilesY = workingMap[y]; for (var x = 0; x < tilesY.Length; x++) { var tile = tilesY[x]; if (tile != 'S') continue; playerX = x; playerY = y; placedPlayer = true; break; } if (placedPlayer) break; } } } }
  • In this block, various class variables and enums are defined.
  • `PlayerActions` is an enum representing player actions.
  • `GameState` is an enum representing game states.
  • Several private variables are used to store player and game state information.

Block 3: ReadUserInput Method

```csharp private string ReadUserInput() { // Read user input from the console, returning an empty string if null return Console.ReadLine() ?? string.Empty; } ```
  • This block defines a private method for reading user input from the console.

Block 4: GetStepCounter Method

```csharp public int GetStepCounter() { return counter; } ```
  • This method returns the number of steps a player has taken on the current map. It increments with each player move.

Block 5: ProcessUserInput Method

```csharp public void ProcessUserInput(string input) { // Split input by space, we get command and args var inputSplit = input.Split(' ', StringSplitOptions.RemoveEmptyEntries); // Command is processed in lowercase to make things easier var command = inputSplit.FirstOrDefault()?.ToLower().Trim(); // Arguments are all trimmed // Also skip the first element as that's the command var args = inputSplit.Skip(1).Select(x => x.Trim()).ToArray(); switch (command) { case "load": // Load map var mapName = args.FirstOrDefault(); LoadMapFromFile(mapName ?? string.Empty); break; case "begin": if (GameIsRunning() == GameState.RUN) { // Game already running, do nothing action = PlayerActions.NOTHING; } else { status = GameState.RUN; counter = 0; var removedS = false; foreach (var tilesY in workingMap) { for (var i = 0; i < tilesY.Length; i++) { var tile = tilesY[i]; if (tile != 'S') continue; removedS = true; tilesY[i] = '_'; break; } if (removedS) break; } var placedPlayer = false; for (var y = 0; y < workingMap.Length; y++) { var tilesY = workingMap[y]; for (var x = 0; x < tilesY.Length; x++) { var tile = tilesY[x]; if (tile != 'S') continue; playerX = x; playerY = y; placedPlayer = true; workingMap[y][x] = '_'; break; } if (placedPlayer) break; } } break; case "w": MovementAction(PlayerActions.NORTH); break; case "a": MovementAction(PlayerActions.WEST); break; case "s": MovementAction(PlayerActions.SOUTH); break; case "d": MovementAction(PlayerActions.EAST); break; case "z": action = PlayerActions.PICKUP; break; case "q": action = PlayerActions.ATTACK; break; } } ```
  • The `ProcessUserInput` method parses user input, interprets commands, and updates the game state accordingly.

Block 6: MovementAction Method

```csharp private void MovementAction(PlayerActions playerAction) { if (GameIsRunning() != GameState.RUN) return; action = playerAction; counter++; } ```
  • This method handles movement-related actions (North, East, South, West).

Block 7: Update Method

```csharp public bool Update(GameState status) { this.status = status; if (status != GameState.RUN) { // If not running, there's no need to update, return false return false; } var pos = GetPlayerPosition(); var currentTile = workingMap[pos[0]][pos[1]]; switch (GetPlayerAction()) { case PlayerActions.NOTHING: break; case PlayerActions.NORTH: playerY--; if (!ValidPlayerMove()) playerY++; break; case PlayerActions.EAST: playerX++; if (!ValidPlayerMove()) playerX--; break; case PlayerActions.SOUTH: playerY++; if (!ValidPlayerMove()) playerY--; break; case PlayerActions.WEST: playerX--; if (!ValidPlayerMove()) playerX++; break; case PlayerActions.PICKUP: if (currentTile == 'C') { // We can pick up a coin coins++; workingMap[pos[0]][pos[1]] = '_'; } break; case PlayerActions.ATTACK: if (currentTile == 'M') { // We can slay the monster kills++; workingMap[pos[0]][pos[1]] = '_'; } break; case PlayerActions.DROP: // Handle dropping an item // ... break; case PlayerActions.QUIT: this.status = GameState.STOP; return false; default: throw new ArgumentOutOfRangeException(); } // The tile could be updated as the player has moved pos = GetPlayerPosition(); currentTile = workingMap[pos[0]][pos[1]]; if (currentTile == 'D') { // Exit, game stops here this.status = GameState.STOP; } return true; } ```
  • The `Update` method updates the game state, handles player actions, and checks game conditions to determine if the game continues.

Block 8: ValidPlayerMove Method

```csharp private bool ValidPlayerMove() { var pos = GetPlayerPosition(); // Check collision return workingMap[pos[0]][pos[1]] != '#'; }
  • This method checks if the player's move is valid by detecting collisions with obstacles in the game map.

Block 9: PrintMapToConsole Method

```csharp public bool PrintMapToConsole() { // Check game status before printing if (status != GameState.RUN) { return false; } Console.Clear(); var map = GetCurrentMapState(); foreach (var yTiles in map) { foreach (var tile in yTiles) { Console.Write(tile); } Console.WriteLine(); } return true; } ```
  • The `PrintMapToConsole` method clears the console and displays the current state of the game map.

Block 10: PrintExtraInfo Method

```csharp public bool PrintExtraInfo() { if (GameIsRunning() != GameState.RUN) return false; Console.WriteLine($"Coins: {coins}, moves: {counter}, kills: {kills}"); return true; } ```
  • This method prints additional information, such as the number of coins collected, moves made, and monsters killed.

Block 11: LoadMapFromFile Method

```csharp public bool LoadMapFromFile(string mapName) { var mapPath = Path.Combine(Environment.CurrentDirectory, mapName); string[] map; try { map = File.ReadAllLines(mapPath); } catch (Exception) { // Couldn't load map file return false; } var mapHeight = map.Length; originalMap = new char[mapHeight][]; workingMap = new char[mapHeight][]; for (var y = 0; y < mapHeight; y++) { var width = map[y].Length; originalMap[y] = new char[width]; for (var x = 0; x < width; x++) { // Pick the correct tile from the map string originalMap[y][x] = map[y][x]; } } // Copy original to working CloneCharArray2D(originalMap, workingMap); InitializeMap(); return true; } // Helper method to clone a 2D char array private static void CloneCharArray2D(char[][] original, char[][] dest) { var height = original.Length; for (var y = 0; y < height; y++) { var line = original[y]; var width = line.Length; dest[y] = new char[width]; Array.Copy(line, dest[y], width); } } ```
  • This method loads a game map from a file, initializes the game state, and handles map-related operations.

Block 12: CloneCharArray2D Method

```csharp private static void CloneCharArray2D(char[][] original, char[][] dest) { var height = original.Length; for (var y = 0; y < height; y++) { var line = original[y]; var width = line.Length; dest[y] = new char[width]; Array.Copy(line, dest[y], width); } } ```
  • This method is a utility function to clone a 2D character array.

Block 13: GetOriginalMap Method

```csharp public char[][] GetOriginalMap() { return originalMap; } ```
  • This method returns the original game map, which doesn't change as the player moves.

Block 14: GetCurrentMapState Method

```csharp public char[][] GetCurrentMapState() { // Don't alter the original, clone it for showing var returningMap = new char[workingMap.Length][]; CloneCharArray2D(workingMap, returningMap); // Only place the player in the map if the game is running if (GameIsRunning() == GameState.RUN) { var pos = GetPlayerPosition(); // Show the player's position returningMap[pos[0]][pos[1]] = '1'; } return returningMap; }
  • This method returns the current state of the game map with the player's position.

Block 15: GetPlayerPosition and GetPlayerAction Methods

```csharp public int[] GetPlayerPosition() { return new[] { playerY, playerX }; } public PlayerActions GetPlayerAction() { return action; } ```
  • These methods return the player's current position and the next player action, respectively.

Block 16: GameIsRunning Method

```csharp public GameState GameIsRunning() { return status; } ```
  • This method returns the current game state (whether the game is running, stopped, etc.).

Block 17: InitializeMap Method

```csharp private void InitializeMap() { status = GameState.START; action = PlayerActions.NOTHING; coins = 0; // Place the player at a valid empty position var placedPlayer = false; for (var y = 0; y < workingMap.Length; y++) { var tilesY = workingMap[y]; for (var x = 0; x < tilesY.Length; x++) { var tile = tilesY[x]; if (tile != 'S') continue; // Found the starting position, move the player there playerX = x; playerY = y; placedPlayer = true; break; } if (placedPlayer) break; } } ```
  • This method initializes the game map and places the player in the starting position.

Block 18: Main Method

```csharp static void Main(string[] args) { Game crawler = new Game(); string input = string.Empty; Console.WriteLine("Welcome to the Commandline Dungeon!" + Environment.NewLine + "May your Quest be filled with riches!" + Environment.NewLine); // Loops through the input and determines when the game should quit while (crawler.GameIsRunning() != GameState.STOP && crawler.GameIsRunning() != GameState.UNKNOWN) { Console.Write("Your Command: "); input = crawler.ReadUserInput(); Console.WriteLine(Environment.NewLine); crawler.ProcessUserInput(input); crawler.Update(crawler.GameIsRunning()); crawler.PrintMapToConsole(); crawler.PrintExtraInfo(); } Console.WriteLine("See you again" + Environment.NewLine + "In the CMD Dungeon! "); } ```
  • This is the main entry point of the program, which sets up and runs the game loop, processes user input, and updates the game state based on user commands.

Conclusion

By breaking down the code into these manageable sections, you gain a better understanding of how the text-based dungeon game is structured and how different components interact to create an engaging gameplay experience. This code serves as a valuable reference for anyone interested in game development, C# programming, or software architecture. Furthermore, the knowledge and skills acquired from this guide can be a stepping stone for your game development journey, enabling you to embark on exciting projects, explore advanced game mechanics, and even create your own unique gaming experiences. As you dive deeper into the world of programming and game design, you'll find that the possibilities are virtually limitless. So, roll up your sleeves, unleash your creativity, and start crafting your very own digital adventures today. The dungeon's gates are open—venture forth and code your way to gaming greatness!