Python · 8676 bytes Raw Blame History
1 # apps/trees/views.py
2 from django.http import HttpResponse
3 from rest_framework import viewsets, status
4 from rest_framework.decorators import action
5 from rest_framework.response import Response
6 from django.utils import timezone
7 from datetime import timedelta
8 from .models import FileSystemTree, DirectoryNode, GameSession
9 from .serializers import (
10 FileSystemTreeSerializer, DirectoryNodeSerializer,
11 GameSessionSerializer, GameCommandSerializer
12 )
13
14
15 class FileSystemTreeViewSet(viewsets.ModelViewSet):
16 """ViewSet for filesystem trees"""
17 queryset = FileSystemTree.objects.all()
18 serializer_class = FileSystemTreeSerializer
19
20 @action(detail=False, methods=['post'])
21 def create_game(self, request):
22 """Create a new game with a generated filesystem tree"""
23 tree_name = request.data.get('name', 'FHS Game Tree')
24 max_depth = request.data.get('max_depth', 5)
25 dirs_per_level = request.data.get('dirs_per_level', 3)
26
27 # Create and generate tree
28 tree = FileSystemTree.objects.create(name=tree_name)
29 tree.generate_tree(max_depth=max_depth, directories_per_level=dirs_per_level)
30
31 # Create game session
32 player_name = request.data.get('player_name', 'Anonymous')
33 session = GameSession.objects.create(
34 tree=tree,
35 player_name=player_name
36 )
37
38 serializer = self.get_serializer(tree)
39 return Response({
40 'tree': serializer.data,
41 'session_id': session.id,
42 'mole_hint': f"The mole is hiding somewhere in the filesystem!"
43 }, status=status.HTTP_201_CREATED)
44
45 @action(detail=True, methods=['get'])
46 def current_directory(self, request, pk=None):
47 """Get current directory contents and player location"""
48 tree = self.get_object()
49
50 try:
51 current_dir = DirectoryNode.objects.get(
52 tree=tree,
53 path=tree.player_location
54 )
55 contents = current_dir.get_contents()
56
57 return Response({
58 'path': tree.player_location,
59 'contents': DirectoryNodeSerializer(contents, many=True).data,
60 'parent': current_dir.parent.path if current_dir.parent else None
61 })
62 except DirectoryNode.DoesNotExist:
63 return Response(
64 {'error': 'Current directory not found'},
65 status=status.HTTP_404_NOT_FOUND
66 )
67
68 @action(detail=True, methods=['post'])
69 def execute_command(self, request, pk=None):
70 """Execute a shell command in the game"""
71 tree = self.get_object()
72 command = request.data.get('command', '').strip()
73 session_id = request.data.get('session_id')
74
75 if not command:
76 return Response(
77 {'error': 'No command provided'},
78 status=status.HTTP_400_BAD_REQUEST
79 )
80
81 # Get session if provided
82 session = None
83 if session_id:
84 try:
85 session = GameSession.objects.get(id=session_id, tree=tree)
86 session.add_command(command)
87 except GameSession.DoesNotExist:
88 pass
89
90 # Parse and execute command
91 parts = command.split()
92 cmd = parts[0] if parts else ""
93
94 response_data = {
95 'command': command,
96 'success': False,
97 'output': '',
98 'current_path': tree.player_location
99 }
100
101 if cmd == 'cd':
102 if len(parts) < 2:
103 response_data['output'] = "cd: missing operand"
104 else:
105 target = parts[1]
106 success, message = tree.move_player(target)
107 response_data['success'] = success
108 response_data['output'] = message
109 response_data['current_path'] = tree.player_location
110
111 if success and session:
112 session.directories_visited += 1
113 session.save()
114
115 elif cmd == 'ls':
116 try:
117 current_dir = DirectoryNode.objects.get(
118 tree=tree,
119 path=tree.player_location
120 )
121 contents = current_dir.get_contents()
122 if contents:
123 response_data['output'] = '\n'.join([d.name for d in contents])
124 else:
125 response_data['output'] = ''
126 response_data['success'] = True
127 except DirectoryNode.DoesNotExist:
128 response_data['output'] = "ls: cannot access directory"
129
130 elif cmd == 'pwd':
131 response_data['output'] = tree.player_location
132 response_data['success'] = True
133
134 elif cmd == 'killall' and len(parts) > 1 and parts[1] == 'moles':
135 if tree.check_win_condition():
136 tree.is_completed = True
137 tree.completed_at = timezone.now()
138 tree.save()
139
140 if session:
141 session.completed_at = timezone.now()
142 session.time_taken = session.completed_at - session.started_at
143 session.save()
144
145 response_data['output'] = "🎉 Congratulations! You found and eliminated the mole!"
146 response_data['success'] = True
147 response_data['game_won'] = True
148 else:
149 response_data['output'] = "No moles found in this directory."
150 response_data['success'] = True
151
152 elif cmd == 'help':
153 response_data['output'] = """Available commands:
154 cd <directory> - Change directory
155 ls - List directory contents
156 pwd - Print working directory
157 killall moles - Eliminate moles (when in the same directory)
158 help - Show this help message"""
159 response_data['success'] = True
160
161 else:
162 response_data['output'] = f"bash: {cmd}: command not found"
163
164 return Response(response_data)
165
166 @action(detail=True, methods=['get'])
167 def hint(self, request, pk=None):
168 """Get a hint about the mole's location"""
169 tree = self.get_object()
170
171 if not tree.mole_location:
172 return Response({'hint': 'No mole in this tree!'})
173
174 mole_depth = tree.mole_location.count('/')
175 player_depth = tree.player_location.count('/')
176
177 hints = []
178 if mole_depth > player_depth:
179 hints.append("The mole is deeper in the filesystem than you are.")
180 elif mole_depth < player_depth:
181 hints.append("The mole is in a shallower directory than you are.")
182 else:
183 hints.append("You're at the same depth as the mole!")
184
185 # Give a path hint
186 mole_parts = tree.mole_location.split('/')
187 player_parts = tree.player_location.split('/')
188
189 # Find common path
190 common_parts = []
191 for i, (m, p) in enumerate(zip(mole_parts, player_parts)):
192 if m == p:
193 common_parts.append(m)
194 else:
195 break
196
197 if len(common_parts) == len(mole_parts):
198 hints.append("You're in the mole's directory! Use 'killall moles'!")
199 elif len(common_parts) > 1:
200 hints.append(f"You share a common path with the mole: {'/'.join(common_parts) or '/'}")
201
202 return Response({'hints': hints})
203
204
205 class DirectoryNodeViewSet(viewsets.ReadOnlyModelViewSet):
206 """ViewSet for browsing directory nodes"""
207 queryset = DirectoryNode.objects.all()
208 serializer_class = DirectoryNodeSerializer
209
210 def get_queryset(self):
211 queryset = super().get_queryset()
212 tree_id = self.request.query_params.get('tree', None)
213 if tree_id:
214 queryset = queryset.filter(tree_id=tree_id)
215 return queryset
216
217
218 class GameSessionViewSet(viewsets.ModelViewSet):
219 """ViewSet for game sessions"""
220 queryset = GameSession.objects.all()
221 serializer_class = GameSessionSerializer
222
223 @action(detail=False, methods=['get'])
224 def leaderboard(self, request):
225 """Get the leaderboard of fastest completions"""
226 completed_sessions = GameSession.objects.filter(
227 completed_at__isnull=False
228 ).order_by('time_taken', 'commands_used')[:20]
229
230 serializer = self.get_serializer(completed_sessions, many=True)
231 return Response(serializer.data)