From 1e72a29591ce030a2f1cd94d42fc615f552f3201 Mon Sep 17 00:00:00 2001 From: Clemens Borys Date: Wed, 23 Sep 2020 21:41:15 +0200 Subject: [PATCH] Add outcome override for ladder 1v1 games (#669) --- server/config.py | 1 + server/games/game.py | 9 ++- server/games/ladder_game.py | 19 +++++- tests/unit_tests/test_game_rating.py | 90 +++++++++++++++++++++++++++- 4 files changed, 116 insertions(+), 3 deletions(-) diff --git a/server/config.py b/server/config.py index 26b2250a1..767497d9b 100644 --- a/server/config.py +++ b/server/config.py @@ -83,6 +83,7 @@ def __init__(self): self.GEO_IP_LICENSE_KEY = "" self.GEO_IP_DATABASE_MAX_AGE_DAYS = 22 + self.LADDER_1V1_OUTCOME_OVERRIDE = True self.LADDER_ANTI_REPETITION_LIMIT = 3 self.LADDER_SEARCH_EXPANSION_MAX = 0.25 self.LADDER_SEARCH_EXPANSION_STEP = 0.05 diff --git a/server/games/game.py b/server/games/game.py index 6d7990b0e..d2d88644a 100644 --- a/server/games/game.py +++ b/server/games/game.py @@ -423,7 +423,11 @@ async def resolve_game_results(self) -> EndedGameInfo: {self.get_player_outcome(player) for player in team} for team in basic_info.teams ] - team_outcomes = resolve_game(team_player_partial_outcomes) + #TODO Remove override once game result messages are reliable + team_outcomes = ( + self._outcome_override_hook() + or resolve_game(team_player_partial_outcomes) + ) except GameResolutionError: await self.mark_invalid(ValidityState.UNKNOWN_RESULT) @@ -439,6 +443,9 @@ async def resolve_game_results(self) -> EndedGameInfo: basic_info, self.validity, team_outcomes, commander_kills ) + def _outcome_override_hook(self) -> Optional[List[GameOutcome]]: + return None + async def load_results(self): """ Load results from the database diff --git a/server/games/ladder_game.py b/server/games/ladder_game.py index 5d8bdd8ba..0843ce1cf 100644 --- a/server/games/ladder_game.py +++ b/server/games/ladder_game.py @@ -1,6 +1,8 @@ import logging +from typing import List, Optional from server.abc.base_game import InitMode +from server.config import config from server.players import Player from server.rating import RatingType @@ -20,7 +22,7 @@ def __init__(self, id_, *args, **kwargs): new_kwargs = { "game_mode": FeaturedModType.LADDER_1V1, "rating_type": RatingType.LADDER_1V1, - "max_players": 2 + "max_players": 2, } new_kwargs.update(kwargs) super().__init__(id_, *args, **new_kwargs) @@ -34,3 +36,18 @@ def get_army_score(self, army: int) -> int: as 1 for win and 0 for anything else. """ return self._results.victory_only_score(army) + + def _outcome_override_hook(self) -> Optional[List[GameOutcome]]: + if not config.LADDER_1V1_OUTCOME_OVERRIDE or len(self.players) > 2: + return None + team_sets = self.get_team_sets() + army_scores = [ + self._results.score(self.get_player_option(team_set.pop().id, "Army")) + for team_set in team_sets + ] + if army_scores[0] > army_scores[1]: + return [GameOutcome.VICTORY, GameOutcome.DEFEAT] + elif army_scores[0] < army_scores[1]: + return [GameOutcome.DEFEAT, GameOutcome.VICTORY] + else: + return [GameOutcome.DRAW, GameOutcome.DRAW] diff --git a/tests/unit_tests/test_game_rating.py b/tests/unit_tests/test_game_rating.py index 1e624af05..807ced12b 100644 --- a/tests/unit_tests/test_game_rating.py +++ b/tests/unit_tests/test_game_rating.py @@ -185,9 +185,11 @@ async def test_on_game_end_global_ratings(custom_game, players): assert results.rating_type is RatingType.GLOBAL assert players.hosting.id in results.ratings assert players.joining.id in results.ratings + assert results.outcomes[players.hosting.id] is GameOutcome.VICTORY + assert results.outcomes[players.joining.id] is GameOutcome.DEFEAT -async def test_on_game_end_ladder_ratings(ladder_game, players): +async def test_on_game_end_ladder_ratings_(ladder_game, players): rating_service = ladder_game.game_service._rating_service ladder_game.state = GameState.LOBBY @@ -206,6 +208,86 @@ async def test_on_game_end_ladder_ratings(ladder_game, players): assert results.rating_type is RatingType.LADDER_1V1 assert players.hosting.id in results.ratings assert players.joining.id in results.ratings + assert results.outcomes[players.hosting.id] is GameOutcome.VICTORY + assert results.outcomes[players.joining.id] is GameOutcome.DEFEAT + + +async def test_on_game_end_ladder_ratings_without_score_override( + ladder_game, players, mocker +): + mocker.patch("server.games.ladder_game.config.LADDER_1V1_OUTCOME_OVERRIDE", False) + rating_service = ladder_game.game_service._rating_service + + ladder_game.state = GameState.LOBBY + add_connected_players(ladder_game, [players.hosting, players.joining]) + ladder_game.set_player_option(players.hosting.id, "Team", 1) + ladder_game.set_player_option(players.joining.id, "Team", 1) + + await ladder_game.launch() + await ladder_game.add_result(players.hosting.id, 0, "victory", 0) + await ladder_game.add_result(players.joining.id, 1, "defeat", 0) + + await ladder_game.on_game_end() + await rating_service._join_rating_queue() + + results = get_persisted_results(rating_service) + assert results.rating_type is RatingType.LADDER_1V1 + assert players.hosting.id in results.ratings + assert players.joining.id in results.ratings + assert results.outcomes[players.hosting.id] is GameOutcome.VICTORY + assert results.outcomes[players.joining.id] is GameOutcome.DEFEAT + + +async def test_on_game_end_ladder_ratings_uses_score_override( + ladder_game, players, mocker +): + mocker.patch("server.games.ladder_game.config.LADDER_1V1_OUTCOME_OVERRIDE", True) + rating_service = ladder_game.game_service._rating_service + + ladder_game.state = GameState.LOBBY + add_connected_players(ladder_game, [players.hosting, players.joining]) + ladder_game.set_player_option(players.hosting.id, "Team", 1) + ladder_game.set_player_option(players.joining.id, "Team", 1) + + await ladder_game.launch() + await ladder_game.add_result(players.hosting.id, 0, "defeat", 1) + await ladder_game.add_result(players.joining.id, 1, "defeat", 0) + + await ladder_game.on_game_end() + await rating_service._join_rating_queue() + + results = get_persisted_results(rating_service) + assert results.rating_type is RatingType.LADDER_1V1 + assert players.hosting.id in results.ratings + assert players.joining.id in results.ratings + assert results.outcomes[players.hosting.id] is GameOutcome.VICTORY + assert results.outcomes[players.joining.id] is GameOutcome.DEFEAT + + +async def test_on_game_end_ladder_ratings_score_override_draw( + ladder_game, players, mocker +): + mocker.patch("server.games.ladder_game.config.LADDER_1V1_OUTCOME_OVERRIDE", True) + rating_service = ladder_game.game_service._rating_service + + ladder_game.state = GameState.LOBBY + add_connected_players(ladder_game, [players.hosting, players.joining]) + ladder_game.set_player_option(players.hosting.id, "Team", 1) + ladder_game.set_player_option(players.joining.id, "Team", 1) + + await ladder_game.launch() + await ladder_game.add_result(players.hosting.id, 0, "defeat", 0) + await ladder_game.add_result(players.joining.id, 1, "defeat", 0) + + await ladder_game.on_game_end() + await rating_service._join_rating_queue() + + results = get_persisted_results(rating_service) + assert results.rating_type is RatingType.LADDER_1V1 + assert players.hosting.id in results.ratings + assert players.joining.id in results.ratings + assert results.outcomes[players.hosting.id] is GameOutcome.DRAW + assert results.outcomes[players.joining.id] is GameOutcome.DRAW async def test_on_game_end_rating_type_not_set(game, players): @@ -306,10 +388,12 @@ async def test_rate_game_sum_of_scores_edge_case(custom_game, player_factory): assert results.ratings[player.id] > Rating( *player.ratings[RatingType.GLOBAL] ) + assert results.outcomes[player.id] is GameOutcome.VICTORY else: assert results.ratings[player.id] < Rating( *player.ratings[RatingType.GLOBAL] ) + assert results.outcomes[player.id] is GameOutcome.DEFEAT async def test_rate_game_only_one_survivor(custom_game, player_factory): @@ -351,10 +435,12 @@ async def test_rate_game_only_one_survivor(custom_game, player_factory): assert results.ratings[player.id] > Rating( *player.ratings[RatingType.GLOBAL] ) + assert results.outcomes[player.id] is GameOutcome.VICTORY else: assert results.ratings[player.id] < Rating( *player.ratings[RatingType.GLOBAL] ) + assert results.outcomes[player.id] is GameOutcome.DEFEAT async def test_rate_game_two_player_FFA(custom_game, player_factory): @@ -592,10 +678,12 @@ async def test_compute_rating_works_with_partially_unknown_results( assert results.ratings[player.id] > Rating( *player.ratings[RatingType.GLOBAL] ) + assert results.outcomes[player.id] is GameOutcome.VICTORY else: assert results.ratings[player.id] < Rating( *player.ratings[RatingType.GLOBAL] ) + assert results.outcomes[player.id] is GameOutcome.DEFEAT async def test_rate_game_single_ffa_vs_single_team(custom_game, player_factory):