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

37 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-01 14:47 +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 typed_stream import Stream 

22 

23from ..utils.request_handler import APIRequestHandler, HTMLRequestHandler 

24from ..utils.utils import ModuleInfo, bounded_edit_distance 

25 

26 

27def get_module_info() -> ModuleInfo: 

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

29 return ModuleInfo( 

30 handlers=( 

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

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

33 ), 

34 name="Wortspiel-Helfer", 

35 description=( 

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

37 ), 

38 path="/wortspiel-helfer", 

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

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

41 hidden=True, 

42 ) 

43 

44 

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

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

47 word_len = len(word) 

48 ignore = {*ignore, word} 

49 

50 return ( 

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

52 .flat_map( 

53 lambda length: read_words_with_length( 

54 Language.DeBasicUmlauts, length 

55 ) 

56 ) 

57 .exclude(ignore.__contains__) 

58 .filter( 

59 lambda test_word: bounded_edit_distance(word, test_word, 2) == 1 

60 ) 

61 ) 

62 

63 

64def get_ranked_solutions( 

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

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

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

68 if not word: 

69 return [] 

70 before_with_word = {*before, word} 

71 return sorted( 

72 ( 

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

74 for sol in find_solutions(word, before) 

75 ), 

76 reverse=True, 

77 ) 

78 

79 

80class WordgameSolver(HTMLRequestHandler): 

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

82 

83 RATELIMIT_GET_LIMIT = 10 

84 

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

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

87 if head: 

88 return 

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

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

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

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

93 

94 await self.render( 

95 "pages/wordgame_solver.html", 

96 word=word, 

97 words=get_ranked_solutions(word, before), 

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

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

100 ) 

101 

102 

103class WordgameSolverAPI(APIRequestHandler): 

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

105 

106 RATELIMIT_GET_LIMIT = 10 

107 ALLOWED_METHODS = ("GET",) 

108 

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

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

111 if head: 

112 return 

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

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

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

116 return await self.finish_dict( 

117 before=before, 

118 word=word, 

119 solutions=get_ranked_solutions(word, before), 

120 )