refactor: reorganize project structure and move examples
- Move examples (Blackjack, TicTacToe) to the root directory - Convert example projects from Console applications to Libraries - Add Directory.Build.props for shared build settings - Update solution file and project references to reflect new paths
This commit is contained in:
parent
5594515a53
commit
7946f4bafd
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>Blackjack</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OECS\OECS.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OECS.SourceGen\OECS.SourceGen.csproj"
|
||||
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>12</LangVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -2,9 +2,6 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>12</LangVersion>
|
||||
<RootNamespace>OECS.SourceGen</RootNamespace>
|
||||
<AssemblyName>OECS.SourceGen</AssemblyName>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>OECS.Tests</RootNamespace>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
|
|
@ -20,11 +17,11 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\OECS\OECS.csproj" />
|
||||
<ProjectReference Include="..\OECS\OECS.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\OECS.SourceGen\OECS.SourceGen.csproj"
|
||||
<ProjectReference Include="..\OECS.SourceGen\OECS.SourceGen.csproj"
|
||||
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
55
OECS.sln
55
OECS.sln
|
|
@ -1,19 +1,16 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OECS.SourceGen", "src\OECS.SourceGen\OECS.SourceGen.csproj", "{C3D4E5F6-A7B8-9012-CDEF-123456789012}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OECS", "OECS\OECS.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OECS", "src\OECS\OECS.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OECS.SourceGen", "OECS.SourceGen\OECS.SourceGen.csproj", "{C3D4E5F6-A7B8-9012-CDEF-123456789012}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OECS.Tests", "tests\OECS.Tests\OECS.Tests.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F12345678901}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OECS.Tests", "OECS.Tests\OECS.Tests.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F12345678901}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blackjack", "Blackjack\Blackjack.csproj", "{85631EDE-F984-4DA6-8EE9-0715AF1204E6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blackjack", "examples\Blackjack\Blackjack.csproj", "{85631EDE-F984-4DA6-8EE9-0715AF1204E6}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TicTacToe", "TicTacToe\TicTacToe.csproj", "{D4E5F6A7-B8C9-0123-DEF4-567890ABCDEF}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
|
@ -25,18 +22,6 @@ Global
|
|||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|x64.Build.0 = Release|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|x86.Build.0 = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
|
|
@ -49,6 +34,18 @@ Global
|
|||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|x64.Build.0 = Release|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|x86.Build.0 = Release|Any CPU
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
|
|
@ -73,12 +70,20 @@ Global
|
|||
{85631EDE-F984-4DA6-8EE9-0715AF1204E6}.Release|x64.Build.0 = Release|Any CPU
|
||||
{85631EDE-F984-4DA6-8EE9-0715AF1204E6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{85631EDE-F984-4DA6-8EE9-0715AF1204E6}.Release|x86.Build.0 = Release|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEF4-567890ABCDEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEF4-567890ABCDEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEF4-567890ABCDEF}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEF4-567890ABCDEF}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEF4-567890ABCDEF}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEF4-567890ABCDEF}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEF4-567890ABCDEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEF4-567890ABCDEF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEF4-567890ABCDEF}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEF4-567890ABCDEF}.Release|x64.Build.0 = Release|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEF4-567890ABCDEF}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEF4-567890ABCDEF}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||
{85631EDE-F984-4DA6-8EE9-0715AF1204E6} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>OECS</RootNamespace>
|
||||
<AssemblyName>OECS</AssemblyName>
|
||||
|
||||
|
|
@ -1,19 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>TicTacToe</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\OECS\OECS.csproj" />
|
||||
<ProjectReference Include="..\OECS\OECS.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\OECS.SourceGen\OECS.SourceGen.csproj"
|
||||
<ProjectReference Include="..\OECS.SourceGen\OECS.SourceGen.csproj"
|
||||
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Blackjack</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\OECS\OECS.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\OECS.SourceGen\OECS.SourceGen.csproj"
|
||||
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
using OECS;
|
||||
|
||||
namespace Blackjack;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static void Main()
|
||||
{
|
||||
var world = new World();
|
||||
|
||||
// ── Initialize game state ─────────────────────────────────────
|
||||
|
||||
// Use a seed based on current time, or a fixed seed for reproducibility.
|
||||
uint seed = (uint)Environment.TickCount;
|
||||
// Uncomment the next line for a deterministic game:
|
||||
// seed = 12345u;
|
||||
|
||||
world.SetSingleton(new GameState
|
||||
{
|
||||
Phase = GamePhase.Betting,
|
||||
Result = RoundResult.None,
|
||||
Chips = 100,
|
||||
CurrentBet = 0,
|
||||
RoundNumber = 1,
|
||||
Seed = seed
|
||||
});
|
||||
world.PostChanges();
|
||||
|
||||
// ── Wire up systems ───────────────────────────────────────────
|
||||
|
||||
var group = new SystemGroup(world);
|
||||
group.Add(new DeckSetupSystem());
|
||||
group.Add(new DealSystem());
|
||||
group.Add(new PlayerBustCheckSystem());
|
||||
group.Add(new DealerSystem());
|
||||
group.Add(new RenderSystem());
|
||||
|
||||
// ── Game loop ─────────────────────────────────────────────────
|
||||
|
||||
group.RunLogical();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var state = world.ReadSingleton<GameState>();
|
||||
|
||||
// Check for game over (out of chips).
|
||||
if (state.Chips <= 0 && state.Phase == GamePhase.Betting)
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(" You're out of chips! Game over.");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (state.Phase)
|
||||
{
|
||||
case GamePhase.Betting:
|
||||
var input = Console.ReadLine();
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
continue;
|
||||
if (input.Trim().ToLower() == "quit")
|
||||
return;
|
||||
|
||||
if (int.TryParse(input.Trim(), out var bet))
|
||||
{
|
||||
world.Commands.Enqueue(new PlaceBetCommand { Amount = bet });
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(" Invalid bet. Enter a number.");
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case GamePhase.PlayerTurn:
|
||||
var choice = Console.ReadLine();
|
||||
if (string.IsNullOrWhiteSpace(choice))
|
||||
continue;
|
||||
if (choice.Trim().ToLower() == "quit")
|
||||
return;
|
||||
|
||||
switch (choice.Trim().ToLower())
|
||||
{
|
||||
case "h":
|
||||
case "hit":
|
||||
world.Commands.Enqueue(new HitCommand());
|
||||
break;
|
||||
case "s":
|
||||
case "stand":
|
||||
world.Commands.Enqueue(new StandCommand());
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine(" Invalid choice. Enter H or S.");
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case GamePhase.RoundOver:
|
||||
Console.ReadLine();
|
||||
ref var roundState = ref world.GetSingleton<GameState>();
|
||||
roundState.RoundNumber++;
|
||||
world.MarkModified<GameState>(World.SingletonEntity);
|
||||
world.PostChanges();
|
||||
world.Commands.Enqueue(new NewRoundCommand());
|
||||
break;
|
||||
|
||||
default:
|
||||
// Dealing, DealerTurn — systems handle these automatically.
|
||||
break;
|
||||
}
|
||||
|
||||
group.RunLogical();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
using OECS;
|
||||
|
||||
namespace Blackjack;
|
||||
|
||||
/// <summary>
|
||||
/// Renders the current game state to the console.
|
||||
/// </summary>
|
||||
public class RenderSystem : ISystem
|
||||
{
|
||||
public void Run(World world)
|
||||
{
|
||||
var state = world.ReadSingleton<GameState>();
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(" Blackjack");
|
||||
Console.WriteLine(" ═════════");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($" Chips: {state.Chips} | Round: {state.RoundNumber}");
|
||||
Console.WriteLine();
|
||||
|
||||
// Show dealer's hand.
|
||||
var dealerTotal = HandUtil.CalculateHand(world, DealerHandTag.Instance);
|
||||
Console.Write(" Dealer: ");
|
||||
if (state.Phase == GamePhase.PlayerTurn)
|
||||
{
|
||||
// Hide first card (hole card) during player's turn.
|
||||
Console.Write("?? ");
|
||||
PrintHand(world, DealerHandTag.Instance, skipFirst: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintHand(world, DealerHandTag.Instance);
|
||||
Console.Write($" ({dealerTotal})");
|
||||
}
|
||||
Console.WriteLine();
|
||||
|
||||
// Show player's hand.
|
||||
var playerTotal = HandUtil.CalculateHand(world, PlayerHandTag.Instance);
|
||||
Console.Write(" Player: ");
|
||||
PrintHand(world, PlayerHandTag.Instance);
|
||||
Console.Write($" ({playerTotal})");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine();
|
||||
|
||||
// Status line.
|
||||
switch (state.Phase)
|
||||
{
|
||||
case GamePhase.Betting:
|
||||
Console.Write($" Enter bet (1–{state.Chips}): ");
|
||||
break;
|
||||
case GamePhase.PlayerTurn:
|
||||
Console.Write(" [H]it or [S]tand: ");
|
||||
break;
|
||||
case GamePhase.RoundOver:
|
||||
Console.WriteLine($" {ResultText(state.Result)}");
|
||||
Console.WriteLine();
|
||||
Console.Write(" Press Enter for next round...");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrintHand(World world, DealerHand hand, bool skipFirst = false)
|
||||
{
|
||||
PrintHandImpl(world, hand, skipFirst);
|
||||
}
|
||||
|
||||
private static void PrintHand(World world, PlayerHand hand)
|
||||
{
|
||||
PrintHandImpl(world, hand, skipFirst: false);
|
||||
}
|
||||
|
||||
private static void PrintHandImpl<T>(World world, T handTag, bool skipFirst)
|
||||
where T : struct
|
||||
{
|
||||
Entity handEntity = Entity.Null;
|
||||
using (var iter = world.Select<T>())
|
||||
{
|
||||
while (iter.MoveNext())
|
||||
{
|
||||
if (iter.CurrentEntity != World.SingletonEntity)
|
||||
{
|
||||
handEntity = iter.CurrentEntity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (handEntity == Entity.Null)
|
||||
return;
|
||||
|
||||
var cards = world.GetSources<Holds>(handEntity);
|
||||
var ordered = cards.OrderByDescending(c =>
|
||||
{
|
||||
var card = world.ReadComponent<Card>(c);
|
||||
return (int)card.Rank;
|
||||
}).ToList();
|
||||
|
||||
bool first = true;
|
||||
foreach (var cardEntity in ordered)
|
||||
{
|
||||
if (skipFirst && first)
|
||||
{
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
first = false;
|
||||
var card = world.ReadComponent<Card>(cardEntity);
|
||||
Console.Write(CardToString(card));
|
||||
Console.Write(" ");
|
||||
}
|
||||
}
|
||||
|
||||
private static string CardToString(Card card)
|
||||
{
|
||||
var suit = card.Suit switch
|
||||
{
|
||||
Suit.Hearts => "♥",
|
||||
Suit.Diamonds => "♦",
|
||||
Suit.Clubs => "♣",
|
||||
Suit.Spades => "♠",
|
||||
_ => "?"
|
||||
};
|
||||
|
||||
var rank = card.Rank switch
|
||||
{
|
||||
Rank.Ace => "A",
|
||||
Rank.Jack => "J",
|
||||
Rank.Queen => "Q",
|
||||
Rank.King => "K",
|
||||
_ => ((int)card.Rank).ToString()
|
||||
};
|
||||
|
||||
return $"{rank}{suit}";
|
||||
}
|
||||
|
||||
private static string ResultText(RoundResult result) => result switch
|
||||
{
|
||||
RoundResult.PlayerBust => "You bust! Dealer wins.",
|
||||
RoundResult.DealerBust => "Dealer busts! You win!",
|
||||
RoundResult.PlayerWin => "You win!",
|
||||
RoundResult.DealerWin => "Dealer wins.",
|
||||
RoundResult.Push => "Push — it's a tie!",
|
||||
RoundResult.Blackjack => "Blackjack!",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
using OECS;
|
||||
using R3;
|
||||
|
||||
namespace TicTacToe;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
private static readonly string SavePath = Path.Combine(
|
||||
AppContext.BaseDirectory, "save.bin");
|
||||
|
||||
public static void Main()
|
||||
{
|
||||
var world = new World();
|
||||
var reactivityLog = new List<string>();
|
||||
SetupReactivityLogging(world, reactivityLog);
|
||||
|
||||
// ── Try to load a saved game ──────────────────────────────────
|
||||
|
||||
bool loaded = false;
|
||||
if (File.Exists(SavePath))
|
||||
{
|
||||
Console.Write("Saved game found. Load it? (y/n): ");
|
||||
if (Console.ReadLine()?.Trim().ToLower() == "y")
|
||||
{
|
||||
using var stream = File.OpenRead(SavePath);
|
||||
WorldSerializer.Load(world, stream);
|
||||
loaded = true;
|
||||
Console.WriteLine("Game loaded!");
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Load fresh board from CSV (if not loaded) ─────────────────
|
||||
|
||||
if (!loaded)
|
||||
{
|
||||
var csvPath = Path.Combine(AppContext.BaseDirectory, "Data", "board.csv");
|
||||
CsvLoader.LoadCells(world, csvPath);
|
||||
world.PostChanges();
|
||||
|
||||
world.SetSingleton(new GameState
|
||||
{
|
||||
CurrentPlayer = Player.X,
|
||||
Status = GameStatus.Playing,
|
||||
MoveCount = 0
|
||||
});
|
||||
world.PostChanges();
|
||||
}
|
||||
|
||||
// ── Wire up systems ───────────────────────────────────────────
|
||||
|
||||
var group = new SystemGroup(world);
|
||||
group.Add(new WinCheckSystem());
|
||||
group.Add(new RenderSystem());
|
||||
|
||||
// ── Game loop ─────────────────────────────────────────────────
|
||||
|
||||
group.RunLogical();
|
||||
PrintReactivityLog(reactivityLog);
|
||||
|
||||
while (true)
|
||||
{
|
||||
ref var state = ref world.GetSingleton<GameState>();
|
||||
if (state.Status != GameStatus.Playing)
|
||||
break;
|
||||
|
||||
var input = Console.ReadLine();
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
continue;
|
||||
|
||||
// "save" command.
|
||||
if (input.Trim().ToLower() == "save")
|
||||
{
|
||||
SaveGame(world);
|
||||
Console.WriteLine(" Game saved.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var parts = input.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 2 ||
|
||||
!int.TryParse(parts[0], out var row) ||
|
||||
!int.TryParse(parts[1], out var col))
|
||||
{
|
||||
Console.WriteLine(" Invalid input. Try \"1 2\" or \"save\".");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (row < 0 || row > 2 || col < 0 || col > 2)
|
||||
{
|
||||
Console.WriteLine(" Row and col must be 0–2.");
|
||||
continue;
|
||||
}
|
||||
|
||||
world.Commands.Enqueue(new PlaceMarkCommand { Row = row, Col = col });
|
||||
group.RunLogical();
|
||||
PrintReactivityLog(reactivityLog);
|
||||
|
||||
// Auto-save after every move.
|
||||
SaveGame(world);
|
||||
}
|
||||
|
||||
// ── Final state ───────────────────────────────────────────────
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("=== Game over ===");
|
||||
PrintReactivityLog(reactivityLog);
|
||||
|
||||
// Clean up save file on game over.
|
||||
if (File.Exists(SavePath))
|
||||
File.Delete(SavePath);
|
||||
}
|
||||
|
||||
private static void SaveGame(World world)
|
||||
{
|
||||
using var stream = File.Create(SavePath);
|
||||
WorldSerializer.Save(world, stream);
|
||||
}
|
||||
|
||||
private static void SetupReactivityLogging(World world, List<string> log)
|
||||
{
|
||||
world.ObserveEntityChanges().Subscribe(change =>
|
||||
{
|
||||
if (change.ComponentType != null) return;
|
||||
log.Add($"[react] {change}");
|
||||
});
|
||||
|
||||
world.ObserveComponentChanges<Mark>().Subscribe(change =>
|
||||
{
|
||||
log.Add($"[react] {change}");
|
||||
});
|
||||
|
||||
world.ObserveComponentChanges<GameState>().Subscribe(change =>
|
||||
{
|
||||
log.Add($"[react] {change}");
|
||||
});
|
||||
|
||||
var markedQuery = world.Query().With<Cell>().With<Mark>().Build();
|
||||
world.ObserveQuery(markedQuery).Subscribe(change =>
|
||||
{
|
||||
log.Add($"[react:query] {change}");
|
||||
});
|
||||
}
|
||||
|
||||
private static void PrintReactivityLog(List<string> log)
|
||||
{
|
||||
if (log.Count == 0) return;
|
||||
Console.WriteLine("── reactivity log ──");
|
||||
foreach (var entry in log)
|
||||
Console.WriteLine(entry);
|
||||
Console.WriteLine();
|
||||
log.Clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
using OECS;
|
||||
|
||||
namespace TicTacToe;
|
||||
|
||||
/// <summary>
|
||||
/// Prints the board to the console.
|
||||
/// </summary>
|
||||
public class RenderSystem : ISystem
|
||||
{
|
||||
public void Run(World world)
|
||||
{
|
||||
var state = world.ReadSingleton<GameState>();
|
||||
|
||||
// Build a 3×3 grid.
|
||||
var grid = new char[3, 3];
|
||||
for (int r = 0; r < 3; r++)
|
||||
for (int c = 0; c < 3; c++)
|
||||
grid[r, c] = '.';
|
||||
|
||||
// Fill in placed marks using the iterator API.
|
||||
using var markIter = world.Select<Cell, Mark>();
|
||||
while (markIter.MoveNext())
|
||||
{
|
||||
grid[markIter.Current1.Row, markIter.Current1.Col] =
|
||||
markIter.Current2.Player == Player.X ? 'X' : 'O';
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(" Tic-Tac-Toe");
|
||||
Console.WriteLine(" ═══════════");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(" 0 1 2");
|
||||
Console.WriteLine(" ┌───┬───┬───┐");
|
||||
for (int r = 0; r < 3; r++)
|
||||
{
|
||||
Console.Write($"{r} │");
|
||||
for (int c = 0; c < 3; c++)
|
||||
{
|
||||
Console.Write($" {grid[r, c]} ");
|
||||
if (c < 2) Console.Write("│");
|
||||
}
|
||||
Console.WriteLine("│");
|
||||
if (r < 2) Console.WriteLine(" ├───┼───┼───┤");
|
||||
}
|
||||
Console.WriteLine(" └───┴───┴───┘");
|
||||
Console.WriteLine();
|
||||
|
||||
// Status line.
|
||||
var statusText = state.Status switch
|
||||
{
|
||||
GameStatus.Playing => $"{(state.CurrentPlayer == Player.X ? 'X' : 'O')}'s turn (move {state.MoveCount + 1})",
|
||||
GameStatus.XWon => "X wins!",
|
||||
GameStatus.OWon => "O wins!",
|
||||
GameStatus.Draw => "It's a draw!",
|
||||
_ => ""
|
||||
};
|
||||
Console.WriteLine($" {statusText}");
|
||||
Console.WriteLine();
|
||||
Console.Write(" Enter row col (e.g. \"1 2\"): ");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
0 0
|
||||
1 1
|
||||
0 1
|
||||
1 0
|
||||
0 2
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
y
|
||||
1 0
|
||||
0 2
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
0 0
|
||||
1 1
|
||||
0 1
|
||||
Loading…
Reference in New Issue