Optimize code structure

This commit is contained in:
Stone_Red
2024-02-06 23:13:43 +01:00
parent bf392ee593
commit d160071e7c
36 changed files with 272 additions and 155 deletions
@@ -1,7 +1,9 @@
@inherits LayoutComponentBase
<div class="fixed-top p-4">
<h1 class="text-white-50 mb-0">TextLore — Stone_Red</h1>
<a href="/" class="text-decoration-none">
<h1 class="text-white-50 mb-0">TextLore — Stone_Red</h1>
</a>
<div>
<a class="link-info" href="https://github.me.stone-red.net/TextLore">https://github.me.stone-red.net/TextLore</a>
</div>
@@ -1,18 +0,0 @@
@page "/counter"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
@@ -1,11 +1,11 @@
using Microsoft.AspNetCore.Components;
using TextLore.Console;
using TextLore.Console.Commands;
using TextLore.Games.Roguelike.Commands;
using TextLore.Games.Roguelike.Models;
using TextLore.Shared.Commands;
using TextLore.Shared.Logic;
using TextLore.Shared.Models.Level;
namespace TextLore.Games.Roguelike;
namespace TextLore.Components.Pages.Games.Roguelike;
public partial class RoguelikePage
{
@@ -14,30 +14,33 @@ public partial class RoguelikePage
// Short description how to play the game
private readonly string desctiption = "This is a roguelike game. Use commands to play the game.";
private Components.Shared.Console console = null!;
private Shared.Console console = null!;
private HelpCommand? helpCommand;
private Level? level;
private GameManager? gameManager = null;
[Parameter]
public string Seed { get; set; } = string.Empty;
public void OnLevelGenerated(Level level)
public async void OnLevelGenerated(Level level)
{
this.level = level;
gameManager = new GameManager(level);
StateHasChanged();
}
protected override void OnInitialized()
{
helpCommand = new HelpCommand(commands);
commands.Add(helpCommand);
commands.Add(new ExitCommand(NavigationManager));
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await console.Execute(new GenerateLevelCommand(DatabaseContext.Rooms, NavigationManager, OnLevelGenerated), Seed);
await console.Execute(new GenerateLevelCommand("roguelike", DatabaseContext.Rooms, NavigationManager, OnLevelGenerated), Seed);
}
}
}
@@ -11,4 +11,4 @@
@using TextLore.Components
@using TextLore.Components.Shared
@using TextLore.Console
@using TextLore.Console.Commands
@using TextLore.Shared.Commands
+3
View File
@@ -1,5 +1,7 @@
@page "/"
@inject NavigationManager NavigationManager
<PageTitle>TextLore</PageTitle>
<Console Name="Main Menu" Commands="commands" Description="@desctiption" HelpCommand="helpCommand" />
@@ -16,5 +18,6 @@
{
helpCommand = new HelpCommand(commands);
commands.Add(helpCommand);
commands.Add(new PlayCommand(NavigationManager, "roguelike"));
}
}
@@ -1,64 +0,0 @@
@page "/weather"
@attribute [StreamRendering]
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
// Simulate asynchronous loading to demonstrate streaming rendering
await Task.Delay(500);
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
}
private class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}
+1 -1
View File
@@ -56,7 +56,7 @@
[Parameter]
public Command? HelpCommand { get; set; }
private string Placeholder => $"Enter a command{(HelpCommand is null ? "." : ", type 'help' for avaliable commands.")}";
private string Placeholder => disabled ? "Processing..." : $"Enter a command{(HelpCommand is null ? "." : ", type 'help' for avaliable commands.")}";
private ConsoleInput ConsoleInput { get; set; } = new();
private List<ConsoleOutput> consoleOutputs = new();
private ConsoleOutput? currentOutput;
+1 -1
View File
@@ -10,4 +10,4 @@
@using TextLore.Components
@using TextLore.Components.Shared
@using TextLore.Console
@using TextLore.Console.Commands
@using TextLore.Shared.Commands
@@ -1,8 +1,10 @@
using Microsoft.EntityFrameworkCore;
using TextLore.Shared.Models.Level;
namespace TextLore.Database;
public class GamesDatabaseContext(DbContextOptions<GamesDatabaseContext> options) : DbContext(options)
{
public DbSet<Games.Roguelike.Models.RoomDefinition> Rooms { get; set; }
public DbSet<RoomDefinition> Rooms { get; set; }
}
+9 -8
View File
@@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore;
using TextLore.Components;
using TextLore.Database;
using TextLore.Shared.Models.Level;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
@@ -29,14 +30,14 @@ GamesDatabaseContext databaseContext = serviceScope.ServiceProvider.GetRequiredS
if (await databaseContext.Database.EnsureCreatedAsync())
{
databaseContext.Rooms.AddRange(
new TextLore.Games.Roguelike.Models.RoomDefinition(1, TextLore.Games.Roguelike.Models.RoomTag.Start, "rogelike") { Name = "Start Room" },
new TextLore.Games.Roguelike.Models.RoomDefinition(1, TextLore.Games.Roguelike.Models.RoomTag.Unique, "rogelike") { Name = "Unique Room" },
new TextLore.Games.Roguelike.Models.RoomDefinition(2, TextLore.Games.Roguelike.Models.RoomTag.None, "rogelike") { Name = "Normal Room 1" },
new TextLore.Games.Roguelike.Models.RoomDefinition(3, TextLore.Games.Roguelike.Models.RoomTag.None, "rogelike") { Name = "Normal Room 2" },
new TextLore.Games.Roguelike.Models.RoomDefinition(4, TextLore.Games.Roguelike.Models.RoomTag.None, "rogelike") { Name = "Normal Room 3" },
new TextLore.Games.Roguelike.Models.RoomDefinition(5, TextLore.Games.Roguelike.Models.RoomTag.None, "rogelike") { Name = "Normal Room 4" },
new TextLore.Games.Roguelike.Models.RoomDefinition(6, TextLore.Games.Roguelike.Models.RoomTag.None, "rogelike") { Name = "Normal Room 5" },
new TextLore.Games.Roguelike.Models.RoomDefinition(1, TextLore.Games.Roguelike.Models.RoomTag.End, "rogelike") { Name = "End Room" }
new RoomDefinition(1, RoomTag.Start, "roguelike") { Name = "Start Room" },
new RoomDefinition(1, RoomTag.Unique, "roguelike") { Name = "Unique Room" },
new RoomDefinition(2, RoomTag.None, "roguelike") { Name = "Normal Room 1" },
new RoomDefinition(3, RoomTag.None, "roguelike") { Name = "Normal Room 2" },
new RoomDefinition(4, RoomTag.None, "roguelike") { Name = "Normal Room 3" },
new RoomDefinition(5, RoomTag.None, "roguelike") { Name = "Normal Room 4" },
new RoomDefinition(6, RoomTag.None, "roguelike") { Name = "Normal Room 5" },
new RoomDefinition(1, RoomTag.End, "roguelike") { Name = "End Room" }
);
_ = await databaseContext.SaveChangesAsync();
@@ -1,4 +1,6 @@
namespace TextLore.Console.Commands;
using TextLore.Console;
namespace TextLore.Shared.Commands;
public class ClearCommand : Command
{
@@ -0,0 +1,17 @@
using TextLore.Console;
namespace TextLore.Shared.Commands;
public class EchoCommand : Command
{
public override string Name => "echo";
public override string Description => "Echoes the input.";
public override Task<CommandResult> Execute(ConsoleWriter consoleOutput, string args)
{
consoleOutput.WriteLine(args);
return CommandResult.Success();
}
}
@@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Components;
using TextLore.Console;
namespace TextLore.Shared.Commands;
public class ExitCommand(NavigationManager navigationManager) : Command
{
public override string Name => "exit";
public override string Description => "Exits the game.";
public override Task<CommandResult> Execute(ConsoleWriter consoleOutput, string args)
{
consoleOutput.WriteLine("Exiting the game...");
navigationManager.NavigateTo("/");
return CommandResult.Success();
}
}
@@ -2,21 +2,18 @@
using Microsoft.EntityFrameworkCore;
using TextLore.Console;
using TextLore.Games.Roguelike.Logic;
using TextLore.Games.Roguelike.Models;
using TextLore.Utilities.Logic;
using TextLore.Utilities.Models;
using TextLore.Shared.Logic;
using TextLore.Shared.Models;
using TextLore.Shared.Models.Level;
namespace TextLore.Games.Roguelike.Commands;
namespace TextLore.Shared.Commands;
public class GenerateLevelCommand(IQueryable<RoomDefinition> roomDefinitions, NavigationManager navigationManager, Action<Level> levelGeneratedCallback) : Command
public class GenerateLevelCommand(string gameName, IQueryable<RoomDefinition> roomDefinitions, NavigationManager navigationManager, Action<Level> levelGeneratedCallback) : Command
{
private bool isGenerated = false;
public override string Name => "generatelevel";
public override string Description => "Generates a new level.";
public override string[] Aliases => ["genlevel"];
public override bool NoHistoryIfNoOutput => true;
public override async Task<CommandResult> Execute(ConsoleWriter consoleOutput, string args)
@@ -26,12 +23,12 @@ public class GenerateLevelCommand(IQueryable<RoomDefinition> roomDefinitions, Na
consoleOutput.WriteLine("Invalid seed. Generating a new one...");
seed = await GenerateSeed();
navigationManager.NavigateTo($"/roguelike/{seed.ToBase64()}");
navigationManager.NavigateTo($"/{gameName}/{seed.ToBase64()}");
}
consoleOutput.WriteLine("Generating level...");
LevelGenerator levelGenerator = new LevelGenerator(seed, roomDefinitions);
LevelGenerator levelGenerator = new LevelGenerator(seed, roomDefinitions.Where(r => r.Game == gameName));
IProgress<PercentageProgress> progress = new Progress<PercentageProgress>(p => ReportProgress(p, consoleOutput));
@@ -88,8 +85,8 @@ public class GenerateLevelCommand(IQueryable<RoomDefinition> roomDefinitions, Na
private async Task<DeterministicRandom> GenerateSeed()
{
int baseSeed = Guid.NewGuid().GetHashCode();
int min = await roomDefinitions.MinAsync(r => r.Id);
int max = await roomDefinitions.MaxAsync(r => r.Id);
int min = await roomDefinitions.Where(r => r.Game == gameName).MinAsync(r => r.Id);
int max = await roomDefinitions.Where(r => r.Game == gameName).MaxAsync(r => r.Id);
return new DeterministicRandom(baseSeed, min, max);
}
@@ -1,4 +1,6 @@
namespace TextLore.Console.Commands;
using TextLore.Console;
namespace TextLore.Shared.Commands;
public class HelpCommand(IEnumerable<Command> commands) : Command
{
@@ -0,0 +1,49 @@
using Microsoft.AspNetCore.Components;
using TextLore.Console;
namespace TextLore.Shared.Commands;
public class PlayCommand(NavigationManager navigationManager, params string[] gameNames) : Command
{
public override string Name => "play";
public override string Description => "Starts a new game.";
public override string Usage => "play <game> (You only have to type the first few letters of the game name.)";
public override Task<CommandResult> Execute(ConsoleWriter consoleOutput, string args)
{
if (string.IsNullOrWhiteSpace(args))
{
consoleOutput.WriteLine("Available games:");
foreach (string availableGame in gameNames)
{
consoleOutput.WriteLine($"- {availableGame}");
}
return Task.FromResult(CommandResult.Failure("You need to specify a game to play."));
}
string? game = Array.Find(gameNames, g => g.StartsWith(args, StringComparison.CurrentCultureIgnoreCase));
if (game is null)
{
consoleOutput.WriteLine("Available games:");
foreach (string availableGame in gameNames)
{
consoleOutput.WriteLine($"- {availableGame}");
}
return Task.FromResult(CommandResult.Failure($"Game '{args}' not found."));
}
consoleOutput.WriteLine($"Starting game '{game}'...");
navigationManager.NavigateTo($"/{game}");
return Task.FromResult(CommandResult.Success());
}
}
@@ -1,4 +1,6 @@
namespace TextLore.Console.Commands;
using TextLore.Console;
namespace TextLore.Shared.Commands;
public class TestCommand : Command
{
@@ -2,7 +2,7 @@
using System.Diagnostics.CodeAnalysis;
namespace TextLore.Utilities.Logic;
namespace TextLore.Shared.Logic;
public class DeterministicRandom(int seed, int min, int max) : IParsable<DeterministicRandom>
{
+63
View File
@@ -0,0 +1,63 @@
using Microsoft.ClearScript;
using Microsoft.ClearScript.V8;
using TextLore.Console;
using TextLore.Shared.Models;
using TextLore.Shared.Models.Level;
using TextLore.Shared.Models.Player;
namespace TextLore.Shared.Logic;
public partial class GameManager
{
private readonly V8ScriptEngine scriptEngine = new V8ScriptEngine();
public PlayState PlayState { get; }
public GameManager(Level level)
{
PlayState = new(level, new());
ScriptContext currentScriptContext = new(() => PlayState.Player, () => PlayState.CurrentRoom?.Definition);
scriptEngine.AddHostObject("context", currentScriptContext);
}
public void MovePlayer(Direction direction)
{
Position newPosition = PlayState.Player.Position + direction;
if (PlayState.Level.IsPositionInBounds(newPosition))
{
PlayState.Player.Position = newPosition;
SetScript(PlayState.CurrentRoom?.Definition.Script ?? string.Empty);
}
}
public void ExecuteMethod(string method, ConsoleWriter consoleWriter)
{
try
{
_ = scriptEngine.Invoke(method, consoleWriter);
}
catch (ScriptEngineException ex)
{
consoleWriter.WriteLine(ex.Message);
}
catch (ScriptInterruptedException ex)
{
consoleWriter.WriteLine(ex.Message);
}
}
private void SetScript(string script)
{
scriptEngine.Execute(script);
}
public sealed class ScriptContext(Func<Player> player, Func<RoomDefinition?> room)
{
public Player Player => player();
public RoomDefinition? Room => room();
}
}
@@ -1,10 +1,9 @@
using Microsoft.EntityFrameworkCore;
using TextLore.Games.Roguelike.Models;
using TextLore.Utilities.Logic;
using TextLore.Utilities.Models;
using TextLore.Shared.Models;
using TextLore.Shared.Models.Level;
namespace TextLore.Games.Roguelike.Logic;
namespace TextLore.Shared.Logic;
public class LevelGenerator(DeterministicRandom seed, IQueryable<RoomDefinition> rooms)
{
+13
View File
@@ -0,0 +1,13 @@
using TextLore.Shared.Models.Level;
using TextLore.Shared.Models.Player;
namespace TextLore.Shared.Logic;
public class PlayState(Level level, Player player)
{
public Level Level { get; } = level;
public Player Player { get; } = player;
public Room? CurrentRoom => Level.GetRoom(Player.Position);
}
@@ -1,4 +1,4 @@
namespace TextLore.Utilities.Models;
namespace TextLore.Shared.Models;
public readonly struct Direction(int x, int y)
{
@@ -10,6 +10,6 @@ public readonly struct Direction(int x, int y)
public static Direction UpRight => new Direction(1, -1);
public static Direction DownLeft => new Direction(-1, 1);
public static Direction DownRight => new Direction(1, 1);
public int X { get; } = x;
public int Y { get; } = y;
public int X => x;
public int Y => y;
}
@@ -1,7 +1,7 @@
using TextLore.Utilities.Logic;
using TextLore.Utilities.Models;
using TextLore.Shared.Logic;
using TextLore.Shared.Models;
namespace TextLore.Games.Roguelike.Models;
namespace TextLore.Shared.Models.Level;
public class Level(Size size, DeterministicRandom seed)
{
@@ -1,6 +1,6 @@
using TextLore.Utilities.Models;
using TextLore.Shared.Models;
namespace TextLore.Games.Roguelike.Models;
namespace TextLore.Shared.Models.Level;
public class Room(Position position, RoomDefinition roomDefinition)
{
@@ -2,7 +2,7 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace TextLore.Games.Roguelike.Models;
namespace TextLore.Shared.Models.Level;
[PrimaryKey(nameof(Id), nameof(Tag), nameof(Game))]
public class RoomDefinition(int id, RoomTag tag, string game)
@@ -1,4 +1,4 @@
namespace TextLore.Games.Roguelike.Models;
namespace TextLore.Shared.Models.Level;
public enum RoomTag
{
@@ -0,0 +1,9 @@
namespace TextLore.Shared.Models;
public readonly struct PercentageProgress(int current, int total)
{
public int Current => current;
public int Total => total;
public int Percentage => (int)((double)Current / Total * 100);
public bool IsComplete => Current >= Total;
}
@@ -0,0 +1,10 @@
namespace TextLore.Shared.Models.Player;
public class InventoryItem(string name, string description, string tag)
{
public string Name { get; set; } = name;
public string Description { get; set; } = description;
public string Tag { get; set; } = tag;
}
@@ -0,0 +1,10 @@
namespace TextLore.Shared.Models.Player;
public class Player
{
public int Health { get; set; } = 100;
public List<InventoryItem> Inventory { get; } = [];
public Position Position { get; set; } = new Position(0, 0);
}
@@ -1,9 +1,9 @@
namespace TextLore.Utilities.Models;
namespace TextLore.Shared.Models;
public struct Position(int x, int y)
public readonly struct Position(int x, int y)
{
public int X { get; set; } = x;
public int Y { get; set; } = y;
public int X => x;
public int Y => y;
public static Position operator +(Position a, Position b)
{
+7
View File
@@ -0,0 +1,7 @@
namespace TextLore.Shared.Models;
public readonly struct Size(int width, int height)
{
public int Width => width;
public int Height => height;
}
+4 -1
View File
@@ -7,8 +7,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.ClearScript.V8" Version="7.4.4" />
<PackageReference Include="Microsoft.ClearScript.V8.Native.linux-x64" Version="7.4.4" />
<PackageReference Include="Microsoft.ClearScript.V8.Native.win-x64" Version="7.4.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="7.3.0" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="7.3.1" />
</ItemGroup>
</Project>
@@ -1,9 +0,0 @@
namespace TextLore.Utilities.Models;
public class PercentageProgress(int current, int total)
{
public int Current { get; set; } = current;
public int Total { get; set; } = total;
public int Percentage => (int)((double)Current / Total * 100);
public bool IsComplete => Current >= Total;
}
-7
View File
@@ -1,7 +0,0 @@
namespace TextLore.Utilities.Models;
public readonly struct Size(int width, int height)
{
public int Width { get; } = width;
public int Height { get; } = height;
}
Binary file not shown.