1- using Codebreaker . ViewModels . Components ;
2- using Codebreaker . ViewModels . Contracts . Services ;
3- using Microsoft . Extensions . Options ;
1+ using Codebreaker . ViewModels . Contracts . Services ;
2+ using Codebreaker . ViewModels . Messages ;
3+ using System . ComponentModel ;
4+ using System . Net ;
45
56namespace Codebreaker . ViewModels ;
67
7- public enum GameMode
8+ public partial class GamePageViewModel ( IGamesClient gamesClient , IInfoBarService infoBarService ) : ObservableRecipient
89{
9- NotRunning ,
10- Started ,
11- MoveSet ,
12- Lost ,
13- Won
14- }
15-
16- public enum GameMoveValue
17- {
18- Started ,
19- Completed
20- }
21-
22- /// <summary>
23- /// Configure to enable dialogs (via <see cref="IDialogService"/>), or use the <see cref="InfoBarService"/>.
24- /// </summary>
25- public class GamePageViewModelOptions
26- {
27- }
28-
29- public partial class GamePageViewModel : ObservableObject
30- {
31- private readonly IGamesClient _client ;
32- private int _moveNumber = 0 ;
33-
34- private readonly IDialogService _dialogService ;
35- private readonly IInfoBarService _infoBarService ;
36-
37- public GamePageViewModel (
38- IGamesClient client ,
39- IOptions < GamePageViewModelOptions > options ,
40- IDialogService dialogService ,
41- IInfoBarService infoBarService
42- )
43- {
44- _client = client ;
45- _dialogService = dialogService ;
46- _infoBarService = infoBarService ;
47-
48- PropertyChanged += ( sender , e ) =>
49- {
50- if ( e . PropertyName == nameof ( GameStatus ) )
51- WeakReferenceMessenger . Default . Send ( new GameStateChangedMessage ( GameStatus ) ) ;
52- } ;
53- }
54-
55- private Game ? _game ;
56- /// <summary>
57- /// <see cref="Models.Game"/> instance."/>
58- /// </summary>
59- public Game ? Game
60- {
61- get => _game ;
62- set
63- {
64- OnPropertyChanging ( nameof ( Game ) ) ;
65- OnPropertyChanging ( nameof ( Fields ) ) ;
66- _game = value ;
67-
68- Fields . Clear ( ) ;
69-
70- for ( int i = 0 ; i < _game ? . NumberCodes ; i ++ )
71- {
72- SelectedFieldViewModel field = new ( ) ;
73- field . PropertyChanged += ( sender , e ) => SetMoveCommand . NotifyCanExecuteChanged ( ) ;
74- Fields . Add ( field ) ;
75- }
76-
77- OnPropertyChanged ( nameof ( Game ) ) ;
78- OnPropertyChanged ( nameof ( Fields ) ) ;
79- }
80- }
81-
82- [ ObservableProperty ]
83- private string _name = string . Empty ;
84-
85- [ NotifyPropertyChangedFor ( nameof ( IsNameEnterable ) ) ]
8610 [ ObservableProperty ]
87- private bool _isNamePredefined = false ;
88-
89- public ObservableCollection < SelectedFieldViewModel > Fields { get ; } = [ ] ;
11+ private bool _isLoading ;
9012
91- public ObservableCollection < SelectionAndKeyPegs > GameMoves { get ; } = [ ] ;
92-
93- /// <summary>
94- /// Status of the game. See <see cref="GameMode"/>.
95- /// </summary>
9613 [ ObservableProperty ]
97- private GameMode _gameStatus = GameMode . NotRunning ;
14+ private Game ? _game ;
9815
99- /// <summary>
100- /// An API call to the games-API is in-progress. Use to display progress indicators.
101- /// </summary>
102- [ NotifyPropertyChangedFor ( nameof ( IsNameEnterable ) ) ]
10316 [ ObservableProperty ]
104- private bool _inProgress = false ;
17+ private Field [ ] _selectedFields = [ ] ;
10518
10619 [ ObservableProperty ]
107- private bool _isCancelling = false ;
20+ [ NotifyCanExecuteChangedFor ( nameof ( StartGameCommand ) ) ]
21+ private string _username = string . Empty ;
10822
109- /// <summary>
110- /// The player name can be entered.
111- /// </summary>
112- public bool IsNameEnterable => ! InProgress && ! IsNamePredefined ;
23+ private bool CanStartGame ( ) => Username is not null && Username . Length > 2 ;
11324
114- /// <summary>
115- /// Starts a new game using <see cref="IGamesClient"/>.
116- /// Updates the <see cref="GameStatus"/> property.
117- /// Initializes <see cref="Game"/>).
118- /// Increments the move number.
119- /// Shows <see cref="IDialogService"/> messages or <see cref="_infoBarService"/> messages with errors.
120- /// </summary>
121- /// <returns>A task</returns>
122- [ RelayCommand ( AllowConcurrentExecutions = false , FlowExceptionsToTaskScheduler = true ) ]
123- private async Task StartGameAsync ( )
25+ [ RelayCommand ( CanExecute = nameof ( CanStartGame ) ) ]
26+ private async Task StartGameAsync ( CancellationToken cancellationToken )
12427 {
28+ IsLoading = true ;
29+ var usedGameMode = GameType . Game6x4 ;
30+ ( Guid id , int numberCode , int maxMoves , IDictionary < string , string [ ] > fieldValues ) response ;
31+
12532 try
12633 {
127- InitializeValues ( ) ;
128-
129- InProgress = true ;
130- ( Guid gameId , int numberCodes , int maxMoves , IDictionary < string , string [ ] > fieldValues ) = await _client . StartGameAsync ( GameType . Game6x4 , Name ) ;
131-
132- GameStatus = GameMode . Started ;
133-
134- Game = new Game ( gameId , GameType . Game6x4 , Name , DateTime . Now , numberCodes , maxMoves )
135- {
136- FieldValues = fieldValues
137- } ;
138- _moveNumber ++ ;
34+ response = await gamesClient . StartGameAsync ( usedGameMode , Username ) ;
13935 }
140- catch ( Exception ex )
36+ catch ( InvalidOperationException )
14137 {
142- _infoBarService . New
143- . IsErrorMessage ( )
144- . WithMessage ( ex . Message )
145- . WithAction ( ( message ) =>
146- {
147- GameStatus = GameMode . NotRunning ;
148- message . Close ( ) ;
149- } )
150- . Show ( ) ;
38+ infoBarService . New . WithMessage ( "Invalid operation" ) . Show ( ) ;
39+ return ;
40+ }
41+ catch ( HttpRequestException ex ) when ( ex . StatusCode == HttpStatusCode . BadRequest )
42+ {
43+ infoBarService . New . WithMessage ( ex . Message ) . Show ( ) ;
44+ return ;
45+ }
46+ catch ( HttpRequestException )
47+ {
48+ infoBarService . New . WithMessage ( "Networking issues" ) . Show ( ) ;
49+ return ;
15150 }
15251 finally
15352 {
154- InProgress = false ;
53+ IsLoading = false ;
15554 }
156- }
15755
158- // TODO: end of the game is not yet implemented (in the client library)
159- // [RelayCommand(AllowConcurrentExecutions = false, FlowExceptionsToTaskScheduler = true)]
160- // private async Task CancelGameAsync()
161- // {
162- // if (Game is null)
163- // throw new InvalidOperationException("No game running");
56+ Game = new Game ( response . id , usedGameMode , Username , DateTime . Now , response . numberCode , response . maxMoves , response . fieldValues ) ;
16457
165- // IsCancelling = true ;
58+ WeakReferenceMessenger . Default . Send ( new GameStartedMessage ( Game ) ) ;
16659
167- // try
168- // {
169- //// await _client.CancelGameAsync(Game!.Value.GameId);
170- // GameStatus = GameMode.NotRunning;
171- // }
172- // catch (Exception ex)
173- // {
174- // InfoBarMessageService.ShowError(ex.Message);
60+ // Initialize SelectedFields
61+ SelectedFields = Enumerable . Range ( 0 , Game . NumberCodes )
62+ . Select ( i =>
63+ {
64+ var field = new Field ( Game . FieldValues [ "colors" ] ) ; // TODO: Hardcoding "colors" is not suitable for all game types
65+ field . PropertyChanged += ( object ? sender , PropertyChangedEventArgs args ) => MakeMoveCommand . NotifyCanExecuteChanged ( ) ;
66+ return field ;
67+ } )
68+ . ToArray ( ) ;
69+ }
17570
176- // if (_enableDialogs)
177- // await _dialogService.ShowMessageAsync(ex.Message);
178- // }
179- // finally
180- // {
181- // IsCancelling = false;
182- // }
183- // }
71+ private bool CanMakeMove ( ) => SelectedFields . All ( field => field is not null ) ;
18472
185- /// <summary>
186- ///
187- /// </summary>
188- /// <returns>A task</returns>
189- /// <exception cref="InvalidOperationException"></exception>
190- [ RelayCommand ( CanExecute = nameof ( CanSetMove ) , AllowConcurrentExecutions = false , FlowExceptionsToTaskScheduler = true ) ]
191- private async Task SetMoveAsync ( )
73+ [ RelayCommand ( CanExecute = nameof ( CanMakeMove ) ) ]
74+ private async Task MakeMoveAsync ( CancellationToken cancellationToken )
19275 {
193- try
194- {
195- InProgress = true ;
196- WeakReferenceMessenger . Default . Send ( new GameMoveMessage ( GameMoveValue . Started ) ) ;
197-
198- if ( _game is null )
199- throw new InvalidOperationException ( "no game running" ) ;
200-
201- if ( Fields . Count != _game . NumberCodes || Fields . Any ( x => ! x . IsSet ) )
202- throw new InvalidOperationException ( "all colors need to be selected before invoking this method" ) ;
203-
204- string [ ] guessPegs = Fields . Select ( x => x . Value ! ) . ToArray ( ) ;
76+ if ( Game is null )
77+ throw new InvalidOperationException ( "A game needs to be started before making a move" ) ;
20578
206- ( string [ ] keyPegs , bool ended , bool isVictory ) = await _client . SetMoveAsync ( _game . GameId , Name , GameType . Game6x4 , _moveNumber , guessPegs ) ;
79+ var selectedColors = SelectedFields
80+ . Select ( x => x . Color )
81+ . ToArray ( ) ;
20782
208- SelectionAndKeyPegs selectionAndKeyPegs = new ( guessPegs , keyPegs , _moveNumber ++ ) ;
209- GameMoves . Add ( selectionAndKeyPegs ) ;
210- GameStatus = GameMode . MoveSet ;
83+ if ( selectedColors . Any ( color => color is null ) )
84+ throw new InvalidOperationException ( "All colors need to be selected before making a move" ) ;
21185
212- WeakReferenceMessenger . Default . Send ( new GameMoveMessage ( GameMoveValue . Completed , selectionAndKeyPegs ) ) ;
86+ WeakReferenceMessenger . Default . Send ( new MakeMoveMessage ( new ( selectedColors ! ) ) ) ;
21387
214- if ( isVictory )
215- {
216- GameStatus = GameMode . Won ;
217- _infoBarService . New . IsSuccessMessage ( ) . WithMessage ( "Congratulations - you won!" ) . Show ( ) ;
218- }
219- else if ( ended )
220- {
221- GameStatus = GameMode . Lost ;
222- _infoBarService . New . WithMessage ( "Sorry, you didn't find the matching colors!" ) . Show ( ) ;
223- }
88+ IsLoading = true ;
89+ ( string [ ] keyPegs , bool hasEnded , bool isVictory ) response ;
90+ try
91+ {
92+ response = await gamesClient . SetMoveAsync ( Game . Id , Game . PlayerName , GameType . Game6x4 , Game . Moves . Count + 1 , selectedColors ! ) ;
22493 }
225- catch ( Exception ex )
94+ catch ( InvalidOperationException )
22695 {
227- _infoBarService . New . WithMessage ( ex . Message ) . IsErrorMessage ( ) . Show ( ) ;
96+ infoBarService . New . WithMessage ( "Invalid operation" ) . Show ( ) ;
97+ return ;
98+ }
99+ catch ( HttpRequestException ex ) when ( ex . StatusCode == HttpStatusCode . BadRequest )
100+ {
101+ infoBarService . New . WithMessage ( ex . Message ) . Show ( ) ;
102+ return ;
103+ }
104+ catch ( HttpRequestException )
105+ {
106+ infoBarService . New . WithMessage ( "Networking issues" ) . Show ( ) ;
107+ return ;
228108 }
229109 finally
230110 {
231- ClearSelectedColor ( ) ;
232- InProgress = false ;
111+ IsLoading = false ;
233112 }
234- }
235-
236- private bool CanSetMove =>
237- Fields . All ( s => s is not null && s . IsSet ) ;
238113
239- private void ClearSelectedColor ( )
240- {
241- for ( int i = 0 ; i < Fields . Count ; i ++ )
242- Fields [ i ] . Reset ( ) ;
114+ var newMove = new Move ( selectedColors ! , response . keyPegs ) ;
115+ Game . Moves . Add ( newMove ) ;
116+ WeakReferenceMessenger . Default . Send ( new MakeMoveMessage ( newMove ) ) ;
243117
244- SetMoveCommand . NotifyCanExecuteChanged ( ) ;
245- }
246-
247- private void InitializeValues ( )
248- {
249- ClearSelectedColor ( ) ;
250- GameMoves . Clear ( ) ;
251- GameStatus = GameMode . NotRunning ;
252- _infoBarService . Clear ( ) ;
253- _moveNumber = 0 ;
118+ if ( response . hasEnded )
119+ {
120+ Game . EndTime = DateTime . Now ;
121+ Game . IsVictory = response . isVictory ;
122+ WeakReferenceMessenger . Default . Send ( new GameEndedMessage ( Game ) ) ;
123+ }
254124 }
255- }
256-
257- /// <summary>
258- ///
259- /// </summary>
260- /// <param name="GuessPegs">String representation of guesses</param>
261- /// <param name="KeyPegs">String representation of results</param>
262- /// <param name="MoveNumber">The move number</param>
263- public record SelectionAndKeyPegs ( string [ ] GuessPegs , string [ ] KeyPegs , int MoveNumber ) ;
264-
265- public record class GameStateChangedMessage ( GameMode GameMode ) ;
266-
267- /// <summary>
268- /// Messages sent when the games starts, or a move is set.
269- /// </summary>
270- /// <param name="GameMoveValue"><see cref="GameMoveValue"/></param>
271- /// <param name="SelectionAndKeyPegs"><see cref="SelectionAndKeyPegs"/></param>
272- public record class GameMoveMessage ( GameMoveValue GameMoveValue , SelectionAndKeyPegs ? SelectionAndKeyPegs = null ) ;
125+ }
0 commit comments