public ObservableParticipant(Participant participant, TournamentContext context) { source = participant; MiscProperties = new Dirtyable <ParticipantMiscProperties>(ParticipantMiscProperties.Parse(Misc)); OwningContext = context; //Check tournament start date, if it is later than missing time, clear the player's missing status. This happens in the case of a bracket reset var tournamentStart = context.Tournament.StartedAt; if (UtcTimeMissing.HasValue && tournamentStart.HasValue && UtcTimeMissing.Value.ToLocalTime() < tournamentStart.Value) { SetMissing(false); } //Do a similar check on station assignment time if (UtcTimeMatchAssigned.HasValue && tournamentStart.HasValue && UtcTimeMatchAssigned.Value.ToLocalTime() < tournamentStart.Value) { ClearStationAssignment(); } //Listen for when properties changed to that changed events for the convenience properties can also be fired. this.PropertyChanged += (sender, e) => { switch (e.PropertyName) { case "Misc": //Handle misc string changes by parsing the new values and raising the properties that have changed var oldMiscProperties = MiscProperties.Value; //Suggest new value for misc properties. This will be ignored if dirty MiscProperties.SuggestValue(ParticipantMiscProperties.Parse(Misc)); var miscProperties = MiscProperties.Value; //Check for changes from old to new, raise those properties if changed if (!object.Equals(oldMiscProperties.UtcTimeMissing, miscProperties.UtcTimeMissing)) { this.Raise("UtcTimeMissing", PropertyChanged); } if (!object.Equals(oldMiscProperties.UtcTimeMatchAssigned, miscProperties.UtcTimeMatchAssigned)) { this.Raise("UtcTimeMatchAssigned", PropertyChanged); } if (!object.Equals(oldMiscProperties.StationAssignment, miscProperties.StationAssignment)) { this.Raise("StationAssignment", PropertyChanged); } break; case "UtcTimeMissing": this.Raise("IsMissing", PropertyChanged); this.Raise("TimeSinceMissing", PropertyChanged); break; case "UtcTimeMatchAssigned": this.Raise("TimeSinceAssigned", PropertyChanged); this.Raise("IsAssignedToStation", PropertyChanged); break; } }; }
public ObservableMatch(Match match, TournamentContext context) { source = match; OwningContext = context; //Round doesn't change, initialize RoundFixed var totalPlayerCount = context.Tournament.ParticipantsCount; var lowerBoundExponent = Math.Floor(Math.Log(totalPlayerCount, 2)); var lowerBound = Math.Pow(2, lowerBoundExponent); if (match.Round < 0 && totalPlayerCount > lowerBound && totalPlayerCount <= lowerBound + (lowerBound / 2)) { Round = match.Round - 1; } else Round = match.Round; //Check if station assignment data checks out. If not, clear the assignment var player1Station = Player1 != null ? Player1.StationAssignment : default(string); var player2Station = Player2 != null ? Player2.StationAssignment : default(string); //If stations don't match, clear. Don't check completed matches because those will frequently have mismatching stations if (State != "complete" && player1Station != player2Station) ClearStationAssignment(); //Listen for when properties changed to that changed events for the convenience properties can also be fired. this.PropertyChanged += (sender, e) => { switch (e.PropertyName) { case "Player1Id": this.Raise("Player1", PropertyChanged); this.Raise("PlayerCount", PropertyChanged); if (Player1 != null) Player1.IsMissing = false; //When a player gets added to a match, clear their missing flag break; case "Player2Id": this.Raise("Player2", PropertyChanged); this.Raise("PlayerCount", PropertyChanged); if (Player2 != null) Player2.IsMissing = false; //When a player gets added to a match, clear their missing flag break; case "Player1PrereqMatchId": this.Raise("Player1PreviousMatch", PropertyChanged); break; case "Player2PrereqMatchId": this.Raise("Player2PreviousMatch", PropertyChanged); break; case "StartedAt": this.Raise("TimeSinceAvailable", PropertyChanged); break; case "State": //Clear station assignments if match state changes if (Player1 != null) Player1.ClearStationAssignment(); if (Player2 != null) Player2.ClearStationAssignment(); //If match state has changed to open, execute selected new match option if (State == "open") { var option = GlobalSettings.Instance.SelectedNewMatchAction; switch (option) { case NewMatchAction.AutoAssign: //TODO: Consider using lock block here to prevent potential multithreaded assignment to the same station var highestPriorityStation = Stations.Instance.GetBestNormalStation(); if (highestPriorityStation != null) AssignPlayersToStation(highestPriorityStation.Name); break; case NewMatchAction.Anywhere: AssignPlayersToStation("Any"); break; } } break; } }; var propertyChangedObs = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(h => this.PropertyChanged += h, h => this.PropertyChanged -= h); //The following will create an observable sequence that will raise an event either when player1 changes or when player1's station assignment status changes var player1ChangedOrAssignmentChanged = propertyChangedObs.Where(a => a.EventArgs.PropertyName == "Player1") .Select(_ => { if (Player1 != null) { return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(h => { player1Queue.Enqueue(Player1); Player1.PropertyChanged += h; }, h => { player1Queue.Dequeue().PropertyChanged -= h; }) .Where(a => a.EventArgs.PropertyName == "IsAssignedToStation" || a.EventArgs.PropertyName == "StationAssignment") .Select(_2 => EventArgs.Empty).StartWith(EventArgs.Empty); } else return Observable.Return(EventArgs.Empty); }).Switch(); //Subscribe to above observable sequence to maintain the assignment state of the match player1ChangedOrAssignmentChanged.Subscribe(_ => { IsMatchInProgress = Player1 != null && Player1.IsAssignedToStation; StationAssignment = Player1 == null ? null : Player1.StationAssignment; }); //Forcibly raise player1 property notification to assign station status this.Raise("Player1", PropertyChanged); }
public ObservableTournament(Tournament tournament, TournamentContext context) { source = tournament; OwningContext = context; }
public MainViewModel() { CurrentScreen = ScreenType.ApiKey; //Observable.Start(() => //{ // try // { // //I'm considering doing an http request to smashboards to find if a new version is released. I think smashboard's anti-DDOS protection is preventing it from working // WebRequest request = WebRequest.Create(ThreadUrl); // request.Credentials = CredentialCache.DefaultCredentials; // WebResponse response = request.GetResponse(); // if (((HttpWebResponse)response).StatusDescription == "OK") // { // Stream dataStream = response.GetResponseStream(); // StreamReader reader = new StreamReader(dataStream); // string responseFromServer = reader.ReadToEnd(); // reader.Close(); // } // response.Close(); // } // catch { /* ignore */ } //}); //Modify ViewModel state when an action is initiated Action startAction = () => { ErrorMessage = null; IsBusy = true; }; //Modify ViewModel state when an action is completed Action endAction = () => { IsBusy = false; }; //Modify ViewModel state when an action comes back with an exception Action<Exception> errorHandler = ex => { if (ex.InnerException is ChallongeApiException) { var cApiEx = (ChallongeApiException)ex.InnerException; if (cApiEx.Errors != null) ErrorMessage = cApiEx.Errors.Aggregate((one, two) => one + "\r\n" + two); else ErrorMessage = string.Format("Error with ResponseStatus \"{0}\" and StatusCode \"{1}\". {2}", cApiEx.RestResponse.ResponseStatus, cApiEx.RestResponse.StatusCode, cApiEx.RestResponse.ErrorMessage); } else { ErrorMessage = ex.NewLineDelimitedMessages(); } IsBusy = false; }; var dispatcher = System.Threading.SynchronizationContext.Current; //Handle next button press NextCommand = Command.CreateAsync(() => true, () => { switch (CurrentScreen) { case ScreenType.ApiKey: var subdomain = string.IsNullOrWhiteSpace(Subdomain) ? null : Subdomain; Portal = new ChallongePortal(ApiKey, subdomain); //Load list of tournaments that match apikey/subdomain TournamentCollection = Portal.GetTournaments().OrderByDescending(t => t.CreatedAt).ToArray(); try { //This is a silly method for checking whether a new application version exists without me having my own website. //I manage the most recent version number in the description of a tournament hosted on challonge. This code fetches that number var versionCheckPortal = new ChallongePortal(ApiKey, "fizzitestorg"); MostRecentVersion = versionCheckPortal.GetTournaments().Where(t => t.Name == "CMDVersionTest").Select(t => { //Modifying the description seems to put some html formatting into the result. This filters the description for //just the version number by itself var versionResult = string.Concat(t.Description.Where(c => char.IsDigit(c) || c == '.')); return versionResult; }).First(); //Check both version numbers to determine if current version is older than recent version var versionCompareResult = Version.Split('.').Zip(MostRecentVersion.Split('.'), (v, mrv) => { return int.Parse(v).CompareTo(int.Parse(mrv)); }).FirstOrDefault(i => i != 0); //If app version is older than most recent version, show message IsVersionOutdatedVisible = versionCompareResult < 0; } catch (Exception) { //If version check fails simply ignore the problem and move on System.Diagnostics.Debug.WriteLine("Version check failed."); } break; case ScreenType.TournamentSelection: if (Context != null) Context.Dispose(); if (matchesChangedHandler != null) Context.Tournament.PropertyChanged -= matchesChangedHandler; //Create tournament context from selected tournament Context = new TournamentContext(Portal, SelectedTournament.Id); Context.StartSynchronization(TimeSpan.FromMilliseconds(500), 6); //Create TO View Model OrgViewModel = new OrganizerViewModel(this, dispatcher); //Load up matches into display matches. This is done to allow ordering of assigned matches over unassigned matches without having to refresh the view DisplayMatches = Context.Tournament.Matches.Select(kvp => new DisplayMatch(OrgViewModel, kvp.Value, DisplayMatch.DisplayType.Assigned)) .Concat(Context.Tournament.Matches.Select(kvp => new DisplayMatch(OrgViewModel, kvp.Value, DisplayMatch.DisplayType.Unassigned))).ToList(); //This handler is used to keep matches display matches in sync with tournament context matches. If the matches in the context change, re-generate the display matches matchesChangedHandler = new PropertyChangedEventHandler((sender, e) => { if (e.PropertyName == "Matches") { if (Context.Tournament.Matches == null) DisplayMatches = null; else { DisplayMatches = Context.Tournament.Matches.Select(kvp => new DisplayMatch(OrgViewModel, kvp.Value, DisplayMatch.DisplayType.Assigned)) .Concat(Context.Tournament.Matches.Select(kvp => new DisplayMatch(OrgViewModel, kvp.Value, DisplayMatch.DisplayType.Unassigned))).ToList(); } } }); Context.Tournament.PropertyChanged += matchesChangedHandler; break; } CurrentScreen = (ScreenType)((int)CurrentScreen + 1); }, startAction, endAction, errorHandler); Back = Command.CreateAsync(() => true, () => { switch (CurrentScreen) { case ScreenType.TournamentSelection: ApiKey = null; break; case ScreenType.PendingMatchView: if (OrgViewModel != null) { OrgViewModel.Dispose(); OrgViewModel = null; } break; } CurrentScreen = (ScreenType)((int)CurrentScreen - 1); }, startAction, endAction, errorHandler); IgnoreVersionNotification = Command.Create(() => true, () => IsVersionOutdatedVisible = false); }
public ObservableMatch(Match match, TournamentContext context) { source = match; OwningContext = context; //Round doesn't change, initialize RoundFixed var totalPlayerCount = context.Tournament.ParticipantsCount; var lowerBoundExponent = Math.Floor(Math.Log(totalPlayerCount, 2)); var lowerBound = Math.Pow(2, lowerBoundExponent); if (match.Round < 0 && totalPlayerCount > lowerBound && totalPlayerCount <= lowerBound + (lowerBound / 2)) { Round = match.Round - 1; } else { Round = match.Round; } //Check if station assignment data checks out. If not, clear the assignment var player1Station = Player1 != null ? Player1.StationAssignment : default(string); var player2Station = Player2 != null ? Player2.StationAssignment : default(string); //If stations don't match, clear. Don't check completed matches because those will frequently have mismatching stations if (State != "complete" && player1Station != player2Station) { ClearStationAssignment(); } //Listen for when properties changed to that changed events for the convenience properties can also be fired. this.PropertyChanged += (sender, e) => { switch (e.PropertyName) { case "Player1Id": this.Raise("Player1", PropertyChanged); this.Raise("PlayerCount", PropertyChanged); if (Player1 != null) { Player1.IsMissing = false; //When a player gets added to a match, clear their missing flag } break; case "Player2Id": this.Raise("Player2", PropertyChanged); this.Raise("PlayerCount", PropertyChanged); if (Player2 != null) { Player2.IsMissing = false; //When a player gets added to a match, clear their missing flag } break; case "Player1PrereqMatchId": this.Raise("Player1PreviousMatch", PropertyChanged); break; case "Player2PrereqMatchId": this.Raise("Player2PreviousMatch", PropertyChanged); break; case "StartedAt": this.Raise("TimeSinceAvailable", PropertyChanged); break; case "State": //Clear station assignments if match state changes if (Player1 != null) { Player1.ClearStationAssignment(); } if (Player2 != null) { Player2.ClearStationAssignment(); } //If match state has changed to open, execute selected new match option if (State == "open") { var option = GlobalSettings.Instance.SelectedNewMatchAction; switch (option) { case NewMatchAction.AutoAssign: //TODO: Consider using lock block here to prevent potential multithreaded assignment to the same station var highestPriorityStation = Stations.Instance.GetBestNormalStation(); if (highestPriorityStation != null) { AssignPlayersToStation(highestPriorityStation.Name); } break; case NewMatchAction.Anywhere: AssignPlayersToStation("Any"); break; } } break; } }; var propertyChangedObs = Observable.FromEventPattern <PropertyChangedEventHandler, PropertyChangedEventArgs>(h => this.PropertyChanged += h, h => this.PropertyChanged -= h); //The following will create an observable sequence that will raise an event either when player1 changes or when player1's station assignment status changes var player1ChangedOrAssignmentChanged = propertyChangedObs.Where(a => a.EventArgs.PropertyName == "Player1") .Select(_ => { if (Player1 != null) { return(Observable.FromEventPattern <PropertyChangedEventHandler, PropertyChangedEventArgs>(h => { player1Queue.Enqueue(Player1); Player1.PropertyChanged += h; }, h => { player1Queue.Dequeue().PropertyChanged -= h; }) .Where(a => a.EventArgs.PropertyName == "IsAssignedToStation" || a.EventArgs.PropertyName == "StationAssignment") .Select(_2 => EventArgs.Empty).StartWith(EventArgs.Empty)); } else { return(Observable.Return(EventArgs.Empty)); } }).Switch(); //Subscribe to above observable sequence to maintain the assignment state of the match player1ChangedOrAssignmentChanged.Subscribe(_ => { IsMatchInProgress = Player1 != null && Player1.IsAssignedToStation; StationAssignment = Player1 == null ? null : Player1.StationAssignment; }); //Forcibly raise player1 property notification to assign station status this.Raise("Player1", PropertyChanged); }