From 2c4fa99635ae4a9548433b5a7a0da3cd496fdbcb Mon Sep 17 00:00:00 2001 From: Corin Hyndman Date: Sun, 12 Jun 2022 12:26:20 +0100 Subject: [PATCH 1/7] Added Duck Hunt --- Projects/Duck Hunt/Duck Hunt.csproj | 11 + Projects/Duck Hunt/Program.cs | 627 ++++++++++++++++++++++++++++ 2 files changed, 638 insertions(+) create mode 100644 Projects/Duck Hunt/Duck Hunt.csproj create mode 100644 Projects/Duck Hunt/Program.cs diff --git a/Projects/Duck Hunt/Duck Hunt.csproj b/Projects/Duck Hunt/Duck Hunt.csproj new file mode 100644 index 00000000..55c25229 --- /dev/null +++ b/Projects/Duck Hunt/Duck Hunt.csproj @@ -0,0 +1,11 @@ + + + + Exe + net6.0 + Duck_Hunt + disable + enable + + + diff --git a/Projects/Duck Hunt/Program.cs b/Projects/Duck Hunt/Program.cs new file mode 100644 index 00000000..871db7e5 --- /dev/null +++ b/Projects/Duck Hunt/Program.cs @@ -0,0 +1,627 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + +if (Console.WindowWidth < 80 || + Console.WindowHeight < 25) +{ + Console.WriteLine("Console Size is too small!"); + Console.WriteLine("Minimum required Width: 80 Height: 25"); + Console.WriteLine("Recommended Width: 120 Height: 30"); +} + +Console.WriteLine("Warning! Game can be inefficient on large console sizes"); +Thread.Sleep(TimeSpan.FromSeconds(2)); + +// Variable game settings +double gunXStretch = 1; +int crosshairSpeed = 2; +bool gunEnabled = true; +bool bulletsEnabled = false; +bool gunOutlineEnabled = false; + +const char NULL_CHAR = '\0'; +const char EMPTY_CHAR = '-'; +const int BARREL_LENGTH = 10; + +bool inMenu = false; +bool fireGun = false; +bool gunSelected = true; +bool crosshairSelected = false; +int frame = 0; +int score = 0; +int ammoCount = 5; +int spawnDelay = 100; +int grassLevel = Console.WindowHeight - 3; +char[,] screenBuffer = new char[Console.WindowWidth, Console.WindowHeight]; +Random rng = new(); +List birds = new(); +List bullets = new(); +StringBuilder screenGraphic = new(); +Point crosshair = new(Console.WindowWidth / 2, Console.WindowHeight / 3); +Point LeftAncor = new(Console.WindowWidth / 2 - 3, Console.WindowHeight - 1); +Point middleAncor = new(Console.WindowWidth / 2, Console.WindowHeight - 1); +Point RightAncor = new(Console.WindowWidth / 2 + 3, Console.WindowHeight - 1); + +Console.CursorVisible = false; + +try +{ + while (ammoCount > 0) + { + while (inMenu) + { + Console.Clear(); + + string menuDisplay = + "Press Corresponding Number to Edit/Select variables" + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + $"[1] Gun X Axis Stretch: {gunXStretch:F} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + $"[2] Crosshair Movement Speed: {crosshairSpeed} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + $"[3] Bullets Enabled: {bulletsEnabled} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + $"[4] Gun Outline Mode Enabled: {gunOutlineEnabled} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + $"[5] Gun Enabled: {gunEnabled} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + "Currently selected variable " + (gunSelected ? "[1]" : "[2] ") + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + "Press [^] arrow to increase and [v] arrow to decrease "; + + DrawToScreenWithColour(Console.WindowWidth / 4, Console.WindowHeight / 4, ConsoleColor.Yellow, menuDisplay.ToCharArray()); + DrawToScreenWithColour(0, 0, ConsoleColor.White, ("[ESC] Quit" + Sprites.NEWLINE_CHAR + "[ENTER] Exit Menu").ToCharArray()); + + switch (Console.ReadKey(true).Key) + { + case ConsoleKey.D1: gunSelected = true; crosshairSelected = false; break; + case ConsoleKey.D2: gunSelected = false; crosshairSelected = true; break; + case ConsoleKey.D3: bulletsEnabled = !bulletsEnabled; break; + case ConsoleKey.D4: gunOutlineEnabled = !gunOutlineEnabled; break; + case ConsoleKey.D5: gunEnabled = !gunEnabled; break; + case ConsoleKey.Enter: inMenu = false; continue; + case ConsoleKey.Escape: return; + + case ConsoleKey.UpArrow: + if (gunSelected) + { + gunXStretch += 0.1; + } + else + { + crosshairSpeed++; + } + break; + case ConsoleKey.DownArrow: + if (gunSelected) + { + gunXStretch -= 0.1; + } + else + { + crosshairSpeed--; + } + break; + } + } + + if (Console.KeyAvailable) + { + switch (Console.ReadKey(true).Key) + { + case ConsoleKey.UpArrow: crosshair.Y -= crosshairSpeed; break; + case ConsoleKey.DownArrow: crosshair.Y += crosshairSpeed; break; + case ConsoleKey.LeftArrow: crosshair.X -= crosshairSpeed; break; + case ConsoleKey.RightArrow: crosshair.X += crosshairSpeed; break; + case ConsoleKey.Spacebar: fireGun = true; break; + case ConsoleKey.Enter: inMenu = true; continue; + case ConsoleKey.Escape: return; + } + + if (crosshair.X < 2) + { + crosshair.X += crosshairSpeed; + } + if (crosshair.X > Console.WindowWidth - Sprites.Enviroment.CrosshairWidth + +2) + { + crosshair.X -= crosshairSpeed; + } + + if (crosshair.Y < 2) + { + crosshair.Y += crosshairSpeed; + } + if (crosshair.Y > Console.WindowHeight - Sprites.Enviroment.CrosshairHeight) + { + crosshair.Y -= crosshairSpeed; + } + } + while (Console.KeyAvailable) + { + Console.ReadKey(true); + } + + WriteToBuffer(0, grassLevel, Sprites.Enviroment.Grass); + WriteToBuffer(Sprites.Enviroment.TreeWidth - Sprites.Enviroment.TreeWidth / 2, grassLevel - Sprites.Enviroment.TreeHeight, Sprites.Enviroment.Tree); + WriteToBuffer(Console.WindowWidth - Sprites.Enviroment.BushWidth * 2, grassLevel - Sprites.Enviroment.BushHeight, Sprites.Enviroment.Bush); + WriteToBuffer(0, 0, "[ENTER] Menu".ToCharArray()); + + double theta = Math.Atan2(middleAncor.Y - crosshair.Y, middleAncor.X - crosshair.X); + int xGunOffset = -(int)Math.Floor(Math.Cos(theta) * BARREL_LENGTH); + int yGunOffset = -(int)Math.Floor(Math.Sin(theta) * BARREL_LENGTH); + Point gunTopOffset = new((int)(xGunOffset * gunXStretch), yGunOffset); + + if (gunEnabled) + { + if (gunOutlineEnabled) + { + DrawLine(RightAncor, RightAncor + gunTopOffset); + DrawLine(LeftAncor, LeftAncor + gunTopOffset); + DrawLine(RightAncor + gunTopOffset, LeftAncor + gunTopOffset); + } + else + { + for (int i = LeftAncor.X; i <= RightAncor.X; i++) + { + Point gunBottomOffset = new(i, middleAncor.Y); + DrawLine(gunBottomOffset, gunBottomOffset + gunTopOffset); + } + } + } + + DrawToScreen(screenBuffer); + DrawGUI(); + + if (bulletsEnabled) + { + if (fireGun) + { + bullets.Add(new Bullet(middleAncor + gunTopOffset, theta)); + } + + for (int i = 0; i < bullets.Count; i++) + { + bullets[i].UpdatePosition(); + + if (bullets[i].OutOfBounds) + { + bullets.RemoveAt(i); + continue; + } + + foreach (Bird bird in birds) + { + if (!bird.IsDead && + (bird.Contains((int)bullets[i].X[0], (int)bullets[i].Y[0]) || + bird.Contains((int)bullets[i].X[1], (int)bullets[i].Y[1]))) + { + bird.IsDead = true; + ammoCount += 2; + score += 350; + } + } + + DrawToScreenWithColour((int)bullets[i].X[0], (int)bullets[i].Y[0], ConsoleColor.DarkGray, '█'); + DrawToScreenWithColour((int)bullets[i].X[1], (int)bullets[i].Y[1], ConsoleColor.DarkGray, '█'); + } + } + else + { + if (fireGun && ammoCount > 0) + { + foreach (Bird bird in birds) + { + if (!bird.IsDead && bird.Contains(crosshair.X, crosshair.Y)) + { + bird.IsDead = true; + ammoCount += 2; + score += 150; + } + } + ammoCount--; + } + } + + fireGun = false; + + foreach (Bird bird in birds) + { + DrawToScreenWithColour(bird.X, bird.Y, ConsoleColor.Red, bird.Direction is -1 ? Sprites.Bird.LeftSprites[bird.Frame] : Sprites.Bird.RightSprites[bird.Frame]); + if (frame % 2 is 0) + { + bird.IncrementFrame(); + if (bird.IsDead) + { + bird.Y++; + } + else + { + bird.X += bird.Direction; + } + } + } + + for (int i = birds.Count; i-- > 0;) + { + if (birds[i].Y > Console.WindowHeight) + { + birds.RemoveAt(i); + } + } + + if (frame % spawnDelay is 0) + { + if (rng.Next(50) > 25) + { + birds.Add(new Bird(Console.WindowWidth, rng.Next(1, grassLevel - Sprites.Bird.Height), -1)); + } + else + { + birds.Add(new Bird(-Sprites.Bird.Width, rng.Next(1, grassLevel - Sprites.Bird.Height), 1)); + } + if (spawnDelay > 60) + { + spawnDelay--; + } + } + + if (ammoCount > 5) + { + ammoCount = 5; + } + + DrawToScreenWithColour(crosshair.X - Sprites.Enviroment.CrosshairHeight / 2, crosshair.Y - Sprites.Enviroment.CrosshairWidth / 2, fireGun ? ConsoleColor.DarkYellow : ConsoleColor.Blue, Sprites.Enviroment.Crosshair); + frame++; + } + + Console.ForegroundColor = ConsoleColor.Yellow; + Console.SetCursorPosition(Console.WindowWidth / 2 - 4, Console.WindowHeight / 2); + Console.WriteLine("Game Over!"); + Console.SetCursorPosition(Console.WindowWidth / 2 - 3, Console.WindowHeight / 2 + 1); + Console.WriteLine($"Score: {score}"); + Console.SetCursorPosition(Console.WindowWidth / 2 - 9, Console.WindowHeight / 2 + 2); + Console.WriteLine("Press [ESC] to quit"); + + while (Console.ReadKey(true).Key != ConsoleKey.Escape) + { + continue; + } + + void DrawGUI() + { + int x = Console.WindowWidth - 18; + int y = grassLevel; + + string topFrame = "╔" + new string('═', 17); + string ammoFrame = string.Format("║ Ammo:{0,-10}", string.Concat(Enumerable.Repeat(" |", ammoCount))); + string scoreFrame = string.Format("║ Score: {0,-9}", score); + + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.SetCursorPosition(x, y); + Console.Write(topFrame); + Console.SetCursorPosition(x, y + 1); + Console.Write(ammoFrame); + Console.SetCursorPosition(x, y + 2); + Console.Write(scoreFrame); + Console.ForegroundColor = ConsoleColor.White; + } + void DrawLine(Point start, Point end) + { + /// Bresenhams line algorithm + int x = start.X; + int y = start.Y; + int dx = Math.Abs(start.X - end.X); + int dy = -Math.Abs(start.Y - end.Y); + int sx = start.X < end.X ? 1 : -1; + int sy = start.Y < end.Y ? 1 : -1; + int error = dx + dy; + while (true) + { + WriteToBuffer(x, y, '▓'); // ░▒▓█ + + if (x == end.X && y == end.Y) + { + return; + } + + float error2 = error * 2; + if (error2 >= dy) + { + if (x == end.X) + { + break; + } + + error += dy; + x += sx; + } + if (error2 <= dx) + { + if (y == end.Y) + { + break; + } + + error += dx; + y += sy; + } + } + } + void DrawToScreen(char[,] array) + { + for (int y = 0; y < Console.WindowHeight; y++) + { + for (int x = 0; x < Console.WindowWidth; x++) + { + if (array[x, y] is NULL_CHAR) + { + screenGraphic.Append(' '); + } + else + { + screenGraphic.Append(array[x, y]); + } + } + } + Console.SetCursorPosition(0, 0); + Console.Write(screenGraphic); + + Array.Clear(screenBuffer, 0, screenBuffer.Length); + screenGraphic.Clear(); + } + void WriteToBuffer(int xPos, int yPos, params char[] characters) + { + int x = xPos; + int y = yPos; + for (int i = 0; i < characters.Length; i++) + { + if (characters[i] == Sprites.NEWLINE_CHAR) + { + y++; + x = xPos; + continue; + } + if (char.IsWhiteSpace(characters[i]) && screenBuffer[x, y] != NULL_CHAR && !char.IsWhiteSpace(screenBuffer[x, y])) + { + x++; + continue; + } + + screenBuffer[x, y] = characters[i]; + x++; + } + } + void DrawToScreenWithColour(int xPos, int yPos, ConsoleColor colour, params char[] characters) + { + int x = xPos; + int y = yPos; + Console.ForegroundColor = colour; + + for (int i = 0; i < characters.Length; i++) + { + if (characters[i] == Sprites.NEWLINE_CHAR) + { + y++; + x = xPos; + continue; + } + + if (char.IsWhiteSpace(characters[i])) + { + x++; + continue; + } + + if (x >= 0 && x < Console.WindowWidth && + y >= 0 && y < Console.WindowHeight) + { + if (characters[i] is EMPTY_CHAR) + { + Console.SetCursorPosition(x, y); + Console.Write(' '); + } + else + { + Console.SetCursorPosition(x, y); + Console.Write(characters[i]); + } + } + + x++; + } + + Console.ForegroundColor = ConsoleColor.White; + } +} +finally +{ + Console.CursorVisible = true; + Console.ResetColor(); + Console.Clear(); +} +struct Point +{ + public int X; + public int Y; + public Point(int x, int y) + { + X = x; + Y = y; + } + public static Point operator +(Point a, Point b) + => new Point(a.X + b.X, a.Y + b.Y); +} +class Bird +{ + public int X; + public int Y; + public int Frame = 0; + public int Direction = 0; + public bool IsDead = false; + public Bird(int x, int y, int direction) + { + X = x; + Y = y; + Direction = direction; + } + public void IncrementFrame() + { + if (IsDead) + { + Frame = 4; + } + else + { + Frame++; + Frame %= 4; + } + } + public bool Contains(int x, int y) + { + return + (x >= X) && + (y >= Y) && + (y < Y + Sprites.Bird.Height) && + (x < X + Sprites.Bird.Width); + } +} +class Bullet +{ + public bool OutOfBounds = false; + public double[] X = new double[2]; + public double[] Y = new double[2]; + + private double XOffset; + private double YOffset; + public Bullet(Point position, double angle) + { + for (int i = 0; i < 2; i++) + { + X[i] = position.X; + Y[i] = position.Y; + } + + XOffset = -Math.Cos(angle); + YOffset = -Math.Sin(angle); + } + public void UpdatePosition() + { + X[1] = X[0]; + Y[1] = Y[0]; + + X[0] += XOffset; + Y[0] += YOffset; + + if (X[0] < 0 || X[0] >= Console.WindowWidth || + Y[0] < 0 || Y[0] >= Console.WindowHeight) + { + OutOfBounds = true; + } + } +} +static class Sprites +{ + public readonly static char NEWLINE_CHAR = '\n'; + public readonly static int SPRITE_MAXWIDTH = Console.WindowWidth; + + public static class Enviroment + { + #region Ascii Sprites + public static char[] Grass = + (new string('V', SPRITE_MAXWIDTH) + NEWLINE_CHAR + + new string('M', SPRITE_MAXWIDTH) + NEWLINE_CHAR + + new string('V', SPRITE_MAXWIDTH)).ToCharArray(); + + public static char[] Crosshair = + (@" │ " + NEWLINE_CHAR + + @" ┌│┐ " + NEWLINE_CHAR + + @"──O──" + NEWLINE_CHAR + + @" └│┘ " + NEWLINE_CHAR + + @" │ ").ToCharArray(); + public static int CrosshairHeight = 5; + public static int CrosshairWidth = 5; + + public static char[] Bush = + (@" (}{{}}} " + NEWLINE_CHAR + + @" {}}{{}'}} " + NEWLINE_CHAR + + @"{{}}}{{}}}{}{" + NEWLINE_CHAR + + @"){}(}'{}}}{}}" + NEWLINE_CHAR + + @"){}(}{{}}}{})" + NEWLINE_CHAR + + @" {}}}{{}}}{} ").ToCharArray(); + public static int BushHeight = 6; + public static int BushWidth = 13; + + public static char[] Tree = + (@" #### " + NEWLINE_CHAR + + @" ###### " + NEWLINE_CHAR + + @" ###### " + NEWLINE_CHAR + + @" #### #### " + NEWLINE_CHAR + + @" || ###### " + NEWLINE_CHAR + + @" || /#### " + NEWLINE_CHAR + + @" ####/ " + NEWLINE_CHAR + + @" ###### " + NEWLINE_CHAR + + @" #### #### #### " + NEWLINE_CHAR + + @"###### |||| ######" + NEWLINE_CHAR + + @" #### |||| ######" + NEWLINE_CHAR + + @" \\ |||| //#### " + NEWLINE_CHAR + + @" \\|||| // " + NEWLINE_CHAR + + @" \||||// " + NEWLINE_CHAR + + @" ||||/ " + NEWLINE_CHAR + + @" |||| " + NEWLINE_CHAR + + @" |||| " + NEWLINE_CHAR + + @" |||| " + NEWLINE_CHAR + + @" |||| " + NEWLINE_CHAR + + @" |||| ").ToCharArray(); + public static int TreeHeight = 20; + public static int TreeWidth = 20; + } + + public static class Bird + { + public static char[][] LeftSprites = + { ( @" _(nn)_ " + NEWLINE_CHAR + + @"<(o----_)=" + NEWLINE_CHAR + + @" (UU) ").ToCharArray(), + + ( @" ______ " + NEWLINE_CHAR + + @"<(o(UU)_)=" + NEWLINE_CHAR + + @" ").ToCharArray(), + + ( @" _(nn)_ " + NEWLINE_CHAR + + @"<(o----_)=" + NEWLINE_CHAR + + @" (UU) ").ToCharArray(), + + ( @" ______ " + NEWLINE_CHAR + + @"<(o(UU)_)=" + NEWLINE_CHAR + + @" ").ToCharArray(), + + ( @" _ " + NEWLINE_CHAR + + @" _<(x)__ " + NEWLINE_CHAR + + @"(--(-)--)" + NEWLINE_CHAR + + @"(__(_)__)" + NEWLINE_CHAR + + @" _/ \_ " ).ToCharArray() + }; + public static char[][] RightSprites = + { ( @" _(nn)_ " + NEWLINE_CHAR + + @"=(_----o)>" + NEWLINE_CHAR + + @" (UU) ").ToCharArray(), + + ( @" ______ " + NEWLINE_CHAR + + @"=(_(UU)o)>" + NEWLINE_CHAR + + @" ").ToCharArray(), + + ( @" _(nn)_ " + NEWLINE_CHAR + + @"=(_----o)>" + NEWLINE_CHAR + + @" (UU) ").ToCharArray(), + + ( @" ______ " + NEWLINE_CHAR + + @"=(_(UU)o)>" + NEWLINE_CHAR + + @" ").ToCharArray(), + + ( @" _ " + NEWLINE_CHAR + + @" __(x)>_ " + NEWLINE_CHAR + + @"(--(-)--)" + NEWLINE_CHAR + + @"(__(_)__)" + NEWLINE_CHAR + + @" _/ \_ " ).ToCharArray() + }; + public static int Height = 3; + public static int Width = 10; + #endregion + } +} \ No newline at end of file From e1966257d8fe5bf874686a292c3a43cbb4cdf22b Mon Sep 17 00:00:00 2001 From: Corin Hyndman Date: Sun, 12 Jun 2022 12:26:47 +0100 Subject: [PATCH 2/7] Added Game --- dotnet-console-games-and-website.sln | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dotnet-console-games-and-website.sln b/dotnet-console-games-and-website.sln index 73d08fa4..d5ecaefd 100644 --- a/dotnet-console-games-and-website.sln +++ b/dotnet-console-games-and-website.sln @@ -85,6 +85,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tower Of Hanoi", "Projects\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tug Of War", "Projects\Tug Of War\Tug Of War.csproj", "{19407C10-9FC5-4614-8846-702621836FC3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duck Hunt", "Projects\Duck Hunt\Duck Hunt.csproj", "{B52224E3-87D4-441B-B5BF-32382DC54C9E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -255,6 +257,10 @@ Global {19407C10-9FC5-4614-8846-702621836FC3}.Debug|Any CPU.Build.0 = Debug|Any CPU {19407C10-9FC5-4614-8846-702621836FC3}.Release|Any CPU.ActiveCfg = Release|Any CPU {19407C10-9FC5-4614-8846-702621836FC3}.Release|Any CPU.Build.0 = Release|Any CPU + {B52224E3-87D4-441B-B5BF-32382DC54C9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B52224E3-87D4-441B-B5BF-32382DC54C9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B52224E3-87D4-441B-B5BF-32382DC54C9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B52224E3-87D4-441B-B5BF-32382DC54C9E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 35a76353a17f3b757cc019d94ffab12eb919eb2a Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Sun, 12 Jun 2022 09:29:20 -0400 Subject: [PATCH 3/7] Duck Hunt additional files --- .github/workflows/Duck Hunt Build.yml | 20 +++++++++++++++++ .vscode/launch.json | 10 +++++++++ .vscode/tasks.json | 13 +++++++++++ Projects/Duck Hunt/README.md | 31 +++++++++++++++++++++++++++ README.md | 1 + dotnet-console-games-and-website.sln | 2 +- dotnet-console-games.slnf | 1 + 7 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/Duck Hunt Build.yml create mode 100644 Projects/Duck Hunt/README.md diff --git a/.github/workflows/Duck Hunt Build.yml b/.github/workflows/Duck Hunt Build.yml new file mode 100644 index 00000000..52592060 --- /dev/null +++ b/.github/workflows/Duck Hunt Build.yml @@ -0,0 +1,20 @@ +name: Duck Hunt Build +on: + push: + paths: + - 'Projects/Duck Hunt/**' + pull_request: + paths: + - 'Projects/Duck Hunt/**' + workflow_dispatch: +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: setup dotnet + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 6.0.x + - name: dotnet build + run: dotnet build "Projects\Duck Hunt\Duck Hunt.csproj" --configuration Release diff --git a/.vscode/launch.json b/.vscode/launch.json index 94148a11..ff6845ec 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -352,6 +352,16 @@ "console": "externalTerminal", "stopAtEntry": false, }, + { + "name": "Duck Hunt", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "Build Duck Hunt", + "program": "${workspaceFolder}/Projects/Duck Hunt/bin/Debug/Duck Hunt.dll", + "cwd": "${workspaceFolder}/Projects/Duck Hunt/bin/Debug", + "console": "externalTerminal", + "stopAtEntry": false, + }, { "name": "Blackjack", "type": "coreclr", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 28ebbefb..2aa4809e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,6 +2,19 @@ "version": "2.0.0", "tasks": [ + { + "label": "Build Duck Hunt", + "command": "dotnet", + "type": "process", + "args": + [ + "build", + "${workspaceFolder}/Projects/Duck Hunt/Duck Hunt.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary", + ], + "problemMatcher": "$msCompile", + }, { "label": "Build Tug Of War", "command": "dotnet", diff --git a/Projects/Duck Hunt/README.md b/Projects/Duck Hunt/README.md new file mode 100644 index 00000000..21d39274 --- /dev/null +++ b/Projects/Duck Hunt/README.md @@ -0,0 +1,31 @@ +

+ Duck Hunt +

+ +

+ flat + Language C# + Target Framework + Build + Discord + +

+ +**[Source Code](Program.cs)** + +Duck Hunt is a first person game where you aim your gun and shoot ducks. + +## Input + +- `↑`, `↓`, `←`, `→` (arrow keys): aim your gun +- `spacebar`: shoot gun + +

+ You can play this game in your browser: +
+ + Play Now + +
+ Hosted On GitHub Pages +

diff --git a/README.md b/README.md index 6c941756..345b2c06 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ |[Bound](https://github.com/ZacharyPatten/dotnet-console-games/blob/main/Projects/Bound)|4|Play Now Go to Action| |[Tents](https://github.com/ZacharyPatten/dotnet-console-games/blob/main/Projects/Tents)|4|Play Now Go to Action| |[Battleship](https://github.com/ZacharyPatten/dotnet-console-games/blob/main/Projects/Battleship)|4|Play Now Go to Action| +|[Duck Hunt](https://github.com/ZacharyPatten/dotnet-console-games/blob/main/Projects/Duck%20Hunt)|5|Play Now Go to Action
*Community Contribution| |[Blackjack](https://github.com/ZacharyPatten/dotnet-console-games/blob/main/Projects/Blackjack)|5|Play Now Go to Action| |[Fighter](https://github.com/ZacharyPatten/dotnet-console-games/blob/main/Projects/Fighter)|5|Play Now Go to Action| |[Maze](https://github.com/ZacharyPatten/dotnet-console-games/blob/main/Projects/Maze)|5|Play Now Go to Action| diff --git a/dotnet-console-games-and-website.sln b/dotnet-console-games-and-website.sln index d5ecaefd..1feedfde 100644 --- a/dotnet-console-games-and-website.sln +++ b/dotnet-console-games-and-website.sln @@ -85,7 +85,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tower Of Hanoi", "Projects\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tug Of War", "Projects\Tug Of War\Tug Of War.csproj", "{19407C10-9FC5-4614-8846-702621836FC3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duck Hunt", "Projects\Duck Hunt\Duck Hunt.csproj", "{B52224E3-87D4-441B-B5BF-32382DC54C9E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duck Hunt", "Projects\Duck Hunt\Duck Hunt.csproj", "{B52224E3-87D4-441B-B5BF-32382DC54C9E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/dotnet-console-games.slnf b/dotnet-console-games.slnf index 3eee43fa..85430ada 100644 --- a/dotnet-console-games.slnf +++ b/dotnet-console-games.slnf @@ -11,6 +11,7 @@ "Projects\\Dice Game\\Dice Game.csproj", "Projects\\Draw\\Draw.csproj", "Projects\\Drive\\Drive.csproj", + "Projects\\Duck Hunt\\Duck Hunt.csproj", "Projects\\Fighter\\Fighter.csproj", "Projects\\Flappy Bird\\Flappy Bird.csproj", "Projects\\Guess A Number\\Guess A Number.csproj", From 1d403e725c192d3aba392021bfbe495745354715 Mon Sep 17 00:00:00 2001 From: Corin Hyndman Date: Tue, 14 Jun 2022 16:11:56 +0100 Subject: [PATCH 4/7] added general optimizations --- Projects/Duck Hunt/Program.cs | 143 +++++++++++++++++++++------------- 1 file changed, 91 insertions(+), 52 deletions(-) diff --git a/Projects/Duck Hunt/Program.cs b/Projects/Duck Hunt/Program.cs index 871db7e5..2cb91bba 100644 --- a/Projects/Duck Hunt/Program.cs +++ b/Projects/Duck Hunt/Program.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; @@ -16,6 +17,7 @@ Thread.Sleep(TimeSpan.FromSeconds(2)); // Variable game settings +int gameDelay = 30; double gunXStretch = 1; int crosshairSpeed = 2; bool gunEnabled = true; @@ -30,51 +32,60 @@ bool fireGun = false; bool gunSelected = true; bool crosshairSelected = false; +bool gameOver = false; int frame = 0; int score = 0; int ammoCount = 5; int spawnDelay = 100; -int grassLevel = Console.WindowHeight - 3; -char[,] screenBuffer = new char[Console.WindowWidth, Console.WindowHeight]; +int grassLevel = Sprites.ScreenHeight - 4; +char[,] screenBuffer = new char[Sprites.ScreenWidth, Sprites.ScreenHeight]; Random rng = new(); List birds = new(); List bullets = new(); StringBuilder screenGraphic = new(); -Point crosshair = new(Console.WindowWidth / 2, Console.WindowHeight / 3); -Point LeftAncor = new(Console.WindowWidth / 2 - 3, Console.WindowHeight - 1); -Point middleAncor = new(Console.WindowWidth / 2, Console.WindowHeight - 1); -Point RightAncor = new(Console.WindowWidth / 2 + 3, Console.WindowHeight - 1); +Stopwatch timer = new(); +Point crosshair = new(Sprites.ScreenWidth / 2, Sprites.ScreenHeight / 3); +Point LeftAncor = new(Sprites.ScreenWidth / 2 - 3, Sprites.ScreenHeight - 2); +Point middleAncor = new(Sprites.ScreenWidth / 2, Sprites.ScreenHeight - 2); +Point RightAncor = new(Sprites.ScreenWidth / 2 + 3, Sprites.ScreenHeight - 2); Console.CursorVisible = false; try { - while (ammoCount > 0) + timer.Start(); + while (!gameOver) { - while (inMenu) + Console.Title = $"FPS: {(int)(frame / timer.Elapsed.TotalSeconds)}"; + + if (inMenu) { Console.Clear(); - + } + while (inMenu) + { string menuDisplay = "Press Corresponding Number to Edit/Select variables" + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + - $"[1] Gun X Axis Stretch: {gunXStretch:F} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + - $"[2] Crosshair Movement Speed: {crosshairSpeed} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + - $"[3] Bullets Enabled: {bulletsEnabled} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + - $"[4] Gun Outline Mode Enabled: {gunOutlineEnabled} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + - $"[5] Gun Enabled: {gunEnabled} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + - "Currently selected variable " + (gunSelected ? "[1]" : "[2] ") + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + " Currently selected variable: " + (gunSelected ? "[1]" : crosshairSelected ? "[2]" : "[3]") + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + $"[1] Gun X Axis Stretch: {gunXStretch:F} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + $"[2] Crosshair Movement Speed: {crosshairSpeed} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + $"[3] Game Delay (Milliseconds): {gameDelay} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + $"[4] Bullets Enabled: {bulletsEnabled}- " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + $"[5] Gun Outline Mode Enabled: {gunOutlineEnabled}- " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + $"[6] Gun Enabled: {gunEnabled}- " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + "Press [^] arrow to increase and [v] arrow to decrease "; - DrawToScreenWithColour(Console.WindowWidth / 4, Console.WindowHeight / 4, ConsoleColor.Yellow, menuDisplay.ToCharArray()); - DrawToScreenWithColour(0, 0, ConsoleColor.White, ("[ESC] Quit" + Sprites.NEWLINE_CHAR + "[ENTER] Exit Menu").ToCharArray()); - + DrawToScreenWithColour(1, 4, ConsoleColor.Yellow, menuDisplay.ToCharArray()); + DrawToScreenWithColour(1, 1, ConsoleColor.White, ("[ESC] Quit" + Sprites.NEWLINE_CHAR + "[ENTER] Exit Menu").ToCharArray()); + switch (Console.ReadKey(true).Key) { case ConsoleKey.D1: gunSelected = true; crosshairSelected = false; break; case ConsoleKey.D2: gunSelected = false; crosshairSelected = true; break; - case ConsoleKey.D3: bulletsEnabled = !bulletsEnabled; break; - case ConsoleKey.D4: gunOutlineEnabled = !gunOutlineEnabled; break; - case ConsoleKey.D5: gunEnabled = !gunEnabled; break; + case ConsoleKey.D3: gunSelected = false; crosshairSelected = false; break; + case ConsoleKey.D4: bulletsEnabled = !bulletsEnabled; break; + case ConsoleKey.D5: gunOutlineEnabled = !gunOutlineEnabled; break; + case ConsoleKey.D6: gunEnabled = !gunEnabled; break; case ConsoleKey.Enter: inMenu = false; continue; case ConsoleKey.Escape: return; @@ -83,22 +94,33 @@ { gunXStretch += 0.1; } - else + else if (crosshairSelected) { crosshairSpeed++; } + else + { + gameDelay++; + } break; case ConsoleKey.DownArrow: if (gunSelected) { gunXStretch -= 0.1; } - else + else if (crosshairSelected) { crosshairSpeed--; } + else + { + gameDelay--; + } break; } + + timer.Restart(); + frame = 0; } if (Console.KeyAvailable) @@ -118,7 +140,7 @@ { crosshair.X += crosshairSpeed; } - if (crosshair.X > Console.WindowWidth - Sprites.Enviroment.CrosshairWidth + +2) + if (crosshair.X > Sprites.ScreenWidth - Sprites.Enviroment.CrosshairWidth + 2) { crosshair.X -= crosshairSpeed; } @@ -127,7 +149,7 @@ { crosshair.Y += crosshairSpeed; } - if (crosshair.Y > Console.WindowHeight - Sprites.Enviroment.CrosshairHeight) + if (crosshair.Y > Sprites.ScreenHeight - Sprites.Enviroment.CrosshairHeight) { crosshair.Y -= crosshairSpeed; } @@ -137,10 +159,11 @@ Console.ReadKey(true); } - WriteToBuffer(0, grassLevel, Sprites.Enviroment.Grass); + WriteToBuffer(0, 0, Sprites.Border); + WriteToBuffer(1, grassLevel, Sprites.Enviroment.Grass); WriteToBuffer(Sprites.Enviroment.TreeWidth - Sprites.Enviroment.TreeWidth / 2, grassLevel - Sprites.Enviroment.TreeHeight, Sprites.Enviroment.Tree); - WriteToBuffer(Console.WindowWidth - Sprites.Enviroment.BushWidth * 2, grassLevel - Sprites.Enviroment.BushHeight, Sprites.Enviroment.Bush); - WriteToBuffer(0, 0, "[ENTER] Menu".ToCharArray()); + WriteToBuffer(Sprites.ScreenWidth - Sprites.Enviroment.BushWidth * 2, grassLevel - Sprites.Enviroment.BushHeight, Sprites.Enviroment.Bush); + WriteToBuffer(1, 1, "[ENTER] Menu".ToCharArray()); double theta = Math.Atan2(middleAncor.Y - crosshair.Y, middleAncor.X - crosshair.X); int xGunOffset = -(int)Math.Floor(Math.Cos(theta) * BARREL_LENGTH); @@ -173,6 +196,7 @@ if (fireGun) { bullets.Add(new Bullet(middleAncor + gunTopOffset, theta)); + ammoCount--; } for (int i = 0; i < bullets.Count; i++) @@ -188,7 +212,7 @@ foreach (Bird bird in birds) { if (!bird.IsDead && - (bird.Contains((int)bullets[i].X[0], (int)bullets[i].Y[0]) || + (bird.Contains((int)bullets[i].X[0], (int)bullets[i].Y[0]) || bird.Contains((int)bullets[i].X[1], (int)bullets[i].Y[1]))) { bird.IsDead = true; @@ -239,7 +263,9 @@ for (int i = birds.Count; i-- > 0;) { - if (birds[i].Y > Console.WindowHeight) + if (birds[i].Y > Sprites.ScreenHeight || + (birds[i].Direction is -1 && birds[i].X < -Sprites.Bird.Width) || + (birds[i].Direction is 1 && birds[i].X > Sprites.ScreenWidth + Sprites.Bird.Width)) { birds.RemoveAt(i); } @@ -249,7 +275,7 @@ { if (rng.Next(50) > 25) { - birds.Add(new Bird(Console.WindowWidth, rng.Next(1, grassLevel - Sprites.Bird.Height), -1)); + birds.Add(new Bird(Sprites.ScreenWidth, rng.Next(1, grassLevel - Sprites.Bird.Height), -1)); } else { @@ -267,15 +293,18 @@ } DrawToScreenWithColour(crosshair.X - Sprites.Enviroment.CrosshairHeight / 2, crosshair.Y - Sprites.Enviroment.CrosshairWidth / 2, fireGun ? ConsoleColor.DarkYellow : ConsoleColor.Blue, Sprites.Enviroment.Crosshair); + Thread.Sleep(TimeSpan.FromMilliseconds(gameDelay)); frame++; + + gameOver = ammoCount is 0 && bullets.Count is 0; } Console.ForegroundColor = ConsoleColor.Yellow; - Console.SetCursorPosition(Console.WindowWidth / 2 - 4, Console.WindowHeight / 2); - Console.WriteLine("Game Over!"); - Console.SetCursorPosition(Console.WindowWidth / 2 - 3, Console.WindowHeight / 2 + 1); + Console.SetCursorPosition(1, 1); + Console.WriteLine("Game Over! "); + Console.SetCursorPosition(1, 2); Console.WriteLine($"Score: {score}"); - Console.SetCursorPosition(Console.WindowWidth / 2 - 9, Console.WindowHeight / 2 + 2); + Console.SetCursorPosition(1, 3); Console.WriteLine("Press [ESC] to quit"); while (Console.ReadKey(true).Key != ConsoleKey.Escape) @@ -285,21 +314,22 @@ void DrawGUI() { - int x = Console.WindowWidth - 18; + int x = Sprites.ScreenWidth - 19; int y = grassLevel; - string topFrame = "╔" + new string('═', 17); - string ammoFrame = string.Format("║ Ammo:{0,-10}", string.Concat(Enumerable.Repeat(" |", ammoCount))); - string scoreFrame = string.Format("║ Score: {0,-9}", score); + string topFrame = '╔' + new string('═', 17) + '╣'; + string ammoFrame = string.Format("║ Ammo:{0,-10}", string.Concat(Enumerable.Repeat(" |", ammoCount))) + '║'; + string scoreFrame = string.Format("║ Score: {0,-9}", score) + '║'; + string bottomFrame = '╩' + new string('═', 17) + '╝'; - Console.ForegroundColor = ConsoleColor.DarkGray; Console.SetCursorPosition(x, y); Console.Write(topFrame); - Console.SetCursorPosition(x, y + 1); + Console.SetCursorPosition(x, ++y); Console.Write(ammoFrame); - Console.SetCursorPosition(x, y + 2); + Console.SetCursorPosition(x, ++y); Console.Write(scoreFrame); - Console.ForegroundColor = ConsoleColor.White; + Console.SetCursorPosition(x, ++y); + Console.Write(bottomFrame); } void DrawLine(Point start, Point end) { @@ -345,9 +375,9 @@ void DrawLine(Point start, Point end) } void DrawToScreen(char[,] array) { - for (int y = 0; y < Console.WindowHeight; y++) + for (int y = 0; y < Sprites.ScreenHeight; y++) { - for (int x = 0; x < Console.WindowWidth; x++) + for (int x = 0; x < Sprites.ScreenWidth; x++) { if (array[x, y] is NULL_CHAR) { @@ -362,7 +392,7 @@ void DrawToScreen(char[,] array) Console.SetCursorPosition(0, 0); Console.Write(screenGraphic); - Array.Clear(screenBuffer, 0, screenBuffer.Length); + screenBuffer = new char[Sprites.ScreenWidth, Sprites.ScreenHeight]; //Array.Clear(screenBuffer, 0, screenBuffer.Length); screenGraphic.Clear(); } void WriteToBuffer(int xPos, int yPos, params char[] characters) @@ -408,8 +438,8 @@ void DrawToScreenWithColour(int xPos, int yPos, ConsoleColor colour, params char continue; } - if (x >= 0 && x < Console.WindowWidth && - y >= 0 && y < Console.WindowHeight) + if (x >= 1 && x < Sprites.ScreenWidth - 1 && + y >= 1 && y < Sprites.ScreenHeight - 1) { if (characters[i] is EMPTY_CHAR) { @@ -518,18 +548,27 @@ public void UpdatePosition() static class Sprites { public readonly static char NEWLINE_CHAR = '\n'; - public readonly static int SPRITE_MAXWIDTH = Console.WindowWidth; + public readonly static int ScreenWidth = Console.WindowWidth; + public readonly static int ScreenHeight = Console.WindowHeight; + public readonly static int SPRITE_MAXWIDTH = ScreenWidth - 2; + public readonly static int SPRITE_MAXHEIGHT = ScreenHeight - 2; + + private static string middleBorder = "║" + new string(' ', SPRITE_MAXWIDTH) + "║" + NEWLINE_CHAR; + public static char[] Border = + ("╔" + new string('═', SPRITE_MAXWIDTH) + "╗" + NEWLINE_CHAR + + string.Concat(Enumerable.Repeat(middleBorder, SPRITE_MAXHEIGHT)) + + "╚" + new string('═', SPRITE_MAXWIDTH) + "╝").ToCharArray(); public static class Enviroment { #region Ascii Sprites public static char[] Grass = - (new string('V', SPRITE_MAXWIDTH) + NEWLINE_CHAR + + ( new string('V', SPRITE_MAXWIDTH) + NEWLINE_CHAR + new string('M', SPRITE_MAXWIDTH) + NEWLINE_CHAR + new string('V', SPRITE_MAXWIDTH)).ToCharArray(); public static char[] Crosshair = - (@" │ " + NEWLINE_CHAR + + ( @" │ " + NEWLINE_CHAR + @" ┌│┐ " + NEWLINE_CHAR + @"──O──" + NEWLINE_CHAR + @" └│┘ " + NEWLINE_CHAR + @@ -538,7 +577,7 @@ public static class Enviroment public static int CrosshairWidth = 5; public static char[] Bush = - (@" (}{{}}} " + NEWLINE_CHAR + + ( @" (}{{}}} " + NEWLINE_CHAR + @" {}}{{}'}} " + NEWLINE_CHAR + @"{{}}}{{}}}{}{" + NEWLINE_CHAR + @"){}(}'{}}}{}}" + NEWLINE_CHAR + From 5892af68b17b612d6c7a17b5066ccde9a9438f9a Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 14 Jun 2022 19:19:56 -0400 Subject: [PATCH 5/7] welcome screen & prevent crash on window resize --- Projects/Duck Hunt/Program.cs | 281 ++++++++++++++++++++++------------ 1 file changed, 182 insertions(+), 99 deletions(-) diff --git a/Projects/Duck Hunt/Program.cs b/Projects/Duck Hunt/Program.cs index 2cb91bba..21552d09 100644 --- a/Projects/Duck Hunt/Program.cs +++ b/Projects/Duck Hunt/Program.cs @@ -5,57 +5,128 @@ using System.Text; using System.Threading; -if (Console.WindowWidth < 80 || - Console.WindowHeight < 25) -{ - Console.WriteLine("Console Size is too small!"); - Console.WriteLine("Minimum required Width: 80 Height: 25"); - Console.WriteLine("Recommended Width: 120 Height: 30"); -} - -Console.WriteLine("Warning! Game can be inefficient on large console sizes"); -Thread.Sleep(TimeSpan.FromSeconds(2)); - -// Variable game settings -int gameDelay = 30; -double gunXStretch = 1; -int crosshairSpeed = 2; -bool gunEnabled = true; -bool bulletsEnabled = false; -bool gunOutlineEnabled = false; - const char NULL_CHAR = '\0'; const char EMPTY_CHAR = '-'; const int BARREL_LENGTH = 10; -bool inMenu = false; -bool fireGun = false; -bool gunSelected = true; -bool crosshairSelected = false; -bool gameOver = false; -int frame = 0; -int score = 0; -int ammoCount = 5; -int spawnDelay = 100; -int grassLevel = Sprites.ScreenHeight - 4; -char[,] screenBuffer = new char[Sprites.ScreenWidth, Sprites.ScreenHeight]; -Random rng = new(); -List birds = new(); -List bullets = new(); -StringBuilder screenGraphic = new(); -Stopwatch timer = new(); -Point crosshair = new(Sprites.ScreenWidth / 2, Sprites.ScreenHeight / 3); -Point LeftAncor = new(Sprites.ScreenWidth / 2 - 3, Sprites.ScreenHeight - 2); -Point middleAncor = new(Sprites.ScreenWidth / 2, Sprites.ScreenHeight - 2); -Point RightAncor = new(Sprites.ScreenWidth / 2 + 3, Sprites.ScreenHeight - 2); - -Console.CursorVisible = false; +// Menu Settings +int gameDelay; +double gunXStretch; +int crosshairSpeed; +bool gunEnabled; +bool bulletsEnabled; +bool gunOutlineEnabled; + +bool inMenu; +bool fireGun; +bool gunSelected; +bool crosshairSelected; +bool gameOver; +int frame; +int score; +int ammoCount; +int spawnDelay; +int grassLevel; +char[,] screenBuffer; +Random rng; +List birds; +List bullets; +StringBuilder screenGraphic; +Stopwatch timer; +Point crosshair; +Point LeftAncor; +Point middleAncor; +Point RightAncor; try { - timer.Start(); + // Welcome Screen + Console.CursorVisible = false; + Console.WriteLine(); + Console.WriteLine(" Duck Hunt"); + Console.WriteLine(); + Console.WriteLine(" Shoot the ducks! Lose ammo missing shots. Run"); + Console.WriteLine(" out of ammo and it is game over."); + Console.WriteLine(); + Console.WriteLine(" Controls"); + Console.WriteLine(" - arrow keys: aim"); + Console.WriteLine(" - spacebar: fire"); + Console.WriteLine(" - enter: open/close menu"); + Console.WriteLine(" - 1-6: adjust settings in menu"); + Console.WriteLine(" - escape: exit game"); + Console.WriteLine(); + Console.WriteLine(" Recommended Window Size: 120 width x 30 height"); + Console.WriteLine(); + Console.WriteLine(" Press any key to begin..."); + Console.ReadKey(true); + Console.CursorVisible = false; + + // Initialization + { + gameDelay = 30; + gunXStretch = 1; + crosshairSpeed = 2; + gunEnabled = true; + bulletsEnabled = false; + gunOutlineEnabled = false; + + inMenu = false; + fireGun = false; + gunSelected = true; + crosshairSelected = false; + gameOver = false; + frame = 0; + score = 0; + ammoCount = 5; + spawnDelay = 100; + grassLevel = Sprites.ScreenHeight - 4; + screenBuffer = new char[Sprites.ScreenWidth, Sprites.ScreenHeight]; + rng = new(); + birds = new(); + bullets = new(); + screenGraphic = new(); + timer = new(); + crosshair = new(Sprites.ScreenWidth / 2, Sprites.ScreenHeight / 3); + LeftAncor = new(Sprites.ScreenWidth / 2 - 3, Sprites.ScreenHeight - 2); + middleAncor = new(Sprites.ScreenWidth / 2, Sprites.ScreenHeight - 2); + RightAncor = new(Sprites.ScreenWidth / 2 + 3, Sprites.ScreenHeight - 2); + timer.Restart(); + } + + // Main Game Loop while (!gameOver) { + if (Sprites.ScreenWidth != Console.WindowWidth - 1 || + Sprites.ScreenHeight != Console.WindowHeight) + { + if (OperatingSystem.IsWindows()) + { + Retry: + try + { + Console.BufferWidth = Console.WindowWidth; + Console.BufferHeight = Console.WindowHeight; + } + catch + { + Console.Clear(); + goto Retry; + } + } + + Sprites.ScreenWidth = Console.WindowWidth - 1; + Sprites.ScreenHeight = Console.WindowHeight; + screenBuffer = new char[Sprites.ScreenWidth, Sprites.ScreenHeight]; + grassLevel = Sprites.ScreenHeight - 4; + LeftAncor = new(Sprites.ScreenWidth / 2 - 3, Sprites.ScreenHeight - 2); + middleAncor = new(Sprites.ScreenWidth / 2, Sprites.ScreenHeight - 2); + RightAncor = new(Sprites.ScreenWidth / 2 + 3, Sprites.ScreenHeight - 2); + crosshair.X = Math.Min(Sprites.ScreenWidth - Sprites.Enviroment.CrosshairWidth + 2, Math.Max(crosshair.X, 2)); + crosshair.Y = Math.Min(Sprites.ScreenHeight - Sprites.Enviroment.CrosshairHeight, Math.Max(crosshair.Y, 2)); + Console.CursorVisible = false; + Console.Clear(); + } + Console.Title = $"FPS: {(int)(frame / timer.Elapsed.TotalSeconds)}"; if (inMenu) @@ -65,8 +136,8 @@ while (inMenu) { string menuDisplay = - "Press Corresponding Number to Edit/Select variables" + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + - " Currently selected variable: " + (gunSelected ? "[1]" : crosshairSelected ? "[2]" : "[3]") + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + "Press Corresponding Number to Edit/Select variables" + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + " Currently selected variable: " + (gunSelected ? "[1]" : crosshairSelected ? "[2]" : "[3]") + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + $"[1] Gun X Axis Stretch: {gunXStretch:F} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + $"[2] Crosshair Movement Speed: {crosshairSpeed} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + $"[3] Game Delay (Milliseconds): {gameDelay} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + @@ -77,7 +148,7 @@ DrawToScreenWithColour(1, 4, ConsoleColor.Yellow, menuDisplay.ToCharArray()); DrawToScreenWithColour(1, 1, ConsoleColor.White, ("[ESC] Quit" + Sprites.NEWLINE_CHAR + "[ENTER] Exit Menu").ToCharArray()); - + switch (Console.ReadKey(true).Key) { case ConsoleKey.D1: gunSelected = true; crosshairSelected = false; break; @@ -136,23 +207,8 @@ case ConsoleKey.Escape: return; } - if (crosshair.X < 2) - { - crosshair.X += crosshairSpeed; - } - if (crosshair.X > Sprites.ScreenWidth - Sprites.Enviroment.CrosshairWidth + 2) - { - crosshair.X -= crosshairSpeed; - } - - if (crosshair.Y < 2) - { - crosshair.Y += crosshairSpeed; - } - if (crosshair.Y > Sprites.ScreenHeight - Sprites.Enviroment.CrosshairHeight) - { - crosshair.Y -= crosshairSpeed; - } + crosshair.X = Math.Min(Sprites.ScreenWidth - Sprites.Enviroment.CrosshairWidth + 2, Math.Max(crosshair.X, 2)); + crosshair.Y = Math.Min(Sprites.ScreenHeight - Sprites.Enviroment.CrosshairHeight, Math.Max(crosshair.Y, 2)); } while (Console.KeyAvailable) { @@ -212,7 +268,7 @@ foreach (Bird bird in birds) { if (!bird.IsDead && - (bird.Contains((int)bullets[i].X[0], (int)bullets[i].Y[0]) || + (bird.Contains((int)bullets[i].X[0], (int)bullets[i].Y[0]) || bird.Contains((int)bullets[i].X[1], (int)bullets[i].Y[1]))) { bird.IsDead = true; @@ -261,11 +317,11 @@ } } - for (int i = birds.Count; i-- > 0;) + for (int i = birds.Count - 1; i >= 0; i--) { if (birds[i].Y > Sprites.ScreenHeight || - (birds[i].Direction is -1 && birds[i].X < -Sprites.Bird.Width) || - (birds[i].Direction is 1 && birds[i].X > Sprites.ScreenWidth + Sprites.Bird.Width)) + (birds[i].Direction is -1 && birds[i].X < -Sprites.Bird.Width) || + (birds[i].Direction is 1 && birds[i].X > Sprites.ScreenWidth + Sprites.Bird.Width)) { birds.RemoveAt(i); } @@ -321,16 +377,23 @@ void DrawGUI() string ammoFrame = string.Format("║ Ammo:{0,-10}", string.Concat(Enumerable.Repeat(" |", ammoCount))) + '║'; string scoreFrame = string.Format("║ Score: {0,-9}", score) + '║'; string bottomFrame = '╩' + new string('═', 17) + '╝'; - - Console.SetCursorPosition(x, y); - Console.Write(topFrame); - Console.SetCursorPosition(x, ++y); - Console.Write(ammoFrame); - Console.SetCursorPosition(x, ++y); - Console.Write(scoreFrame); - Console.SetCursorPosition(x, ++y); - Console.Write(bottomFrame); + try + { + Console.SetCursorPosition(x, y); + Console.Write(topFrame); + Console.SetCursorPosition(x, ++y); + Console.Write(ammoFrame); + Console.SetCursorPosition(x, ++y); + Console.Write(scoreFrame); + Console.SetCursorPosition(x, ++y); + Console.Write(bottomFrame); + } + catch //(IndexOutOfRangeException) + { + // user is likely resizing the console window + } } + void DrawLine(Point start, Point end) { /// Bresenhams line algorithm @@ -388,6 +451,10 @@ void DrawToScreen(char[,] array) screenGraphic.Append(array[x, y]); } } + if (y < Sprites.ScreenHeight - 1) + { + screenGraphic.AppendLine(); + } } Console.SetCursorPosition(0, 0); Console.Write(screenGraphic); @@ -395,28 +462,30 @@ void DrawToScreen(char[,] array) screenBuffer = new char[Sprites.ScreenWidth, Sprites.ScreenHeight]; //Array.Clear(screenBuffer, 0, screenBuffer.Length); screenGraphic.Clear(); } + void WriteToBuffer(int xPos, int yPos, params char[] characters) { int x = xPos; int y = yPos; for (int i = 0; i < characters.Length; i++) { - if (characters[i] == Sprites.NEWLINE_CHAR) + if (characters[i] is Sprites.NEWLINE_CHAR) { y++; x = xPos; - continue; } - if (char.IsWhiteSpace(characters[i]) && screenBuffer[x, y] != NULL_CHAR && !char.IsWhiteSpace(screenBuffer[x, y])) + else if (x < 0 || y < 0 || x >= screenBuffer.GetLength(0) || y >= screenBuffer.GetLength(1)) { x++; - continue; } - - screenBuffer[x, y] = characters[i]; - x++; + else + { + screenBuffer[x, y] = characters[i]; + x++; + } } } + void DrawToScreenWithColour(int xPos, int yPos, ConsoleColor colour, params char[] characters) { int x = xPos; @@ -443,13 +512,21 @@ void DrawToScreenWithColour(int xPos, int yPos, ConsoleColor colour, params char { if (characters[i] is EMPTY_CHAR) { - Console.SetCursorPosition(x, y); - Console.Write(' '); + try + { + Console.SetCursorPosition(x, y); + Console.Write(' '); + } + catch { } } else { - Console.SetCursorPosition(x, y); - Console.Write(characters[i]); + try + { + Console.SetCursorPosition(x, y); + Console.Write(characters[i]); + } + catch { } } } @@ -464,7 +541,9 @@ void DrawToScreenWithColour(int xPos, int yPos, ConsoleColor colour, params char Console.CursorVisible = true; Console.ResetColor(); Console.Clear(); + Console.Write("Duck Hunt was closed."); } + struct Point { public int X; @@ -477,6 +556,7 @@ public Point(int x, int y) public static Point operator +(Point a, Point b) => new Point(a.X + b.X, a.Y + b.Y); } + class Bird { public int X; @@ -511,6 +591,7 @@ public bool Contains(int x, int y) (x < X + Sprites.Bird.Width); } } + class Bullet { public bool OutOfBounds = false; @@ -545,30 +626,32 @@ public void UpdatePosition() } } } + static class Sprites { - public readonly static char NEWLINE_CHAR = '\n'; - public readonly static int ScreenWidth = Console.WindowWidth; - public readonly static int ScreenHeight = Console.WindowHeight; - public readonly static int SPRITE_MAXWIDTH = ScreenWidth - 2; - public readonly static int SPRITE_MAXHEIGHT = ScreenHeight - 2; - - private static string middleBorder = "║" + new string(' ', SPRITE_MAXWIDTH) + "║" + NEWLINE_CHAR; - public static char[] Border = - ("╔" + new string('═', SPRITE_MAXWIDTH) + "╗" + NEWLINE_CHAR + + public const char NEWLINE_CHAR = '\n'; + public static int ScreenWidth = Console.WindowWidth - 1; + public static int ScreenHeight = Console.WindowHeight; + public static int SPRITE_MAXWIDTH => ScreenWidth - 2; + public static int SPRITE_MAXHEIGHT => ScreenHeight - 2; + + private static string middleBorder => "║" + new string(' ', SPRITE_MAXWIDTH) + "║" + NEWLINE_CHAR; + + public static char[] Border => + ("╔" + new string('═', SPRITE_MAXWIDTH) + "╗" + NEWLINE_CHAR + string.Concat(Enumerable.Repeat(middleBorder, SPRITE_MAXHEIGHT)) + "╚" + new string('═', SPRITE_MAXWIDTH) + "╝").ToCharArray(); public static class Enviroment { #region Ascii Sprites - public static char[] Grass = - ( new string('V', SPRITE_MAXWIDTH) + NEWLINE_CHAR + + public static char[] Grass => + (new string('V', SPRITE_MAXWIDTH) + NEWLINE_CHAR + new string('M', SPRITE_MAXWIDTH) + NEWLINE_CHAR + new string('V', SPRITE_MAXWIDTH)).ToCharArray(); public static char[] Crosshair = - ( @" │ " + NEWLINE_CHAR + + (@" │ " + NEWLINE_CHAR + @" ┌│┐ " + NEWLINE_CHAR + @"──O──" + NEWLINE_CHAR + @" └│┘ " + NEWLINE_CHAR + @@ -577,7 +660,7 @@ public static class Enviroment public static int CrosshairWidth = 5; public static char[] Bush = - ( @" (}{{}}} " + NEWLINE_CHAR + + (@" (}{{}}} " + NEWLINE_CHAR + @" {}}{{}'}} " + NEWLINE_CHAR + @"{{}}}{{}}}{}{" + NEWLINE_CHAR + @"){}(}'{}}}{}}" + NEWLINE_CHAR + From acfb1f6e74c6dd08a731d3e33cb1b978f44b2e04 Mon Sep 17 00:00:00 2001 From: Zachary Patten Date: Tue, 14 Jun 2022 20:02:30 -0400 Subject: [PATCH 6/7] Duck Hunt blazor port --- Projects/Duck Hunt/Duck Hunt.csproj | 16 +- Projects/Duck Hunt/Program.cs | 1 + Projects/Website/BlazorConsole.cs | 1 + Projects/Website/Games/Duck Hunt/Duck Hunt.cs | 784 ++++++++++++++++++ Projects/Website/Pages/Duck Hunt.razor | 63 ++ Projects/Website/Shared/NavMenu.razor | 5 + 6 files changed, 861 insertions(+), 9 deletions(-) create mode 100644 Projects/Website/Games/Duck Hunt/Duck Hunt.cs create mode 100644 Projects/Website/Pages/Duck Hunt.razor diff --git a/Projects/Duck Hunt/Duck Hunt.csproj b/Projects/Duck Hunt/Duck Hunt.csproj index 55c25229..2a37d11e 100644 --- a/Projects/Duck Hunt/Duck Hunt.csproj +++ b/Projects/Duck Hunt/Duck Hunt.csproj @@ -1,11 +1,9 @@ - - - Exe - net6.0 - Duck_Hunt - disable - enable - - + + Exe + net6.0 + Duck_Hunt + disable + enable + diff --git a/Projects/Duck Hunt/Program.cs b/Projects/Duck Hunt/Program.cs index 21552d09..b7c77e0d 100644 --- a/Projects/Duck Hunt/Program.cs +++ b/Projects/Duck Hunt/Program.cs @@ -436,6 +436,7 @@ void DrawLine(Point start, Point end) } } } + void DrawToScreen(char[,] array) { for (int y = 0; y < Sprites.ScreenHeight; y++) diff --git a/Projects/Website/BlazorConsole.cs b/Projects/Website/BlazorConsole.cs index e01bb701..e7075550 100644 --- a/Projects/Website/BlazorConsole.cs +++ b/Projects/Website/BlazorConsole.cs @@ -27,6 +27,7 @@ public struct Pixel public bool RefreshOnInputOnly = true; public Pixel[,] View; + public string Title; public ConsoleColor BackgroundColor = ConsoleColor.Black; public ConsoleColor ForegroundColor = ConsoleColor.White; public bool CursorVisible = true; diff --git a/Projects/Website/Games/Duck Hunt/Duck Hunt.cs b/Projects/Website/Games/Duck Hunt/Duck Hunt.cs new file mode 100644 index 00000000..604989a5 --- /dev/null +++ b/Projects/Website/Games/Duck Hunt/Duck Hunt.cs @@ -0,0 +1,784 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Website.Games.Duck_Hunt; + +public class Duck_Hunt +{ + public readonly BlazorConsole Console = new(); + + public async Task Run() + { + Sprites.Console = Console; + Sprites.Initialize(); + + const char NULL_CHAR = '\0'; + const char EMPTY_CHAR = '-'; + const int BARREL_LENGTH = 10; + + // Menu Settings + int gameDelay; + double gunXStretch; + int crosshairSpeed; + bool gunEnabled; + bool bulletsEnabled; + bool gunOutlineEnabled; + + bool inMenu; + bool fireGun; + bool gunSelected; + bool crosshairSelected; + bool gameOver; + int frame; + int score; + int ammoCount; + int spawnDelay; + int grassLevel; + char[,] screenBuffer; + Random rng; + List birds; + List bullets; + StringBuilder screenGraphic; + Stopwatch timer; + Point crosshair; + Point LeftAncor; + Point middleAncor; + Point RightAncor; + + Stopwatch frameRateTimer; + + try + { + // Welcome Screen + Console.CursorVisible = false; + await Console.WriteLine(); + await Console.WriteLine(" Duck Hunt"); + await Console.WriteLine(); + await Console.WriteLine(" Shoot the ducks! Lose ammo missing shots. Run"); + await Console.WriteLine(" out of ammo and it is game over."); + await Console.WriteLine(); + await Console.WriteLine(" Controls"); + await Console.WriteLine(" - arrow keys: aim"); + await Console.WriteLine(" - spacebar: fire"); + await Console.WriteLine(" - enter: open/close menu"); + await Console.WriteLine(" - 1-6: adjust settings in menu"); + await Console.WriteLine(" - escape: exit game"); + await Console.WriteLine(); + await Console.WriteLine(" Recommended Window Size: 120 width x 30 height"); + await Console.WriteLine(); + await Console.WriteLine(" Press any key to begin..."); + await Console.ReadKey(true); + Console.CursorVisible = false; + + // Initialization + { + gameDelay = 30; + gunXStretch = 1; + crosshairSpeed = 2; + gunEnabled = true; + bulletsEnabled = false; + gunOutlineEnabled = false; + + inMenu = false; + fireGun = false; + gunSelected = true; + crosshairSelected = false; + gameOver = false; + frame = 0; + score = 0; + ammoCount = 5; + spawnDelay = 100; + grassLevel = Sprites.ScreenHeight - 4; + screenBuffer = new char[Sprites.ScreenWidth, Sprites.ScreenHeight]; + rng = new(); + birds = new(); + bullets = new(); + screenGraphic = new(); + timer = new(); + crosshair = new(Sprites.ScreenWidth / 2, Sprites.ScreenHeight / 3); + LeftAncor = new(Sprites.ScreenWidth / 2 - 3, Sprites.ScreenHeight - 2); + middleAncor = new(Sprites.ScreenWidth / 2, Sprites.ScreenHeight - 2); + RightAncor = new(Sprites.ScreenWidth / 2 + 3, Sprites.ScreenHeight - 2); + frameRateTimer = new(); + frameRateTimer.Restart(); + timer.Restart(); + } + + // Main Game Loop + while (!gameOver) + { + if (Sprites.ScreenWidth != Console.WindowWidth - 1 || + Sprites.ScreenHeight != Console.WindowHeight) + { + if (OperatingSystem.IsWindows()) + { + Retry: + try + { + Console.BufferWidth = Console.WindowWidth; + Console.BufferHeight = Console.WindowHeight; + } + catch + { + await Console.Clear(); + goto Retry; + } + } + + Sprites.ScreenWidth = Console.WindowWidth - 1; + Sprites.ScreenHeight = Console.WindowHeight; + screenBuffer = new char[Sprites.ScreenWidth, Sprites.ScreenHeight]; + grassLevel = Sprites.ScreenHeight - 4; + LeftAncor = new(Sprites.ScreenWidth / 2 - 3, Sprites.ScreenHeight - 2); + middleAncor = new(Sprites.ScreenWidth / 2, Sprites.ScreenHeight - 2); + RightAncor = new(Sprites.ScreenWidth / 2 + 3, Sprites.ScreenHeight - 2); + crosshair.X = Math.Min(Sprites.ScreenWidth - Sprites.Enviroment.CrosshairWidth + 2, Math.Max(crosshair.X, 2)); + crosshair.Y = Math.Min(Sprites.ScreenHeight - Sprites.Enviroment.CrosshairHeight, Math.Max(crosshair.Y, 2)); + Console.CursorVisible = false; + await Console.Clear(); + } + + Console.Title = $"FPS: {(int)(frame / timer.Elapsed.TotalSeconds)}"; + + if (inMenu) + { + await Console.Clear(); + } + while (inMenu) + { + string menuDisplay = + "Press Corresponding Number to Edit/Select variables" + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + " Currently selected variable: " + (gunSelected ? "[1]" : crosshairSelected ? "[2]" : "[3]") + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + $"[1] Gun X Axis Stretch: {gunXStretch:F} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + $"[2] Crosshair Movement Speed: {crosshairSpeed} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + $"[3] Game Delay (Milliseconds): {gameDelay} " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + $"[4] Bullets Enabled: {bulletsEnabled}- " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + $"[5] Gun Outline Mode Enabled: {gunOutlineEnabled}- " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + $"[6] Gun Enabled: {gunEnabled}- " + Sprites.NEWLINE_CHAR + Sprites.NEWLINE_CHAR + + "Press [^] arrow to increase and [v] arrow to decrease "; + + await DrawToScreenWithColour(1, 4, ConsoleColor.Yellow, menuDisplay.ToCharArray()); + await DrawToScreenWithColour(1, 1, ConsoleColor.White, ("[ESC] Quit" + Sprites.NEWLINE_CHAR + "[ENTER] Exit Menu").ToCharArray()); + + switch ((await Console.ReadKey(true)).Key) + { + case ConsoleKey.D1: gunSelected = true; crosshairSelected = false; break; + case ConsoleKey.D2: gunSelected = false; crosshairSelected = true; break; + case ConsoleKey.D3: gunSelected = false; crosshairSelected = false; break; + case ConsoleKey.D4: bulletsEnabled = !bulletsEnabled; break; + case ConsoleKey.D5: gunOutlineEnabled = !gunOutlineEnabled; break; + case ConsoleKey.D6: gunEnabled = !gunEnabled; break; + case ConsoleKey.Enter: inMenu = false; continue; + case ConsoleKey.Escape: return; + + case ConsoleKey.UpArrow: + if (gunSelected) + { + gunXStretch += 0.1; + } + else if (crosshairSelected) + { + crosshairSpeed++; + } + else + { + gameDelay++; + } + break; + case ConsoleKey.DownArrow: + if (gunSelected) + { + gunXStretch -= 0.1; + } + else if (crosshairSelected) + { + crosshairSpeed--; + } + else + { + gameDelay--; + } + break; + } + + timer.Restart(); + frame = 0; + } + + if (await Console.KeyAvailable()) + { + switch ((await Console.ReadKey(true)).Key) + { + case ConsoleKey.UpArrow: crosshair.Y -= crosshairSpeed; break; + case ConsoleKey.DownArrow: crosshair.Y += crosshairSpeed; break; + case ConsoleKey.LeftArrow: crosshair.X -= crosshairSpeed; break; + case ConsoleKey.RightArrow: crosshair.X += crosshairSpeed; break; + case ConsoleKey.Spacebar: fireGun = true; break; + case ConsoleKey.Enter: inMenu = true; continue; + case ConsoleKey.Escape: return; + } + + crosshair.X = Math.Min(Sprites.ScreenWidth - Sprites.Enviroment.CrosshairWidth + 2, Math.Max(crosshair.X, 2)); + crosshair.Y = Math.Min(Sprites.ScreenHeight - Sprites.Enviroment.CrosshairHeight, Math.Max(crosshair.Y, 2)); + } + while (await Console.KeyAvailable()) + { + await Console.ReadKey(true); + } + + WriteToBuffer(0, 0, Sprites.Border); + WriteToBuffer(1, grassLevel, Sprites.Enviroment.Grass); + WriteToBuffer(Sprites.Enviroment.TreeWidth - Sprites.Enviroment.TreeWidth / 2, grassLevel - Sprites.Enviroment.TreeHeight, Sprites.Enviroment.Tree); + WriteToBuffer(Sprites.ScreenWidth - Sprites.Enviroment.BushWidth * 2, grassLevel - Sprites.Enviroment.BushHeight, Sprites.Enviroment.Bush); + WriteToBuffer(1, 1, "[ENTER] Menu".ToCharArray()); + + double theta = Math.Atan2(middleAncor.Y - crosshair.Y, middleAncor.X - crosshair.X); + int xGunOffset = -(int)Math.Floor(Math.Cos(theta) * BARREL_LENGTH); + int yGunOffset = -(int)Math.Floor(Math.Sin(theta) * BARREL_LENGTH); + Point gunTopOffset = new((int)(xGunOffset * gunXStretch), yGunOffset); + + if (gunEnabled) + { + if (gunOutlineEnabled) + { + DrawLine(RightAncor, RightAncor + gunTopOffset); + DrawLine(LeftAncor, LeftAncor + gunTopOffset); + DrawLine(RightAncor + gunTopOffset, LeftAncor + gunTopOffset); + } + else + { + for (int i = LeftAncor.X; i <= RightAncor.X; i++) + { + Point gunBottomOffset = new(i, middleAncor.Y); + DrawLine(gunBottomOffset, gunBottomOffset + gunTopOffset); + } + } + } + + await DrawToScreen(screenBuffer); + await DrawGUI(); + + if (bulletsEnabled) + { + if (fireGun) + { + bullets.Add(new Bullet(middleAncor + gunTopOffset, theta, Console)); + ammoCount--; + } + + for (int i = 0; i < bullets.Count; i++) + { + bullets[i].UpdatePosition(); + + if (bullets[i].OutOfBounds) + { + bullets.RemoveAt(i); + continue; + } + + foreach (Bird bird in birds) + { + if (!bird.IsDead && + (bird.Contains((int)bullets[i].X[0], (int)bullets[i].Y[0]) || + bird.Contains((int)bullets[i].X[1], (int)bullets[i].Y[1]))) + { + bird.IsDead = true; + ammoCount += 2; + score += 350; + } + } + + await DrawToScreenWithColour((int)bullets[i].X[0], (int)bullets[i].Y[0], ConsoleColor.DarkGray, '█'); + await DrawToScreenWithColour((int)bullets[i].X[1], (int)bullets[i].Y[1], ConsoleColor.DarkGray, '█'); + } + } + else + { + if (fireGun && ammoCount > 0) + { + foreach (Bird bird in birds) + { + if (!bird.IsDead && bird.Contains(crosshair.X, crosshair.Y)) + { + bird.IsDead = true; + ammoCount += 2; + score += 150; + } + } + ammoCount--; + } + } + + fireGun = false; + + foreach (Bird bird in birds) + { + await DrawToScreenWithColour(bird.X, bird.Y, ConsoleColor.Red, bird.Direction is -1 ? Sprites.Bird.LeftSprites[bird.Frame] : Sprites.Bird.RightSprites[bird.Frame]); + if (frame % 2 is 0) + { + bird.IncrementFrame(); + if (bird.IsDead) + { + bird.Y++; + } + else + { + bird.X += bird.Direction; + } + } + } + + for (int i = birds.Count - 1; i >= 0; i--) + { + if (birds[i].Y > Sprites.ScreenHeight || + (birds[i].Direction is -1 && birds[i].X < -Sprites.Bird.Width) || + (birds[i].Direction is 1 && birds[i].X > Sprites.ScreenWidth + Sprites.Bird.Width)) + { + birds.RemoveAt(i); + } + } + + if (frame % spawnDelay is 0) + { + if (rng.Next(50) > 25) + { + birds.Add(new Bird(Sprites.ScreenWidth, rng.Next(1, grassLevel - Sprites.Bird.Height), -1)); + } + else + { + birds.Add(new Bird(-Sprites.Bird.Width, rng.Next(1, grassLevel - Sprites.Bird.Height), 1)); + } + if (spawnDelay > 60) + { + spawnDelay--; + } + } + + if (ammoCount > 5) + { + ammoCount = 5; + } + + await DrawToScreenWithColour(crosshair.X - Sprites.Enviroment.CrosshairHeight / 2, crosshair.Y - Sprites.Enviroment.CrosshairWidth / 2, fireGun ? ConsoleColor.DarkYellow : ConsoleColor.Blue, Sprites.Enviroment.Crosshair); + + TimeSpan gameDelayTimespan = TimeSpan.FromMilliseconds(gameDelay); + TimeSpan delay = frameRateTimer.Elapsed > gameDelayTimespan + ? TimeSpan.Zero + : TimeSpan.FromMilliseconds(gameDelay) - frameRateTimer.Elapsed; + await Console.RefreshAndDelay(delay); + frameRateTimer.Restart(); + frame++; + + gameOver = ammoCount is 0 && bullets.Count is 0; + } + + Console.ForegroundColor = ConsoleColor.Yellow; + await Console.SetCursorPosition(1, 1); + await Console.WriteLine("Game Over! "); + await Console.SetCursorPosition(1, 2); + await Console.WriteLine($"Score: {score}"); + await Console.SetCursorPosition(1, 3); + await Console.WriteLine("Press [ESC] to quit"); + + while ((await Console.ReadKey(true)).Key != ConsoleKey.Escape) + { + continue; + } + + async Task DrawGUI() + { + int x = Sprites.ScreenWidth - 19; + int y = grassLevel; + + string topFrame = '╔' + new string('═', 17) + '╣'; + string ammoFrame = string.Format("║ Ammo:{0,-10}", string.Concat(Enumerable.Repeat(" |", ammoCount))) + '║'; + string scoreFrame = string.Format("║ Score: {0,-9}", score) + '║'; + string bottomFrame = '╩' + new string('═', 17) + '╝'; + try + { + await Console.SetCursorPosition(x, y); + await Console.Write(topFrame); + await Console.SetCursorPosition(x, ++y); + await Console.Write(ammoFrame); + await Console.SetCursorPosition(x, ++y); + await Console.Write(scoreFrame); + await Console.SetCursorPosition(x, ++y); + await Console.Write(bottomFrame); + } + catch //(IndexOutOfRangeException) + { + // user is likely resizing the console window + } + } + + void DrawLine(Point start, Point end) + { + /// Bresenhams line algorithm + int x = start.X; + int y = start.Y; + int dx = Math.Abs(start.X - end.X); + int dy = -Math.Abs(start.Y - end.Y); + int sx = start.X < end.X ? 1 : -1; + int sy = start.Y < end.Y ? 1 : -1; + int error = dx + dy; + while (true) + { + WriteToBuffer(x, y, '▓'); // ░▒▓█ + + if (x == end.X && y == end.Y) + { + return; + } + + float error2 = error * 2; + if (error2 >= dy) + { + if (x == end.X) + { + break; + } + + error += dy; + x += sx; + } + if (error2 <= dx) + { + if (y == end.Y) + { + break; + } + + error += dx; + y += sy; + } + } + } + + async Task DrawToScreen(char[,] array) + { + for (int y = 0; y < Sprites.ScreenHeight; y++) + { + for (int x = 0; x < Sprites.ScreenWidth; x++) + { + if (array[x, y] is NULL_CHAR) + { + screenGraphic.Append(' '); + } + else + { + screenGraphic.Append(array[x, y]); + } + } + if (y < Sprites.ScreenHeight - 1) + { + screenGraphic.AppendLine(); + } + } + await Console.SetCursorPosition(0, 0); + await Console.Write(screenGraphic); + + screenBuffer = new char[Sprites.ScreenWidth, Sprites.ScreenHeight]; //Array.Clear(screenBuffer, 0, screenBuffer.Length); + screenGraphic.Clear(); + } + + void WriteToBuffer(int xPos, int yPos, params char[] characters) + { + int x = xPos; + int y = yPos; + for (int i = 0; i < characters.Length; i++) + { + if (characters[i] is Sprites.NEWLINE_CHAR) + { + y++; + x = xPos; + } + else if (x < 0 || y < 0 || x >= screenBuffer.GetLength(0) || y >= screenBuffer.GetLength(1)) + { + x++; + } + else + { + screenBuffer[x, y] = characters[i]; + x++; + } + } + } + + async Task DrawToScreenWithColour(int xPos, int yPos, ConsoleColor colour, params char[] characters) + { + int x = xPos; + int y = yPos; + Console.ForegroundColor = colour; + + for (int i = 0; i < characters.Length; i++) + { + if (characters[i] == Sprites.NEWLINE_CHAR) + { + y++; + x = xPos; + continue; + } + + if (char.IsWhiteSpace(characters[i])) + { + x++; + continue; + } + + if (x >= 1 && x < Sprites.ScreenWidth - 1 && + y >= 1 && y < Sprites.ScreenHeight - 1) + { + if (characters[i] is EMPTY_CHAR) + { + try + { + await Console.SetCursorPosition(x, y); + await Console.Write(' '); + } + catch { } + } + else + { + try + { + await Console.SetCursorPosition(x, y); + await Console.Write(characters[i]); + } + catch { } + } + } + + x++; + } + + Console.ForegroundColor = ConsoleColor.White; + } + } + finally + { + Console.CursorVisible = true; + Console.ResetColor(); + await Console.Clear(); + await Console.Write("Duck Hunt was closed."); + await Console.Refresh(); + } + } + +struct Point + { + public int X; + public int Y; + public Point(int x, int y) + { + X = x; + Y = y; + } + public static Point operator +(Point a, Point b) + => new Point(a.X + b.X, a.Y + b.Y); + } + + class Bird + { + public int X; + public int Y; + public int Frame = 0; + public int Direction = 0; + public bool IsDead = false; + public Bird(int x, int y, int direction) + { + X = x; + Y = y; + Direction = direction; + } + public void IncrementFrame() + { + if (IsDead) + { + Frame = 4; + } + else + { + Frame++; + Frame %= 4; + } + } + public bool Contains(int x, int y) + { + return + (x >= X) && + (y >= Y) && + (y < Y + Sprites.Bird.Height) && + (x < X + Sprites.Bird.Width); + } + } + + class Bullet + { + public static BlazorConsole Console; + public bool OutOfBounds = false; + public double[] X = new double[2]; + public double[] Y = new double[2]; + + private double XOffset; + private double YOffset; + public Bullet(Point position, double angle, BlazorConsole console) + { + Console = console; + for (int i = 0; i < 2; i++) + { + X[i] = position.X; + Y[i] = position.Y; + } + + XOffset = -Math.Cos(angle); + YOffset = -Math.Sin(angle); + } + public void UpdatePosition() + { + X[1] = X[0]; + Y[1] = Y[0]; + + X[0] += XOffset; + Y[0] += YOffset; + + if (X[0] < 0 || X[0] >= Console.WindowWidth || + Y[0] < 0 || Y[0] >= Console.WindowHeight) + { + OutOfBounds = true; + } + } + } + + static class Sprites + { + public static BlazorConsole Console = default!; + + public static void Initialize() + { + ScreenWidth = Console.WindowWidth - 1; + ScreenHeight = Console.WindowHeight; + } + + public const char NEWLINE_CHAR = '\n'; + public static int ScreenWidth; + public static int ScreenHeight; + public static int SPRITE_MAXWIDTH => ScreenWidth - 2; + public static int SPRITE_MAXHEIGHT => ScreenHeight - 2; + + private static string middleBorder => "║" + new string(' ', SPRITE_MAXWIDTH) + "║" + NEWLINE_CHAR; + + public static char[] Border => + ("╔" + new string('═', SPRITE_MAXWIDTH) + "╗" + NEWLINE_CHAR + + string.Concat(Enumerable.Repeat(middleBorder, SPRITE_MAXHEIGHT)) + + "╚" + new string('═', SPRITE_MAXWIDTH) + "╝").ToCharArray(); + + public static class Enviroment + { + #region Ascii Sprites + public static char[] Grass => + (new string('V', SPRITE_MAXWIDTH) + NEWLINE_CHAR + + new string('M', SPRITE_MAXWIDTH) + NEWLINE_CHAR + + new string('V', SPRITE_MAXWIDTH)).ToCharArray(); + + public static char[] Crosshair = + (@" │ " + NEWLINE_CHAR + + @" ┌│┐ " + NEWLINE_CHAR + + @"──O──" + NEWLINE_CHAR + + @" └│┘ " + NEWLINE_CHAR + + @" │ ").ToCharArray(); + public static int CrosshairHeight = 5; + public static int CrosshairWidth = 5; + + public static char[] Bush = + (@" (}{{}}} " + NEWLINE_CHAR + + @" {}}{{}'}} " + NEWLINE_CHAR + + @"{{}}}{{}}}{}{" + NEWLINE_CHAR + + @"){}(}'{}}}{}}" + NEWLINE_CHAR + + @"){}(}{{}}}{})" + NEWLINE_CHAR + + @" {}}}{{}}}{} ").ToCharArray(); + public static int BushHeight = 6; + public static int BushWidth = 13; + + public static char[] Tree = + (@" #### " + NEWLINE_CHAR + + @" ###### " + NEWLINE_CHAR + + @" ###### " + NEWLINE_CHAR + + @" #### #### " + NEWLINE_CHAR + + @" || ###### " + NEWLINE_CHAR + + @" || /#### " + NEWLINE_CHAR + + @" ####/ " + NEWLINE_CHAR + + @" ###### " + NEWLINE_CHAR + + @" #### #### #### " + NEWLINE_CHAR + + @"###### |||| ######" + NEWLINE_CHAR + + @" #### |||| ######" + NEWLINE_CHAR + + @" \\ |||| //#### " + NEWLINE_CHAR + + @" \\|||| // " + NEWLINE_CHAR + + @" \||||// " + NEWLINE_CHAR + + @" ||||/ " + NEWLINE_CHAR + + @" |||| " + NEWLINE_CHAR + + @" |||| " + NEWLINE_CHAR + + @" |||| " + NEWLINE_CHAR + + @" |||| " + NEWLINE_CHAR + + @" |||| ").ToCharArray(); + public static int TreeHeight = 20; + public static int TreeWidth = 20; + } + + public static class Bird + { + public static char[][] LeftSprites = + { ( @" _(nn)_ " + NEWLINE_CHAR + + @"<(o----_)=" + NEWLINE_CHAR + + @" (UU) ").ToCharArray(), + + ( @" ______ " + NEWLINE_CHAR + + @"<(o(UU)_)=" + NEWLINE_CHAR + + @" ").ToCharArray(), + + ( @" _(nn)_ " + NEWLINE_CHAR + + @"<(o----_)=" + NEWLINE_CHAR + + @" (UU) ").ToCharArray(), + + ( @" ______ " + NEWLINE_CHAR + + @"<(o(UU)_)=" + NEWLINE_CHAR + + @" ").ToCharArray(), + + ( @" _ " + NEWLINE_CHAR + + @" _<(x)__ " + NEWLINE_CHAR + + @"(--(-)--)" + NEWLINE_CHAR + + @"(__(_)__)" + NEWLINE_CHAR + + @" _/ \_ " ).ToCharArray() + }; + public static char[][] RightSprites = + { ( @" _(nn)_ " + NEWLINE_CHAR + + @"=(_----o)>" + NEWLINE_CHAR + + @" (UU) ").ToCharArray(), + + ( @" ______ " + NEWLINE_CHAR + + @"=(_(UU)o)>" + NEWLINE_CHAR + + @" ").ToCharArray(), + + ( @" _(nn)_ " + NEWLINE_CHAR + + @"=(_----o)>" + NEWLINE_CHAR + + @" (UU) ").ToCharArray(), + + ( @" ______ " + NEWLINE_CHAR + + @"=(_(UU)o)>" + NEWLINE_CHAR + + @" ").ToCharArray(), + + ( @" _ " + NEWLINE_CHAR + + @" __(x)>_ " + NEWLINE_CHAR + + @"(--(-)--)" + NEWLINE_CHAR + + @"(__(_)__)" + NEWLINE_CHAR + + @" _/ \_ " ).ToCharArray() + }; + public static int Height = 3; + public static int Width = 10; + #endregion + } + } +} diff --git a/Projects/Website/Pages/Duck Hunt.razor b/Projects/Website/Pages/Duck Hunt.razor new file mode 100644 index 00000000..c80aec6d --- /dev/null +++ b/Projects/Website/Pages/Duck Hunt.razor @@ -0,0 +1,63 @@ +@using System + +@page "/Duck Hunt" + +Duck Hunt + +

Duck Hunt

+ + + Go To Readme + + +
+
+
+			@Console.State
+		
+
+
+ + + + + + + + + + + + + +
+
+ + + + + + + +@code +{ + Games.Duck_Hunt.Duck_Hunt Game; + BlazorConsole Console; + + public Duck_Hunt() + { + Game = new(); + Console = Game.Console; + Console.WindowWidth = 120; + Console.WindowHeight = 30; + Console.StateHasChanged = StateHasChanged; + } + + protected override void OnInitialized() => InvokeAsync(Game.Run); +} diff --git a/Projects/Website/Shared/NavMenu.razor b/Projects/Website/Shared/NavMenu.razor index 6d3df738..06d216f6 100644 --- a/Projects/Website/Shared/NavMenu.razor +++ b/Projects/Website/Shared/NavMenu.razor @@ -178,6 +178,11 @@ Battleship +