diff --git a/MultiServer.py b/MultiServer.py index 1a672afa..8ccd50e7 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -1802,14 +1802,32 @@ class ServerCommandProcessor(CommonCommandProcessor): self.output(response) return False + def resolve_player(self, input_name: str) -> typing.Optional[typing.Tuple[int, int, str]]: + """ returns (team, slot, player name) """ + # first match case + for (team, slot), name in self.ctx.player_names.items(): + if name == input_name: + return team, slot, name + + # if no case-sensitive match, then match without case only if there's only 1 match + input_lower = input_name.lower() + match: typing.Optional[typing.Tuple[int, int, str]] = None + for (team, slot), name in self.ctx.player_names.items(): + lowered = name.lower() + if lowered == input_lower: + if match: + return None # ambiguous input_name + match = (team, slot, name) + return match + @mark_raw def _cmd_collect(self, player_name: str) -> bool: """Send out the remaining items to player.""" - seeked_player = player_name.lower() - for (team, slot), name in self.ctx.player_names.items(): - if name.lower() == seeked_player: - collect_player(self.ctx, team, slot) - return True + player = self.resolve_player(player_name) + if player: + team, slot, _ = player + collect_player(self.ctx, team, slot) + return True self.output(f"Could not find player {player_name} to collect") return False @@ -1822,11 +1840,11 @@ class ServerCommandProcessor(CommonCommandProcessor): @mark_raw def _cmd_forfeit(self, player_name: str) -> bool: """Send out the remaining items from a player to their intended recipients.""" - seeked_player = player_name.lower() - for (team, slot), name in self.ctx.player_names.items(): - if name.lower() == seeked_player: - forfeit_player(self.ctx, team, slot) - return True + player = self.resolve_player(player_name) + if player: + team, slot, _ = player + forfeit_player(self.ctx, team, slot) + return True self.output(f"Could not find player {player_name} to release") return False @@ -1834,12 +1852,12 @@ class ServerCommandProcessor(CommonCommandProcessor): @mark_raw def _cmd_allow_forfeit(self, player_name: str) -> bool: """Allow the specified player to use the !release command.""" - seeked_player = player_name.lower() - for (team, slot), name in self.ctx.player_names.items(): - if name.lower() == seeked_player: - self.ctx.allow_forfeits[(team, slot)] = True - self.output(f"Player {player_name} is now allowed to use the !release command at any time.") - return True + player = self.resolve_player(player_name) + if player: + team, slot, name = player + self.ctx.allow_forfeits[(team, slot)] = True + self.output(f"Player {name} is now allowed to use the !release command at any time.") + return True self.output(f"Could not find player {player_name} to allow the !release command for.") return False @@ -1847,13 +1865,12 @@ class ServerCommandProcessor(CommonCommandProcessor): @mark_raw def _cmd_forbid_forfeit(self, player_name: str) -> bool: """"Disallow the specified player from using the !release command.""" - seeked_player = player_name.lower() - for (team, slot), name in self.ctx.player_names.items(): - if name.lower() == seeked_player: - self.ctx.allow_forfeits[(team, slot)] = False - self.output( - f"Player {player_name} has to follow the server restrictions on use of the !release command.") - return True + player = self.resolve_player(player_name) + if player: + team, slot, name = player + self.ctx.allow_forfeits[(team, slot)] = False + self.output(f"Player {name} has to follow the server restrictions on use of the !release command.") + return True self.output(f"Could not find player {player_name} to forbid the !release command for.") return False diff --git a/test/programs/TestMultiServer.py b/test/programs/TestMultiServer.py new file mode 100644 index 00000000..6ea66e33 --- /dev/null +++ b/test/programs/TestMultiServer.py @@ -0,0 +1,40 @@ +import unittest +from MultiServer import Context, ServerCommandProcessor + + +class TestResolvePlayerName(unittest.TestCase): + def test_resolve(self) -> None: + p = ServerCommandProcessor(Context("", 0, "", "", 0, 0, False)) + p.ctx.player_names = { + (1, 1): "AAA", + (1, 2): "aBc", + (1, 3): "abC", + } + assert not p.resolve_player("abc"), "ambiguous name entry shouldn't resolve to player" + assert not p.resolve_player("Abc"), "ambiguous name entry shouldn't resolve to player" + assert p.resolve_player("aBc") == (1, 2, "aBc"), "matching case resolve" + assert p.resolve_player("abC") == (1, 3, "abC"), "matching case resolve" + assert not p.resolve_player("aB"), "partial name shouldn't resolve to player" + assert not p.resolve_player("abCD"), "incorrect name shouldn't resolve to player" + + p.ctx.player_names = { + (1, 1): "aaa", + (1, 2): "abc", + (1, 3): "abC", + } + assert p.resolve_player("abc") == (1, 2, "abc"), "matching case resolve" + assert not p.resolve_player("Abc"), "ambiguous name entry shouldn't resolve to player" + assert not p.resolve_player("aBc"), "ambiguous name entry shouldn't resolve to player" + assert p.resolve_player("abC") == (1, 3, "abC"), "matching case resolve" + + p.ctx.player_names = { + (1, 1): "AbcdE", + (1, 2): "abc", + (1, 3): "abCD", + } + assert p.resolve_player("abc") == (1, 2, "abc"), "matching case resolve" + assert p.resolve_player("abC") == (1, 2, "abc"), "case insensitive resolves when 1 match" + assert p.resolve_player("Abc") == (1, 2, "abc"), "case insensitive resolves when 1 match" + assert p.resolve_player("ABC") == (1, 2, "abc"), "case insensitive resolves when 1 match" + assert p.resolve_player("abcd") == (1, 3, "abCD"), "case insensitive resolves when 1 match" + assert not p.resolve_player("aB"), "partial name shouldn't resolve to player"