Skip to content

Commit 3909011

Browse files
authored
Merge pull request #191 from CodebreakerApp/190-implement-gamemodes-winui
Prepared the VM for shapes and gametype selection
2 parents f0c7781 + d343818 commit 3909011

25 files changed

Lines changed: 447 additions & 212 deletions
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace System.Collections.Generic;
2+
3+
internal static class DictionaryExtensions
4+
{
5+
public static TValue? GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue? defaultValue = default) =>
6+
dictionary.TryGetValue(key, out var value) ? value : defaultValue;
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Codebreaker.ViewModels.Models.Extensions;
2+
3+
internal static class FieldExtensions
4+
{
5+
public static IEnumerable<string> Serialize(this IEnumerable<Field> fields) =>
6+
fields.Select(f => f.Serialize());
7+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace Codebreaker.ViewModels.Models;
4+
public partial class Field : IParsable<Field>
5+
{
6+
public static Field Parse(string s, IFormatProvider? provider = null) =>
7+
s?.Split(';', StringSplitOptions.RemoveEmptyEntries) switch
8+
{
9+
[string shape, string color] => new() { Shape = shape, Color = color },
10+
[string color] => new() { Color = color },
11+
_ => throw new FormatException()
12+
};
13+
14+
public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Field result)
15+
{
16+
result = s?.Split(';', StringSplitOptions.RemoveEmptyEntries) switch
17+
{
18+
[string shape, string color] => new() { Shape = shape, Color = color },
19+
[string color] => new() { Color = color },
20+
_ => null
21+
};
22+
23+
return result is not null;
24+
}
25+
26+
public static IEnumerable<Field> Parse(IEnumerable<string> strings, IFormatProvider? provider = null) =>
27+
strings.Select(s => Parse(s, provider));
28+
29+
public static bool TryParse([NotNullWhen(true)] IEnumerable<string> strings, IFormatProvider? provider, [MaybeNullWhen(false)] out IEnumerable<Field> result)
30+
{
31+
List<Field> tempResult = [];
32+
33+
foreach (var s in strings)
34+
{
35+
if (!TryParse(s, provider, out var partialResult))
36+
{
37+
result = null;
38+
return false;
39+
}
40+
41+
tempResult.Add(partialResult);
42+
}
43+
44+
result = tempResult;
45+
return true;
46+
}
47+
48+
public string Serialize() =>
49+
Shape is not null
50+
? $"{Shape};{Color}"
51+
: Color;
52+
53+
public string ToString(string? format, IFormatProvider? formatProvider) =>
54+
Serialize();
55+
}

src/Codebreaker.ViewModels/Models/Field.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,27 @@
33
/// <summary>
44
/// Represents a field in a move.
55
/// </summary>
6-
/// <param name="possibleColors">The possible colors for this field.</param>
7-
public partial class Field(string[] possibleColors) : ObservableObject
6+
public partial class Field : ObservableObject
87
{
8+
public Field()
9+
{
10+
}
11+
12+
public Field(string? color, string? shape)
13+
{
14+
Color = color;
15+
Shape = shape;
16+
}
17+
918
/// <summary>
1019
/// The selected color for this field.
1120
/// </summary>
1221
[ObservableProperty]
1322
private string? _color;
1423

1524
/// <summary>
16-
/// The possible colors for this field.
25+
/// The selected shape for this field.
1726
/// </summary>
18-
public string[] PossibleColors { get; init; } = possibleColors;
27+
[ObservableProperty]
28+
private string? _shape;
1929
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
namespace Codebreaker.ViewModels.Models;
22

3-
public class Move(ICollection<string> guessPegs, ICollection<string>? keyPegs = null)
3+
public class Move(ICollection<Field> guessPegs, ICollection<string>? keyPegs = null)
44
{
55
/// <summary>
66
/// The guess pegs from the user for this move.
77
/// </summary>
8-
public ICollection<string> GuessPegs { get; } = guessPegs;
8+
public ICollection<Field> GuessPegs { get; } = guessPegs;
99

1010
/// <summary>
1111
/// The result from the analyer for this move based on the associated game that contains the move.
@@ -14,4 +14,4 @@ public class Move(ICollection<string> guessPegs, ICollection<string>? keyPegs =
1414
/// Null if the move was not analyzed yet.
1515
/// </remarks>
1616
public ICollection<string>? KeyPegs { get; } = keyPegs;
17-
}
17+
}

src/Codebreaker.ViewModels/Pages/GamePageViewModel.cs

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,27 @@ public partial class GamePageViewModel(IGamesClient gamesClient, IInfoBarService
2020
[NotifyCanExecuteChangedFor(nameof(StartGameCommand))]
2121
private string _username = string.Empty;
2222

23+
[ObservableProperty]
24+
private GameType _selectedGameType = GameType.Game6x4;
25+
26+
public IEnumerable<GameType> GameTypes { get; } = [
27+
GameType.Game6x4,
28+
GameType.Game6x4Mini,
29+
GameType.Game8x5,
30+
GameType.Game5x5x4
31+
];
32+
2333
private bool CanStartGame() => Username is not null && Username.Length > 2;
2434

2535
[RelayCommand(CanExecute = nameof(CanStartGame))]
2636
private async Task StartGameAsync(CancellationToken cancellationToken)
2737
{
2838
IsLoading = true;
29-
var usedGameMode = GameType.Game6x4;
3039

3140
try
3241
{
33-
var response = await gamesClient.StartGameAsync(usedGameMode, Username);
34-
Game = new Game(response.Id, usedGameMode, Username, DateTime.Now, response.NumberCodes, response.MaxMoves, response.FieldValues);
42+
var response = await gamesClient.StartGameAsync(SelectedGameType, Username);
43+
Game = new Game(response.Id, SelectedGameType, Username, DateTime.Now, response.NumberCodes, response.MaxMoves, response.FieldValues);
3544
}
3645
catch (InvalidOperationException)
3746
{
@@ -59,7 +68,7 @@ private async Task StartGameAsync(CancellationToken cancellationToken)
5968
SelectedFields = Enumerable.Range(0, Game.NumberCodes)
6069
.Select(i =>
6170
{
62-
var field = new Field(Game.FieldValues["colors"]); // TODO: Hardcoding "colors" is not suitable for all game types
71+
var field = new Field();
6372
field.PropertyChanged += (object? sender, PropertyChangedEventArgs args) => MakeMoveCommand.NotifyCanExecuteChanged();
6473
return field;
6574
})
@@ -74,21 +83,20 @@ private async Task MakeMoveAsync(CancellationToken cancellationToken)
7483
if (Game is null)
7584
throw new InvalidOperationException("A game needs to be started before making a move");
7685

77-
var selectedColors = SelectedFields
78-
.Select(x => x.Color)
79-
.ToArray();
80-
81-
if (selectedColors.Any(color => color is null))
86+
if (SelectedFields.Any(field => field.Color is null))
8287
throw new InvalidOperationException("All colors need to be selected before making a move");
8388

84-
WeakReferenceMessenger.Default.Send(new MakeMoveMessage(new(selectedColors!)));
89+
WeakReferenceMessenger.Default.Send(new MakeMoveMessage(new(SelectedFields)));
8590

91+
var serializedFields = SelectedFields.Select(field => field.Serialize()).ToArray();
8692
IsLoading = true;
8793
try
8894
{
89-
var response = await gamesClient.SetMoveAsync(Game.Id, Game.PlayerName, GameType.Game6x4, Game.Moves.Count + 1, selectedColors!);
95+
var response = await gamesClient.SetMoveAsync(Game.Id, Game.PlayerName, Game.GameType, Game.Moves.Count + 1, serializedFields);
9096

91-
var newMove = new Move(selectedColors!, response.Results);
97+
// It is necessary to copy the fields to avoid every move having the same reference to the same fields
98+
var copiedFields = SelectedFields.Select(f => new Field(f.Color, f.Shape)).ToArray();
99+
var newMove = new Move(copiedFields, response.Results);
92100
Game.Moves.Add(newMove);
93101
WeakReferenceMessenger.Default.Send(new MakeMoveMessage(newMove));
94102

src/Codebreaker.WinUI/App.xaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
<ResourceDictionary Source="/Styles/FontSizes.xaml" />
1111
<ResourceDictionary Source="/Styles/Thickness.xaml" />
1212
<ResourceDictionary Source="/Styles/TextBlock.xaml" />
13-
<ResourceDictionary Source="/Views/Templates/CodeBreakerTemplates.xaml" />
1413
<!-- Other merged dictionaries here -->
1514
</ResourceDictionary.MergedDictionaries>
1615
<!-- Other app resources here -->

src/Codebreaker.WinUI/Codebreaker.WinUI.csproj

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,6 @@
6363
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
6464
<PackageReference Include="WinUIEx" Version="2.3.4" />
6565
</ItemGroup>
66-
<ItemGroup>
67-
<Page Update="Views\Templates\CodeBreakerTemplates.xaml">
68-
<Generator>MSBuild:Compile</Generator>
69-
</Page>
70-
</ItemGroup>
7166
<ItemGroup>
7267
<Page Update="Styles\FontSizes.xaml">
7368
<Generator>MSBuild:Compile</Generator>
@@ -98,6 +93,9 @@
9893
<ItemGroup>
9994
<None Remove="Assets\Animations\LostAnimation_300_opt.gif" />
10095
<None Remove="Assets\Animations\WonAnimation_300_opt.gif" />
96+
<None Remove="Views\Components\GamePage\Gamebar.xaml" />
97+
<None Remove="Views\Components\GamePage\Moves.xaml" />
98+
<None Remove="Views\Components\GamePage\StartGameComponent.xaml" />
10199
<None Remove="Views\Components\GameResultDisplay.xaml" />
102100
<None Remove="Views\Components\InfoBarArea.xaml" />
103101
<None Remove="Views\Components\PegSelectionComponent.xaml" />
@@ -114,6 +112,21 @@
114112
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
115113
</Content>
116114
</ItemGroup>
115+
<ItemGroup>
116+
<Page Update="Views\Components\GamePage\Moves.xaml">
117+
<Generator>MSBuild:Compile</Generator>
118+
</Page>
119+
</ItemGroup>
120+
<ItemGroup>
121+
<Page Update="Views\Components\GamePage\Gamebar.xaml">
122+
<Generator>MSBuild:Compile</Generator>
123+
</Page>
124+
</ItemGroup>
125+
<ItemGroup>
126+
<Page Update="Views\Components\GamePage\StartGameComponent.xaml">
127+
<Generator>MSBuild:Compile</Generator>
128+
</Page>
129+
</ItemGroup>
117130
<ItemGroup>
118131
<Page Update="Views\Components\GameResultDisplay.xaml">
119132
<Generator>MSBuild:Compile</Generator>

src/Codebreaker.WinUI/Helpers/PageExtensions.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,31 @@ namespace CodeBreaker.WinUI.Helpers;
55

66
internal static class PageExtensions
77
{
8+
/// <summary>
9+
/// Registers all message recipients and unregisters them when the page is unloaded.
10+
/// </summary>
11+
/// <param name="messenger">The messenger instance.</param>
12+
/// <param name="page">The page to register and unregister the message recipients.</param>
13+
public static void RegisterAllAndUnregisterAllOnUnloaded(this IMessenger messenger, FrameworkElement page)
14+
{
15+
messenger.RegisterAll(page);
16+
messenger.UnregisterAllOnUnloaded(page, page);
17+
}
18+
19+
/// <summary>
20+
/// Unregisters all message recipients when the page is unloaded.
21+
/// </summary>
22+
/// <param name="messenger">The messenger instance.</param>
23+
/// <param name="page">The page to unregister the message recipients.</param>
824
public static void UnregisterAllOnUnloaded(this IMessenger messenger, FrameworkElement page) =>
925
messenger.UnregisterAllOnUnloaded(page, page);
1026

27+
/// <summary>
28+
/// Unregisters all message recipients when the page is unloaded.
29+
/// </summary>
30+
/// <param name="messenger">The messenger instance.</param>
31+
/// <param name="page">The page to unregister the message recipients.</param>
32+
/// <param name="messageRecepient">The specific message recipient to unregister.</param>
1133
public static void UnregisterAllOnUnloaded(this IMessenger messenger, FrameworkElement page, object messageRecepient)
1234
{
1335
void UnloadedCallback(object sender, RoutedEventArgs args)
@@ -19,6 +41,11 @@ void UnloadedCallback(object sender, RoutedEventArgs args)
1941
page.Unloaded += UnloadedCallback;
2042
}
2143

44+
/// <summary>
45+
/// Calls the specified action once when the page is unloaded.
46+
/// </summary>
47+
/// <param name="page">The page to call the action on.</param>
48+
/// <param name="action">The action to be called.</param>
2249
public static void CallOnceOnUnloaded(this FrameworkElement page, Action<object, RoutedEventArgs> action)
2350
{
2451
void Callback(object sender, RoutedEventArgs args)
@@ -30,6 +57,12 @@ void Callback(object sender, RoutedEventArgs args)
3057
page.Unloaded += Callback;
3158
}
3259

60+
/// <summary>
61+
/// Finds all children of the specified type recursively.
62+
/// </summary>
63+
/// <typeparam name="T">The type of the children to find.</typeparam>
64+
/// <param name="dependencyObject">The dependency object to search for children.</param>
65+
/// <returns>An enumerable collection of children of the specified type.</returns>
3366
public static IEnumerable<T> FindChildrenRecursively<T>(this DependencyObject dependencyObject)
3467
where T : DependencyObject
3568
{

src/Codebreaker.WinUI/Views/Components/GameResultDisplay.xaml renamed to src/Codebreaker.WinUI/Views/Components/GamePage/GameResultDisplay.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<UserControl
3-
x:Class="CodeBreaker.WinUI.Views.Components.GameResultDisplay"
3+
x:Class="CodeBreaker.WinUI.Views.Components.GamePage.GameResultDisplay"
44
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
55
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
66
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

0 commit comments

Comments
 (0)