Coverage for an_website/hangman_solver/wordgame_solver.py: 100.000%

38 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-11-16 19:56 +0000

1# This program is free software: you can redistribute it and/or modify 

2# it under the terms of the GNU Affero General Public License as 

3# published by the Free Software Foundation, either version 3 of the 

4# License, or (at your option) any later version. 

5# 

6# This program is distributed in the hope that it will be useful, 

7# but WITHOUT ANY WARRANTY; without even the implied warranty of 

8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

9# GNU Affero General Public License for more details. 

10# 

11# You should have received a copy of the GNU Affero General Public License 

12# along with this program. If not, see <https://www.gnu.org/licenses/>. 

13 

14"""The module for the wordgame solver.""" 

15 

16from __future__ import annotations 

17 

18from collections.abc import Collection 

19 

20from hangman_solver import Language, read_words_with_length 

21from rapidfuzz.distance.Levenshtein import distance 

22from typed_stream import Stream 

23 

24from ..utils.request_handler import APIRequestHandler, HTMLRequestHandler 

25from ..utils.utils import ModuleInfo 

26 

27 

28def get_module_info() -> ModuleInfo: 

29 """Create and return the ModuleInfo for this module.""" 

30 return ModuleInfo( 

31 handlers=( 

32 ("/wortspiel-helfer", WordgameSolver), 

33 ("/api/wortspiel-helfer", WordgameSolverAPI), 

34 ), 

35 name="Wortspiel-Helfer", 

36 description=( 

37 "Findet Worte, die nur eine Änderung voneinander entfernt sind." 

38 ), 

39 path="/wortspiel-helfer", 

40 keywords=("Wortspiel", "Helfer", "Hilfe", "Worte"), 

41 aliases=("/wordgame-solver",), 

42 hidden=True, 

43 ) 

44 

45 

46def find_solutions(word: str, ignore: Collection[str]) -> Stream[str]: 

47 """Find words that have only one different letter.""" 

48 word_len = len(word) 

49 ignore = {*ignore, word} 

50 

51 return ( 

52 Stream((word_len - 1, word_len, word_len + 1)) 

53 .flat_map( 

54 lambda length: read_words_with_length( 

55 Language.DeBasicUmlauts, length 

56 ) 

57 ) 

58 .exclude(ignore.__contains__) 

59 .filter(lambda test_word: distance(word, test_word) == 1) 

60 ) 

61 

62 

63def get_ranked_solutions( 

64 word: str, before: Collection[str] = () 

65) -> list[tuple[int, str]]: 

66 """Find solutions for the word and rank them.""" 

67 if not word: 

68 return [] 

69 before_with_word = {*before, word} 

70 return sorted( 

71 ( 

72 (find_solutions(sol, before_with_word).count(), sol) 

73 for sol in find_solutions(word, before) 

74 ), 

75 reverse=True, 

76 ) 

77 

78 

79class WordgameSolver(HTMLRequestHandler): 

80 """The request handler for the wordgame solver page.""" 

81 

82 RATELIMIT_GET_LIMIT = 10 

83 

84 async def get(self, *, head: bool = False) -> None: 

85 """Handle GET requests to the wordgame solver page.""" 

86 if head: 

87 return 

88 word = self.get_argument("word", "").lower() 

89 before_str = self.get_argument("before", "") 

90 before = [_w.strip() for _w in before_str.split(",") if _w.strip()] 

91 new_before = [*before, word] if word and word not in before else before 

92 

93 await self.render( 

94 "pages/wordgame_solver.html", 

95 word=word, 

96 words=get_ranked_solutions(word, before), 

97 before=", ".join(before), 

98 new_before=", ".join(new_before), 

99 ) 

100 

101 

102class WordgameSolverAPI(APIRequestHandler): 

103 """The request handler for the wordgame solver API.""" 

104 

105 RATELIMIT_GET_LIMIT = 10 

106 ALLOWED_METHODS = ("GET",) 

107 

108 async def get(self, *, head: bool = False) -> None: 

109 """Handle GET requests to the wordgame solver API.""" 

110 if head: 

111 return 

112 word: str = self.get_argument("word", "").lower() 

113 before_str: str = self.get_argument("before", "") 

114 before = [_w.strip() for _w in before_str.split(",") if _w.strip()] 

115 return await self.finish_dict( 

116 before=before, 

117 word=word, 

118 solutions=get_ranked_solutions(word, before), 

119 )