feat: add admin award winner command #23
Reference in New Issue
Block a user
Delete Branch "feature/admin-award-winner"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Testing
Resolves #22
Pull request overview
Adds an admin-only
GAME:AWARDcommand path to forcibly award a winner for a given match, updating match state, notifying participants/viewers, and advancing tournaments when applicable.Changes:
Server::handle_game_awardto resolve a match by admin command and propagate win/loss notifications.GAME:AWARD <match_id> <winner_username>command parsing to call the new server handler.Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
src/server.rssrc/main.rsGAME:AWARDto invoke the server handler.💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
The match is removed from
self.matchesbefore playercurrent_matchfields are cleared. Sincehandle_playdoesmatches_guard.get(...).unwrap(), a player sending a move during this window can cause a server panic. To avoid this race, clear both players'current_match(and abort threads) while the match is still present, or makehandle_playtolerate a missing match instead of unwrapping.In the non-tournament path, this only sends
TOURNAMENT:ENDwhen!demo_mode. However, normal match completion inhandle_playsendsTOURNAMENT:ENDeven for demo games (to the human player). Awarding a demo match currently won’t notify the client that the match/tournament flow ended, which can leave the client state inconsistent. Align this behavior with the existing match-end logic (sendTOURNAMENT:ENDto the human player even in demo mode, and only skip the server-opponent send).matches_guard.remove(&match_id)happens before validating thatwinner_usernamematches one of the players (and before any later fallible lookups). If validation fails (or a client lookup fails), this will abort threads and drop the match even though the command returns an error. Consider deferring match removal/state teardown until after winner/participants are validated (or use a two-phase approach where you only remove once you know the award will succeed).Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
@@ -818,6 +819,127 @@ impl Server {Ok(())Winner validation is comparing
winner_username(a username from the command) tothe_match.player1/2.to_string()(socket addresses). This will reject valid usernames and/or accept address strings. Resolve winner identity via theusernamesmap or by reading theClient.usernameforplayer1/player2, then verify the winner belongs to this match.@@ -821,0 +860,4 @@wait_thread.abort();}if let Some(timeout_thread) = &the_match.timeout_thread {Potential deadlock: this function holds a read-lock on the match (
found_match.read().await) and then acquires a write-lock onself.matchesto remove the match. Elsewhere (e.g.,handle_play) the lock order ismatches.write()->match.write(), which can deadlock withmatch.read()->matches.write(). Reorder to avoid holding the match lock while takingself.matches.write(), e.g. remove/take the match entry from the map first (undermatches.write()), drop the map lock, then operate on the match.@@ -821,0 +895,4 @@player1.color = Color::None;player2.current_match = None;player2.color = Color::None;This uses multiple
unwrap()s when fetching players fromclients_guard(and similarly in the demo-mode branch). If a player disconnects before an admin awards the match, the client entry may already be removed (seehandle_disconnect), causing a panic and leaving tournament/match cleanup incomplete. Prefer returning anERROR:INVALID:AWARD(or terminating the match) when a client is missing, while still ensuring the match is removed and the tournament is advanced based on the storedSocketAddrs.@@ -821,1 +940,4 @@Ok(())}pub async fn handle_tournament_start(For non-tournament matches,
handle_playsendsTOURNAMENT:ENDto both players when the match finishes, buthandle_game_award_winneronly does this in the demo-mode branch. Without this, clients may not transition back to the post-match state after an admin-awarded result. Consider mirroringhandle_playbehavior by sendingTOURNAMENT:ENDto both players whenself.tournamentisNone.