@@ -1,5 +1,6 @@ |
| 1 | 1 | # apps/trees/views.py |
| 2 | 2 | from django.http import HttpResponse |
| 3 | +from django.db import models |
| 3 | 4 | from rest_framework import viewsets, status |
| 4 | 5 | from rest_framework.decorators import action |
| 5 | 6 | from rest_framework.response import Response |
@@ -99,6 +100,11 @@ class FileSystemTreeViewSet(viewsets.ModelViewSet): |
| 99 | 100 | "command": "score", |
| 100 | 101 | "description": "Show current score and moles killed", |
| 101 | 102 | "examples": ["score"] |
| 103 | + }, |
| 104 | + { |
| 105 | + "command": "exit", |
| 106 | + "description": "End the game and save your score", |
| 107 | + "examples": ["exit"] |
| 102 | 108 | } |
| 103 | 109 | ], |
| 104 | 110 | "special_paths": [ |
@@ -239,7 +245,7 @@ class FileSystemTreeViewSet(viewsets.ModelViewSet): |
| 239 | 245 | response_data.update({ |
| 240 | 246 | 'mole_escaped': True, |
| 241 | 247 | 'escape_data': escape_data, |
| 242 | | - 'mole_direction': mole_direction, # Add this line |
| 248 | + 'mole_direction': mole_direction, |
| 243 | 249 | 'message': f"The mole escaped from {escape_data['old_location']}! A new mole appeared!" |
| 244 | 250 | }) |
| 245 | 251 | |
@@ -252,12 +258,6 @@ class FileSystemTreeViewSet(viewsets.ModelViewSet): |
| 252 | 258 | session.save() |
| 253 | 259 | except GameSession.DoesNotExist: |
| 254 | 260 | pass |
| 255 | | - |
| 256 | | - response_data.update({ |
| 257 | | - 'mole_escaped': True, |
| 258 | | - 'escape_data': escape_data, |
| 259 | | - 'message': f"The mole escaped from {escape_data['old_location']}! A new mole appeared!" |
| 260 | | - }) |
| 261 | 261 | |
| 262 | 262 | return Response(response_data) |
| 263 | 263 | |
@@ -632,6 +632,34 @@ class FileSystemTreeViewSet(viewsets.ModelViewSet): |
| 632 | 632 | response_data['output'] = "No active session to score." |
| 633 | 633 | response_data['success'] = True |
| 634 | 634 | |
| 635 | + elif cmd == 'exit': |
| 636 | + # Complete the game |
| 637 | + tree.complete_game() |
| 638 | + |
| 639 | + # Complete the session if it exists |
| 640 | + if session: |
| 641 | + session.completed_at = timezone.now() |
| 642 | + session.time_taken = session.completed_at - session.started_at |
| 643 | + session.save() |
| 644 | + |
| 645 | + response_data['output'] = f"Game Over! Final score: {session.calculate_score()} | Moles killed: {session.moles_killed}" |
| 646 | + response_data['score'] = session.calculate_score() |
| 647 | + response_data['game_completed'] = True |
| 648 | + response_data['session_completed'] = True |
| 649 | + response_data['final_stats'] = { |
| 650 | + 'score': session.calculate_score(), |
| 651 | + 'moles_killed': session.moles_killed, |
| 652 | + 'moles_escaped': session.moles_escaped, |
| 653 | + 'commands_used': session.commands_used, |
| 654 | + 'time_taken': str(session.time_taken), |
| 655 | + 'directories_visited': session.directories_visited |
| 656 | + } |
| 657 | + else: |
| 658 | + response_data['output'] = "Game ended. No session to record." |
| 659 | + response_data['game_completed'] = True |
| 660 | + |
| 661 | + response_data['success'] = True |
| 662 | + |
| 635 | 663 | elif cmd == 'help': |
| 636 | 664 | response_data['output'] = """Available commands: |
| 637 | 665 | cd <directory> - Change directory (supports ~, -, and ..) |
@@ -645,6 +673,7 @@ echo <text> - Display text (supports $HOME, $PWD, $OLDPWD) |
| 645 | 673 | tree [-L depth] - Display directory tree (use -L to limit depth) |
| 646 | 674 | killall moles - Eliminate moles (when in the same directory) |
| 647 | 675 | score - Show current score and moles killed |
| 676 | +exit - End the game and save your score |
| 648 | 677 | help - Show this help message |
| 649 | 678 | |
| 650 | 679 | Special paths: |
@@ -715,12 +744,75 @@ class GameSessionViewSet(viewsets.ModelViewSet): |
| 715 | 744 | queryset = GameSession.objects.all() |
| 716 | 745 | serializer_class = GameSessionSerializer |
| 717 | 746 | |
| 747 | + @action(detail=False, methods=['post']) |
| 748 | + def save_player_name(self, request): |
| 749 | + """Save player name for a completed session""" |
| 750 | + session_id = request.data.get('session_id') |
| 751 | + player_name = request.data.get('player_name', 'Anonymous') |
| 752 | + |
| 753 | + if not session_id: |
| 754 | + return Response( |
| 755 | + {'error': 'No session ID provided'}, |
| 756 | + status=status.HTTP_400_BAD_REQUEST |
| 757 | + ) |
| 758 | + |
| 759 | + try: |
| 760 | + session = GameSession.objects.get(id=session_id) |
| 761 | + session.player_name = player_name |
| 762 | + session.save() |
| 763 | + |
| 764 | + # Get leaderboard position |
| 765 | + better_scores = GameSession.objects.filter( |
| 766 | + completed_at__isnull=False |
| 767 | + ).exclude(id=session_id).annotate( |
| 768 | + score=models.F('moles_killed') * 1000 # Simplified score calculation |
| 769 | + ).filter(score__gt=session.calculate_score()).count() |
| 770 | + |
| 771 | + return Response({ |
| 772 | + 'success': True, |
| 773 | + 'player_name': player_name, |
| 774 | + 'score': session.calculate_score(), |
| 775 | + 'leaderboard_position': better_scores + 1 |
| 776 | + }) |
| 777 | + except GameSession.DoesNotExist: |
| 778 | + return Response( |
| 779 | + {'error': 'Session not found'}, |
| 780 | + status=status.HTTP_404_NOT_FOUND |
| 781 | + ) |
| 782 | + |
| 718 | 783 | @action(detail=False, methods=['get']) |
| 719 | 784 | def leaderboard(self, request): |
| 720 | | - """Get the leaderboard of fastest completions""" |
| 785 | + """Get the leaderboard of top scores""" |
| 786 | + # Get top 10 completed sessions |
| 721 | 787 | completed_sessions = GameSession.objects.filter( |
| 722 | 788 | completed_at__isnull=False |
| 723 | | - ).order_by('time_taken', 'commands_used')[:20] |
| 789 | + )[:20] # Get more than 10 to handle sorting by score |
| 790 | + |
| 791 | + leaderboard_data = [] |
| 792 | + for session in completed_sessions: |
| 793 | + score = session.calculate_score() |
| 794 | + leaderboard_data.append({ |
| 795 | + 'rank': 0, # Will be set after sorting |
| 796 | + 'player_name': session.player_name, |
| 797 | + 'score': score, |
| 798 | + 'moles_killed': session.moles_killed, |
| 799 | + 'moles_escaped': session.moles_escaped, |
| 800 | + 'commands_used': session.commands_used, |
| 801 | + 'time_taken': str(session.time_taken) if session.time_taken else 'N/A', |
| 802 | + 'completed_at': session.completed_at |
| 803 | + }) |
| 804 | + |
| 805 | + # Sort by score (highest first) |
| 806 | + leaderboard_data.sort(key=lambda x: x['score'], reverse=True) |
| 807 | + |
| 808 | + # Keep only top 10 |
| 809 | + leaderboard_data = leaderboard_data[:10] |
| 724 | 810 | |
| 725 | | - serializer = self.get_serializer(completed_sessions, many=True) |
| 726 | | - return Response(serializer.data) |
| 811 | + # Update ranks after sorting |
| 812 | + for i, entry in enumerate(leaderboard_data, 1): |
| 813 | + entry['rank'] = i |
| 814 | + |
| 815 | + return Response({ |
| 816 | + 'leaderboard': leaderboard_data, |
| 817 | + 'total_games': GameSession.objects.filter(completed_at__isnull=False).count() |
| 818 | + }) |