random starting position
Authored by
mfwolffe <wolffemf@dukes.jmu.edu>
- SHA
9bb5d9528f220d2bf02c391492f531d335280991- Parents
-
4f93540 - Tree
cd00891
9bb5d95
9bb5d9528f220d2bf02c391492f531d3352809914f93540
cd00891| Status | File | + | - |
|---|---|---|---|
| M |
backend/apps/trees/models.py
|
41 | 0 |
| M |
frontend/src/components/Game.tsx
|
27 | 8 |
backend/apps/trees/models.pymodified@@ -52,6 +52,9 @@ class FileSystemTree(models.Model): | ||
| 52 | 52 | # Place the mole in a random directory (not in standard FHS locations) |
| 53 | 53 | self._place_mole() |
| 54 | 54 | |
| 55 | + # Set random starting position for player | |
| 56 | + self._set_random_start_position() | |
| 57 | + | |
| 55 | 58 | # Cache the tree structure |
| 56 | 59 | self.cache_tree() |
| 57 | 60 | self.save() |
@@ -194,6 +197,44 @@ class FileSystemTree(models.Model): | ||
| 194 | 197 | mole_dir = random.choice(candidates) |
| 195 | 198 | self.mole_location = mole_dir.path |
| 196 | 199 | |
| 200 | + def _set_random_start_position(self): | |
| 201 | + """Set a random starting position for the player""" | |
| 202 | + # Get all directories that could be valid starting positions | |
| 203 | + # Exclude root and very deep directories (depth > 3) | |
| 204 | + candidates = DirectoryNode.objects.filter( | |
| 205 | + tree=self | |
| 206 | + ).exclude( | |
| 207 | + path="/" # Don't start at root | |
| 208 | + ) | |
| 209 | + | |
| 210 | + # Filter to reasonable starting positions | |
| 211 | + valid_starts = [] | |
| 212 | + for node in candidates: | |
| 213 | + depth = node.path.count('/') | |
| 214 | + # Prefer directories at depth 1-3 | |
| 215 | + if 1 <= depth <= 3: | |
| 216 | + # Don't start at the mole location | |
| 217 | + if node.path != self.mole_location: | |
| 218 | + valid_starts.append(node) | |
| 219 | + | |
| 220 | + if valid_starts: | |
| 221 | + # Weight selection towards common starting areas but allow anywhere | |
| 222 | + weights = [] | |
| 223 | + for node in valid_starts: | |
| 224 | + if node.path.startswith('/home'): | |
| 225 | + weights.append(3) # Higher weight for home directories | |
| 226 | + elif node.path.startswith('/usr'): | |
| 227 | + weights.append(2) # Medium weight for usr directories | |
| 228 | + else: | |
| 229 | + weights.append(1) # Lower weight for other directories | |
| 230 | + | |
| 231 | + # Select random starting position with weights | |
| 232 | + start_node = random.choices(valid_starts, weights=weights, k=1)[0] | |
| 233 | + self.player_location = start_node.path | |
| 234 | + else: | |
| 235 | + # Fallback to /home if no valid candidates | |
| 236 | + self.player_location = "/home" | |
| 237 | + | |
| 197 | 238 | def cache_tree(self): |
| 198 | 239 | """Cache the tree structure for efficient retrieval""" |
| 199 | 240 | def build_tree_dict(node): |
frontend/src/components/Game.tsxmodified@@ -60,9 +60,26 @@ const Game: React.FC = () => { | ||
| 60 | 60 | loading: false, |
| 61 | 61 | error: null, |
| 62 | 62 | }); |
| 63 | + | |
| 64 | + // Create a more dynamic starting message based on random location | |
| 65 | + const startLocation = response.tree.player_location; | |
| 66 | + let locationContext = ''; | |
| 67 | + | |
| 68 | + if (startLocation.startsWith('/home')) { | |
| 69 | + locationContext = "You've been dropped in someone's home directory. "; | |
| 70 | + } else if (startLocation.startsWith('/usr')) { | |
| 71 | + locationContext = "You're in the system's usr hierarchy. "; | |
| 72 | + } else if (startLocation.startsWith('/var')) { | |
| 73 | + locationContext = "You're somewhere in the variable data area. "; | |
| 74 | + } else if (startLocation.startsWith('/opt')) { | |
| 75 | + locationContext = "You're in the optional packages directory. "; | |
| 76 | + } else { | |
| 77 | + locationContext = "You've been placed somewhere in the filesystem. "; | |
| 78 | + } | |
| 79 | + | |
| 63 | 80 | setCommandHistory([{ |
| 64 | 81 | command: 'Hunt started!', |
| 65 | - output: response.mole_hint + '\nType "help" for available commands.', | |
| 82 | + output: `${response.mole_hint}\n${locationContext}Use 'pwd' to see where you are.\nType "help" for available commands.`, | |
| 66 | 83 | success: true, |
| 67 | 84 | }]); |
| 68 | 85 | setHints([]); |
@@ -162,11 +179,7 @@ const Game: React.FC = () => { | ||
| 162 | 179 | return treeData; |
| 163 | 180 | }; |
| 164 | 181 | |
| 165 | - // Handle form submission | |
| 166 | - const handleSubmit = (e: React.FormEvent) => { | |
| 167 | - e.preventDefault(); | |
| 168 | - executeCommand(command); | |
| 169 | - }; | |
| 182 | + | |
| 170 | 183 | |
| 171 | 184 | // Handle node click in visualizer |
| 172 | 185 | const handleNodeClick = (path: string) => { |
@@ -301,13 +314,19 @@ const Game: React.FC = () => { | ||
| 301 | 314 | <span className="text-gray-400 mx-1">::</span> |
| 302 | 315 | <span className="text-blue-400">{gameState.tree?.player_location || '~'}</span> |
| 303 | 316 | <span className="text-gray-400 ml-1">$</span> |
| 304 | - <form onSubmit={handleSubmit} className="flex-1 ml-2"> | |
| 317 | + <div className="flex-1 ml-2"> | |
| 305 | 318 | <div className="relative"> |
| 306 | 319 | <input |
| 307 | 320 | ref={inputRef} |
| 308 | 321 | type="text" |
| 309 | 322 | value={command} |
| 310 | 323 | onChange={(e) => setCommand(e.target.value)} |
| 324 | + onKeyDown={(e) => { | |
| 325 | + if (e.key === 'Enter') { | |
| 326 | + e.preventDefault(); | |
| 327 | + executeCommand(command); | |
| 328 | + } | |
| 329 | + }} | |
| 311 | 330 | disabled={executing || gameState.tree?.is_completed} |
| 312 | 331 | className="w-full bg-transparent text-gray-300 outline-none caret-transparent" |
| 313 | 332 | placeholder="" |
@@ -328,7 +347,7 @@ const Game: React.FC = () => { | ||
| 328 | 347 | _ |
| 329 | 348 | </span> |
| 330 | 349 | </div> |
| 331 | - </form> | |
| 350 | + </div> | |
| 332 | 351 | </div> |
| 333 | 352 | </div> |
| 334 | 353 | )} |