Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monster Maze #98

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/Monster Maze Build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Monster Maze Build
on:
push:
paths:
- 'Projects/MonsterMaze/**'
- '!**.md'
pull_request:
paths:
- 'Projects/MonsterMaze/**'
- '!**.md'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
- run: dotnet build "Projects\MonsterMaze\MonsterMaze.csproj" --configuration Release
10 changes: 10 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -542,5 +542,15 @@
"console": "externalTerminal",
"stopAtEntry": false,
},
{
"name": "Monster Maze",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "Build Monster Maze",
"program": "${workspaceFolder}/Projects/MonsterMaze/bin/Debug/MonsterMaze.dll",
"cwd": "${workspaceFolder}/Projects/MonsterMaze/bin/Debug",
"console": "externalTerminal",
"stopAtEntry": false,
},
],
}
13 changes: 13 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,19 @@
],
"problemMatcher": "$msCompile",
},
{
"label": "Build Monster Maze",
"command": "dotnet",
"type": "process",
"args":
[
"build",
"${workspaceFolder}/Projects/MonsterMaze/MonsterMaze.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary",
],
"problemMatcher": "$msCompile",
},
{
"label": "Build Solution",
"command": "dotnet",
Expand Down
37 changes: 37 additions & 0 deletions Projects/MonsterMaze/DirectionEnum.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
public enum EntityAction
{
None,
Left,
Up,
Right,
Down,
Quit
}

public static class EntityActionExtensions
{
public static EntityAction FromConsoleKey(ConsoleKeyInfo key)
{
return key.Key switch
{
ConsoleKey.LeftArrow => EntityAction.Left,
ConsoleKey.RightArrow => EntityAction.Right,
ConsoleKey.UpArrow => EntityAction.Up,
ConsoleKey.DownArrow => EntityAction.Down,
ConsoleKey.Escape => EntityAction.Quit,
_ => key.KeyChar switch {
'a' => EntityAction.Left,
'A' => EntityAction.Left,
'w' => EntityAction.Up,
'W' => EntityAction.Up,
'd' => EntityAction.Right,
'D' => EntityAction.Right,
's' => EntityAction.Down,
'S' => EntityAction.Down,
'q' => EntityAction.Quit,
'Q' => EntityAction.Quit,
_ => EntityAction.None
}
};
}
}
26 changes: 26 additions & 0 deletions Projects/MonsterMaze/GameUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
public static class GameUtils
{
public static char[,] ConvertToCharMaze(bool[,] maze, char wallCharacter = '#')
{
var result = new char[maze.GetLength(0), maze.GetLength(1)];
for (int i = 0; i < maze.GetLength(0); i++)
{
for (int j = 0; j < maze.GetLength(1); j++)
{
result[i, j] = maze[i, j] ? ' ' : wallCharacter;
}
}
return result;
}

public static bool WaitForEscapeOrSpace()
{
var key = Console.ReadKey(true).Key;
while(key != ConsoleKey.Spacebar && key != ConsoleKey.Escape)
{
key = Console.ReadKey(true).Key;
}
return key == ConsoleKey.Escape;
}
}

28 changes: 28 additions & 0 deletions Projects/MonsterMaze/MazePoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
public struct MazePoint(int x, int y)
{
public int X { get; set; } = x;
public int Y { get; set; } = y;

public override bool Equals(object? obj)
{
if(obj is not MazePoint)
return false;

var other = (MazePoint)obj;
return other.X == X && other.Y == Y;
}
public static bool operator ==(MazePoint left, MazePoint right)
{
return left.Equals(right);
}

public static bool operator !=(MazePoint left, MazePoint right)
{
return !(left == right);
}

public override readonly int GetHashCode()
{
return HashCode.Combine(X, Y);
}
}
140 changes: 140 additions & 0 deletions Projects/MonsterMaze/MazeRecursiveGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
public class MazeRecursiveGenerator
{
public enum MazeMode {
OnePath,
FilledDeadEnds,
Loops
};

private static readonly (int, int)[] Directions = { (0, -1), (1, 0), (0, 1), (-1, 0) }; // Up, Right, Down, Left
private static Random random = new Random();

public static bool[,] GenerateMaze(int width, int height, MazeMode mazeMode = MazeMode.OnePath)
{
if (width % 2 == 0 || height % 2 == 0)
throw new ArgumentException("Width and height must be odd numbers for a proper maze.");

bool[,] maze = new bool[width, height]; // by default, everything is a wall (cell value == false)

// Start the maze generation
GenerateMazeRecursive(maze, 1, 1);

// Make sure the entrance and exit are open
maze[0, 1] = true; // Entrance
maze[width - 1, height - 2] = true; // Exit

if(mazeMode == MazeMode.FilledDeadEnds)
FillDeadEnds(maze);

else if(mazeMode == MazeMode.Loops)
RemoveDeadEnds(maze);

return maze;
}

private static void GenerateMazeRecursive(bool[,] maze, int x, int y)
{
maze[x, y] = true;

// Shuffle directions
var shuffledDirections = ShuffleDirections();

foreach (var (dx, dy) in shuffledDirections)
{
int nx = x + dx * 2;
int ny = y + dy * 2;

// Check if the new position is within bounds and not visited
if (IsInBounds(maze, nx, ny) && !maze[nx, ny])
{
// Carve a path
maze[x + dx, y + dy] = true;
GenerateMazeRecursive(maze, nx, ny);
}
}
}

private static List<(int, int)> ShuffleDirections()
{
var directions = new List<(int, int)>(Directions);
for (int i = directions.Count - 1; i > 0; i--)
{
int j = random.Next(i + 1);
(directions[i], directions[j]) = (directions[j], directions[i]);
}
return directions;
}

private static bool IsInBounds(bool[,] maze, int x, int y)
{
return x > 0 && y > 0 && x < maze.GetLength(0) - 1 && y < maze.GetLength(1) - 1;
}

private static void FillDeadEnds(bool[,] maze)
{
bool removed;
do
{
removed = false;
for (int x = 1; x < maze.GetLength(0) - 1; x++)
{
for (int y = 1; y < maze.GetLength(1) - 1; y++)
{
if (maze[x, y]) // If it's a path
{
int neighbors = 0;
foreach (var (dx, dy) in Directions)
{
if (maze[x + dx, y + dy])
neighbors++;
}
if (neighbors <= 1) // If it's a dead end
{
maze[x, y] = false;
removed = true;
}
}
}
}
} while (removed);
}

private static void RemoveDeadEnds(bool[,] maze)
{
bool removed;
do
{
removed = false;
for (int x = 1; x < maze.GetLength(0) - 1; x++)
{
for (int y = 1; y < maze.GetLength(1) - 1; y++)
{
if (maze[x, y]) // If it's a path
{
int neighbors = 0;
foreach (var (dx, dy) in Directions)
{
if (maze[x + dx, y + dy])
neighbors++;
}
if (neighbors <= 1) // If it's a dead end
{
// Pick a random neighbor to keep open
var shuffledDirections = ShuffleDirections();
foreach(var (dx, dy) in shuffledDirections)
{
if(IsInBounds(maze, x + dx, y + dy) && !maze[x + dx, y + dy])
{
maze[x + dx, y + dy] = true;
break;
}
}
removed = true;
}
}
}
}
} while (removed);
}

}
11 changes: 11 additions & 0 deletions Projects/MonsterMaze/MazeStep.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
public class MazeStep
{
public MazePoint Position {get; set;}
public EntityAction Direction {get; set;}

public MazeStep(MazePoint position, EntityAction direction)
{
Position = position;
Direction = direction;
}
}
18 changes: 18 additions & 0 deletions Projects/MonsterMaze/MonsterMaze.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ApplicationIcon>monstermaze.ico</ApplicationIcon>
<Company></Company>
<Authors>Geoff Thompson (geoff.t.nz2 @ gmail.com)</Authors>
<Description>A console window game, where the player has to escape the maze while being chased by monsters.</Description>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<Optimize>True</Optimize>
<PublishTrimmed>true</PublishTrimmed>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>
</Project>
Loading
Loading