Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
55d3446
Moved components into more specifiy sub-directories/namespaces.
szv Jul 4, 2024
2aadadd
Introduced extension for method for registering for all messages and …
szv Jul 4, 2024
c8159d5
Added documentation comments
szv Jul 4, 2024
fefd16b
Moved most parts of the GamePage to newly created UserControls to imp…
szv Jul 4, 2024
e215481
Added missing converter
szv Jul 4, 2024
19ecbad
Moved the PegsTemplate from the seperate template file directly into …
szv Jul 4, 2024
7354456
Added shape to Field
szv Jul 8, 2024
34a664d
Added partial class for field providing serialization and deserializa…
szv Jul 8, 2024
717c24e
Added partial class for field for validation purposes
szv Jul 8, 2024
837c891
Use the Field class for guesses in the Move class
szv Jul 8, 2024
973029d
Added extensions for Field
szv Jul 8, 2024
4774298
Introduced extension method for dictionaries for getting the desired …
szv Jul 8, 2024
abc3469
Adapted the GamePageViewModel for recent changes + prepared for other…
szv Jul 8, 2024
1515541
Removed properites for possible colors and shapes in the Field class.
szv Jul 10, 2024
59ffbee
Added constructors for shorter instantiation of the Field class
szv Jul 10, 2024
027d25a
Removed validation partial class for Field
szv Jul 10, 2024
8d4460e
Adapted the GamePageViewModel for the changes newly made to the Field
szv Jul 10, 2024
42340a3
Copied the selected fields to prevent every move holding references t…
szv Jul 10, 2024
d343818
Prepared the GamePageViewModel for game-type selection in the UI
szv Jul 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/Codebreaker.ViewModels/Extensions/DictionaryExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace System.Collections.Generic;

internal static class DictionaryExtensions
{
public static TValue? GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue? defaultValue = default) =>
dictionary.TryGetValue(key, out var value) ? value : defaultValue;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Codebreaker.ViewModels.Models.Extensions;

internal static class FieldExtensions
{
public static IEnumerable<string> Serialize(this IEnumerable<Field> fields) =>
fields.Select(f => f.Serialize());
}
55 changes: 55 additions & 0 deletions src/Codebreaker.ViewModels/Models/Field.Serializable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Diagnostics.CodeAnalysis;

namespace Codebreaker.ViewModels.Models;
public partial class Field : IParsable<Field>
{
public static Field Parse(string s, IFormatProvider? provider = null) =>
s?.Split(';', StringSplitOptions.RemoveEmptyEntries) switch
{
[string shape, string color] => new() { Shape = shape, Color = color },
[string color] => new() { Color = color },
_ => throw new FormatException()
};

public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Field result)
{
result = s?.Split(';', StringSplitOptions.RemoveEmptyEntries) switch
{
[string shape, string color] => new() { Shape = shape, Color = color },
[string color] => new() { Color = color },
_ => null
};

return result is not null;
}

public static IEnumerable<Field> Parse(IEnumerable<string> strings, IFormatProvider? provider = null) =>
strings.Select(s => Parse(s, provider));

public static bool TryParse([NotNullWhen(true)] IEnumerable<string> strings, IFormatProvider? provider, [MaybeNullWhen(false)] out IEnumerable<Field> result)
{
List<Field> tempResult = [];

foreach (var s in strings)
{
if (!TryParse(s, provider, out var partialResult))
{
result = null;
return false;
}

tempResult.Add(partialResult);
}

result = tempResult;
return true;
}

public string Serialize() =>
Shape is not null
? $"{Shape};{Color}"
: Color;

public string ToString(string? format, IFormatProvider? formatProvider) =>
Serialize();
}
18 changes: 14 additions & 4 deletions src/Codebreaker.ViewModels/Models/Field.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,27 @@
/// <summary>
/// Represents a field in a move.
/// </summary>
/// <param name="possibleColors">The possible colors for this field.</param>
public partial class Field(string[] possibleColors) : ObservableObject
public partial class Field : ObservableObject
{
public Field()
{
}

public Field(string? color, string? shape)
{
Color = color;
Shape = shape;
}

/// <summary>
/// The selected color for this field.
/// </summary>
[ObservableProperty]
private string? _color;

/// <summary>
/// The possible colors for this field.
/// The selected shape for this field.
/// </summary>
public string[] PossibleColors { get; init; } = possibleColors;
[ObservableProperty]
private string? _shape;
}
6 changes: 3 additions & 3 deletions src/Codebreaker.ViewModels/Models/Move.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
namespace Codebreaker.ViewModels.Models;

public class Move(ICollection<string> guessPegs, ICollection<string>? keyPegs = null)
public class Move(ICollection<Field> guessPegs, ICollection<string>? keyPegs = null)
{
/// <summary>
/// The guess pegs from the user for this move.
/// </summary>
public ICollection<string> GuessPegs { get; } = guessPegs;
public ICollection<Field> GuessPegs { get; } = guessPegs;

/// <summary>
/// The result from the analyer for this move based on the associated game that contains the move.
Expand All @@ -14,4 +14,4 @@ public class Move(ICollection<string> guessPegs, ICollection<string>? keyPegs =
/// Null if the move was not analyzed yet.
/// </remarks>
public ICollection<string>? KeyPegs { get; } = keyPegs;
}
}
32 changes: 20 additions & 12 deletions src/Codebreaker.ViewModels/Pages/GamePageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,27 @@ public partial class GamePageViewModel(IGamesClient gamesClient, IInfoBarService
[NotifyCanExecuteChangedFor(nameof(StartGameCommand))]
private string _username = string.Empty;

[ObservableProperty]
private GameType _selectedGameType = GameType.Game6x4;

public IEnumerable<GameType> GameTypes { get; } = [
GameType.Game6x4,
GameType.Game6x4Mini,
GameType.Game8x5,
GameType.Game5x5x4
];

private bool CanStartGame() => Username is not null && Username.Length > 2;

[RelayCommand(CanExecute = nameof(CanStartGame))]
private async Task StartGameAsync(CancellationToken cancellationToken)
{
IsLoading = true;
var usedGameMode = GameType.Game6x4;

try
{
var response = await gamesClient.StartGameAsync(usedGameMode, Username);
Game = new Game(response.Id, usedGameMode, Username, DateTime.Now, response.NumberCodes, response.MaxMoves, response.FieldValues);
var response = await gamesClient.StartGameAsync(SelectedGameType, Username);
Game = new Game(response.Id, SelectedGameType, Username, DateTime.Now, response.NumberCodes, response.MaxMoves, response.FieldValues);
}
catch (InvalidOperationException)
{
Expand Down Expand Up @@ -59,7 +68,7 @@ private async Task StartGameAsync(CancellationToken cancellationToken)
SelectedFields = Enumerable.Range(0, Game.NumberCodes)
.Select(i =>
{
var field = new Field(Game.FieldValues["colors"]); // TODO: Hardcoding "colors" is not suitable for all game types
var field = new Field();
field.PropertyChanged += (object? sender, PropertyChangedEventArgs args) => MakeMoveCommand.NotifyCanExecuteChanged();
return field;
})
Expand All @@ -74,21 +83,20 @@ private async Task MakeMoveAsync(CancellationToken cancellationToken)
if (Game is null)
throw new InvalidOperationException("A game needs to be started before making a move");

var selectedColors = SelectedFields
.Select(x => x.Color)
.ToArray();

if (selectedColors.Any(color => color is null))
if (SelectedFields.Any(field => field.Color is null))
throw new InvalidOperationException("All colors need to be selected before making a move");

WeakReferenceMessenger.Default.Send(new MakeMoveMessage(new(selectedColors!)));
WeakReferenceMessenger.Default.Send(new MakeMoveMessage(new(SelectedFields)));

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

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

Expand Down
1 change: 0 additions & 1 deletion src/Codebreaker.WinUI/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
<ResourceDictionary Source="/Styles/FontSizes.xaml" />
<ResourceDictionary Source="/Styles/Thickness.xaml" />
<ResourceDictionary Source="/Styles/TextBlock.xaml" />
<ResourceDictionary Source="/Views/Templates/CodeBreakerTemplates.xaml" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<!-- Other app resources here -->
Expand Down
23 changes: 18 additions & 5 deletions src/Codebreaker.WinUI/Codebreaker.WinUI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,6 @@
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="WinUIEx" Version="2.3.4" />
</ItemGroup>
<ItemGroup>
<Page Update="Views\Templates\CodeBreakerTemplates.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Styles\FontSizes.xaml">
<Generator>MSBuild:Compile</Generator>
Expand Down Expand Up @@ -98,6 +93,9 @@
<ItemGroup>
<None Remove="Assets\Animations\LostAnimation_300_opt.gif" />
<None Remove="Assets\Animations\WonAnimation_300_opt.gif" />
<None Remove="Views\Components\GamePage\Gamebar.xaml" />
<None Remove="Views\Components\GamePage\Moves.xaml" />
<None Remove="Views\Components\GamePage\StartGameComponent.xaml" />
<None Remove="Views\Components\GameResultDisplay.xaml" />
<None Remove="Views\Components\InfoBarArea.xaml" />
<None Remove="Views\Components\PegSelectionComponent.xaml" />
Expand All @@ -114,6 +112,21 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Page Update="Views\Components\GamePage\Moves.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\Components\GamePage\Gamebar.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\Components\GamePage\StartGameComponent.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\Components\GameResultDisplay.xaml">
<Generator>MSBuild:Compile</Generator>
Expand Down
33 changes: 33 additions & 0 deletions src/Codebreaker.WinUI/Helpers/PageExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,31 @@ namespace CodeBreaker.WinUI.Helpers;

internal static class PageExtensions
{
/// <summary>
/// Registers all message recipients and unregisters them when the page is unloaded.
/// </summary>
/// <param name="messenger">The messenger instance.</param>
/// <param name="page">The page to register and unregister the message recipients.</param>
public static void RegisterAllAndUnregisterAllOnUnloaded(this IMessenger messenger, FrameworkElement page)
{
messenger.RegisterAll(page);
messenger.UnregisterAllOnUnloaded(page, page);
}

/// <summary>
/// Unregisters all message recipients when the page is unloaded.
/// </summary>
/// <param name="messenger">The messenger instance.</param>
/// <param name="page">The page to unregister the message recipients.</param>
public static void UnregisterAllOnUnloaded(this IMessenger messenger, FrameworkElement page) =>
messenger.UnregisterAllOnUnloaded(page, page);

/// <summary>
/// Unregisters all message recipients when the page is unloaded.
/// </summary>
/// <param name="messenger">The messenger instance.</param>
/// <param name="page">The page to unregister the message recipients.</param>
/// <param name="messageRecepient">The specific message recipient to unregister.</param>
public static void UnregisterAllOnUnloaded(this IMessenger messenger, FrameworkElement page, object messageRecepient)
{
void UnloadedCallback(object sender, RoutedEventArgs args)
Expand All @@ -19,6 +41,11 @@ void UnloadedCallback(object sender, RoutedEventArgs args)
page.Unloaded += UnloadedCallback;
}

/// <summary>
/// Calls the specified action once when the page is unloaded.
/// </summary>
/// <param name="page">The page to call the action on.</param>
/// <param name="action">The action to be called.</param>
public static void CallOnceOnUnloaded(this FrameworkElement page, Action<object, RoutedEventArgs> action)
{
void Callback(object sender, RoutedEventArgs args)
Expand All @@ -30,6 +57,12 @@ void Callback(object sender, RoutedEventArgs args)
page.Unloaded += Callback;
}

/// <summary>
/// Finds all children of the specified type recursively.
/// </summary>
/// <typeparam name="T">The type of the children to find.</typeparam>
/// <param name="dependencyObject">The dependency object to search for children.</param>
/// <returns>An enumerable collection of children of the specified type.</returns>
public static IEnumerable<T> FindChildrenRecursively<T>(this DependencyObject dependencyObject)
where T : DependencyObject
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<UserControl
x:Class="CodeBreaker.WinUI.Views.Components.GameResultDisplay"
x:Class="CodeBreaker.WinUI.Views.Components.GamePage.GameResultDisplay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Codebreaker.ViewModels.Messages;
using CommunityToolkit.Mvvm.Messaging;

namespace CodeBreaker.WinUI.Views.Components;
namespace CodeBreaker.WinUI.Views.Components.GamePage;

public sealed partial class GameResultDisplay : UserControl,
IRecipient<GameEndedMessage>,
Expand Down
37 changes: 37 additions & 0 deletions src/Codebreaker.WinUI/Views/Components/GamePage/Gamebar.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<UserControl
x:Class="CodeBreaker.WinUI.Views.Components.GamePage.Gamebar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CodeBreaker.WinUI.Views.Components.GamePage"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ca="using:CodeBreaker.WinUI.CustomAttachedProperties"
xmlns:cme="using:CodeBreaker.WinUI.CustomMarkupExtensions"
xmlns:toolkitConverters="using:CommunityToolkit.WinUI.UI.Converters"
mc:Ignorable="d">
<UserControl.Resources>
<toolkitConverters:BoolToVisibilityConverter x:Key="BoolToVisibility" />
</UserControl.Resources>

<StackPanel Orientation="Horizontal">
<Button
Height="92" Margin="0,-6,4,0" Width="100"
ca:Confirm.Enabled="True"
ca:Confirm.Title="{cme:ResourceString Name=GamePage_CancelConfirmTitle}"
ca:Confirm.Content="{cme:ResourceString Name=GamePage_CancelConfirmContent}"
ca:Confirm.PrimaryCommand="{x:Bind ViewModel.CancelGameCommand, Mode=OneTime}"
ca:Confirm.PrimaryText="{cme:ResourceString Name=Yes}"
ca:Confirm.CloseText="{cme:ResourceString Name=No}">
<StackPanel Orientation="Vertical" Spacing="05">
<SymbolIcon Symbol="Cancel" Visibility="{x:Bind ViewModel.CancelGameCommand.IsRunning, Mode=OneWay,Converter={StaticResource BoolToVisibility},ConverterParameter=True}" />
<ProgressRing Visibility="{x:Bind ViewModel.CancelGameCommand.IsRunning, Mode=OneWay}" />
<TextBlock Text="{cme:ResourceString Name=GamePage_CancelButtonText}" />
</StackPanel>
</Button>
<local:PegSelectionComponent
Grid.Row="3"
Margin="65,0,0,0"
ViewModel="{x:Bind ViewModel, Mode=OneWay}" />
</StackPanel>
</UserControl>
21 changes: 21 additions & 0 deletions src/Codebreaker.WinUI/Views/Components/GamePage/Gamebar.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Codebreaker.ViewModels;

namespace CodeBreaker.WinUI.Views.Components.GamePage;

public sealed partial class Gamebar : UserControl
{
public Gamebar()
{
InitializeComponent();
}

// Dependency property for the ViewModel
public GamePageViewModel ViewModel
{
get => (GamePageViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}

public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register(nameof(ViewModel), typeof(GamePageViewModel), typeof(StartGameComponent), new PropertyMetadata(null));
}
Loading