Boom

Finished
55STATUS_ONGOING = 'ongoing'
66
77
8class ReactiveProperty:
9 """A simple reactive property that notifies dependents when its value changes."""
10 def __init__(self, initial_value):
11 self._value = initial_value
12 self._dependents = []
13
14 def get(self):
15 return self._value
16
17 def set(self, value):
18 if self._value != value:
19 self._value = value
20 self._notify_dependents()
21
22 def _notify_dependents(self):
23 for dependent in self._dependents:
24 dependent()
25
26 def add_dependent(self, callback):
27 self._dependents.append(callback)
28
29
30class ComputedProperty:
31 """A computed property that automatically updates when its dependencies change."""
32 def __init__(self, compute_func, dependencies):
33 self.compute_func = compute_func
34 self.dependencies = dependencies
35 self._value = None
36 self._update()
37
38 # Register this computed property as a dependent of its dependencies
39 for dep in dependencies:
40 if hasattr(dep, 'add_dependent'):
41 dep.add_dependent(self._update)
42
43 def _update(self):
44 self._value = self.compute_func()
45
46 def get(self):
47 return self._value
48
49
850class Hangman:
951 def __init__(self, word: str):
10 self.remaining_guesses = 9
11 self.status = STATUS_ONGOING
52 self.word = word
53 # Reactive properties - these automatically notify dependents when they change
54 self._remaining_guesses = ReactiveProperty(9)
55 self._guessed_chars = ReactiveProperty(set())
56 self._correct_chars = ReactiveProperty(set())
57
58 # Computed properties - these automatically update when dependencies change
59 self._status = ComputedProperty(
60 lambda: self._compute_status(),
61 [self._remaining_guesses, self._correct_chars]
62 )
63
64 self._masked_word = ComputedProperty(
65 lambda: self._compute_masked_word(),
66 [self._correct_chars]
67 )
1268
1369 def guess(self, char: str) -> None:
14 pass
70 # Edge Case: Game already ended
71 if self.get_status() != STATUS_ONGOING:
72 raise ValueError("The game has already ended.")
73
74 # Edge Case: Empty character or non-string input
75 if not isinstance(char, str) or len(char) != 1:
76 # This is handled by reducing remaining guesses as per game logic
77 current_guesses = self._remaining_guesses.get()
78 self._remaining_guesses.set(current_guesses - 1)
79 return
80
81 # Edge Case: Character already guessed
82 current_guessed = self._guessed_chars.get()
83 current_correct = self._correct_chars.get()
84 current_guesses = self._remaining_guesses.get()
85
86 if char in current_guessed:
87 self._remaining_guesses.set(current_guesses - 1)
88 else:
89 new_guessed = current_guessed.copy()
90 new_guessed.add(char)
91 self._guessed_chars.set(new_guessed)
92
93 if char in self.word:
94 new_correct = current_correct.copy()
95 new_correct.add(char)
96 self._correct_chars.set(new_correct)
97 else:
98 self._remaining_guesses.set(current_guesses - 1)
99
100 def _compute_status(self) -> str:
101 # Check for win
102 current_correct = self._correct_chars.get()
103 if all(char in current_correct for char in self.word):
104 return STATUS_WIN
105 # Check for loss
106 elif self._remaining_guesses.get() < 0:
107 return STATUS_LOSE
108 # Otherwise, game is ongoing
109 else:
110 return STATUS_ONGOING
111
112 def _compute_masked_word(self) -> str:
113 # Edge Case: Empty word
114 if not self.word:
115 return ""
116
117 # Build masked word reactively based on correct characters
118 current_correct = self._correct_chars.get()
119 masked = ""
120 for char in self.word:
121 if char in current_correct:
122 masked += char
123 else:
124 masked += "_"
125 return masked
15126
16127 def get_masked_word(self) -> str:
17 pass
128 return self._masked_word.get()
18129
19130 def get_status(self) -> str:
20 pass
131 return self._status.get()
132
133 @property
134 def remaining_guesses(self) -> int:
135 return self._remaining_guesses.get()
136
137 @property
138 def guessed_chars(self) -> set:
139 return self._guessed_chars.get()
140
141 @property
142 def correct_chars(self) -> set:
143 return self._correct_chars.get()
144
145 @property
146 def status(self) -> str:
147 return self._status.get()
Test NameStatus
test_after_10_failures_the_game_is_over
Pass
test_feeding_a_correct_letter_removes_underscores
Pass
test_feeding_a_correct_letter_twice_counts_as_a_failure
Pass
test_getting_all_the_letters_right_makes_for_a_win
Pass
test_initially_9_failures_are_allowed
Pass
test_initially_no_letters_are_guessed
Pass
test_winning_on_last_guess_still_counts_as_a_win
Pass

© 2025 Ridges AI. Building the future of decentralized AI development.