zeroed-some/bashamole / ce66300

Browse files

frontend leaderboard

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
ce663003a5532ef56bbe6fad85d05d173ba680c2
Parents
fbe7527
Tree
d1432aa

4 changed files

StatusFile+-
M frontend/src/components/Game.tsx 28 3
A frontend/src/components/GameOverModal.tsx 208 0
M frontend/src/hooks/useCommandExecution.ts 47 63
M frontend/src/lib/api.ts 60 32
frontend/src/components/Game.tsxmodified
@@ -1,11 +1,12 @@
1
 "use client";
1
 "use client";
2
 
2
 
3
-import React, { useEffect, useCallback } from 'react';
3
+import React, { useEffect, useCallback, useState } from 'react';
4
 import TreeVisualizer from './TreeVisualizer';
4
 import TreeVisualizer from './TreeVisualizer';
5
 import Terminal from './Terminal';
5
 import Terminal from './Terminal';
6
 import HelpModals from './HelpModals';
6
 import HelpModals from './HelpModals';
7
 import GameStatus from './GameStatus';
7
 import GameStatus from './GameStatus';
8
-import { gameApi, CommandResponse } from '@/lib/api';
8
+import GameOverModal from './GameOverModal';
9
+import { gameApi, CommandResponse, GameStats } from '@/lib/api';
9
 
10
 
10
 // Import our custom hooks
11
 // Import our custom hooks
11
 import { useGameState } from '@/hooks/useGameState';
12
 import { useGameState } from '@/hooks/useGameState';
@@ -45,6 +46,10 @@ const Game: React.FC = () => {
45
 
46
 
46
   const { updateTreeDataToShowMole, removeMoleFromTree } = useTreeUtils();
47
   const { updateTreeDataToShowMole, removeMoleFromTree } = useTreeUtils();
47
 
48
 
49
+  // Game over modal state
50
+  const [showGameOver, setShowGameOver] = useState(false);
51
+  const [finalStats, setFinalStats] = useState<GameStats | null>(null);
52
+
48
   // Handle mole kill animation and updates
53
   // Handle mole kill animation and updates
49
   const handleMoleKilled = useCallback((response: CommandResponse) => {
54
   const handleMoleKilled = useCallback((response: CommandResponse) => {
50
     if (!gameState.tree) return;
55
     if (!gameState.tree) return;
@@ -92,7 +97,11 @@ const Game: React.FC = () => {
92
     gameState.sessionId,
97
     gameState.sessionId,
93
     updatePlayerLocation,
98
     updatePlayerLocation,
94
     handleMoleKilled,
99
     handleMoleKilled,
95
-    updateTreeData
100
+    updateTreeData,
101
+    (stats) => {  // Add this callback for game completion
102
+      setFinalStats(stats);
103
+      setShowGameOver(true);
104
+    }
96
   );
105
   );
97
 
106
 
98
   // Wrap executeCommand to clear the command input
107
   // Wrap executeCommand to clear the command input
@@ -185,6 +194,13 @@ const Game: React.FC = () => {
185
     executeCommand(`cd ${path}`);
194
     executeCommand(`cd ${path}`);
186
   }, [executeCommand]);
195
   }, [executeCommand]);
187
 
196
 
197
+  // Handler for starting a new game from the modal
198
+  const handleNewGameFromModal = () => {
199
+    setShowGameOver(false);
200
+    setFinalStats(null);
201
+    initializeGame();
202
+  };
203
+
188
   // Start game on mount
204
   // Start game on mount
189
   useEffect(() => {
205
   useEffect(() => {
190
     initializeGame();
206
     initializeGame();
@@ -290,6 +306,15 @@ const Game: React.FC = () => {
290
       {/* Help Modals */}
306
       {/* Help Modals */}
291
       <HelpModals {...helpModals} />
307
       <HelpModals {...helpModals} />
292
 
308
 
309
+      {/* Game Over Modal */}
310
+      <GameOverModal
311
+        isOpen={showGameOver}
312
+        gameStats={finalStats}
313
+        sessionId={gameState.sessionId}
314
+        onClose={() => setShowGameOver(false)}
315
+        onNewGame={handleNewGameFromModal}
316
+      />
317
+
293
       {/* Bottom Game Bar */}
318
       {/* Bottom Game Bar */}
294
       <div className="absolute bottom-0 left-0 right-0 bg-slate-800/90 backdrop-blur-sm border-t border-slate-700 p-3 z-20">
319
       <div className="absolute bottom-0 left-0 right-0 bg-slate-800/90 backdrop-blur-sm border-t border-slate-700 p-3 z-20">
295
         <div className="max-w-7xl mx-auto flex justify-between items-center px-4">
320
         <div className="max-w-7xl mx-auto flex justify-between items-center px-4">
frontend/src/components/GameOverModal.tsxadded
@@ -0,0 +1,208 @@
1
+import React, { useState, useEffect } from 'react';
2
+import { GameStats, LeaderboardEntry, gameApi } from '@/lib/api';
3
+
4
+interface GameOverModalProps {
5
+  isOpen: boolean;
6
+  gameStats: GameStats | null;
7
+  sessionId: number | null;
8
+  onClose: () => void;
9
+  onNewGame: () => void;
10
+}
11
+
12
+const GameOverModal: React.FC<GameOverModalProps> = ({
13
+  isOpen,
14
+  gameStats,
15
+  sessionId,
16
+  onClose,
17
+  onNewGame,
18
+}) => {
19
+  const [playerName, setPlayerName] = useState('');
20
+  const [submitted, setSubmitted] = useState(false);
21
+  const [leaderboard, setLeaderboard] = useState<LeaderboardEntry[]>([]);
22
+  const [leaderboardPosition, setLeaderboardPosition] = useState<number | null>(null);
23
+  const [showLeaderboard, setShowLeaderboard] = useState(false);
24
+  const [loading, setLoading] = useState(false);
25
+
26
+  useEffect(() => {
27
+    if (isOpen && !submitted) {
28
+      // Load leaderboard when modal opens
29
+      loadLeaderboard();
30
+    }
31
+  }, [isOpen, submitted]);
32
+
33
+  const loadLeaderboard = async () => {
34
+    try {
35
+      const response = await gameApi.getLeaderboard();
36
+      setLeaderboard(response.leaderboard);
37
+    } catch (error) {
38
+      console.error('Failed to load leaderboard:', error);
39
+    }
40
+  };
41
+
42
+  const handleSubmit = async (e: React.FormEvent | React.MouseEvent) => {
43
+    e.preventDefault();
44
+    if (!sessionId || submitted) return;
45
+
46
+    setLoading(true);
47
+    try {
48
+      const response = await gameApi.savePlayerName(
49
+        sessionId,
50
+        playerName.trim() || 'Anonymous'
51
+      );
52
+      setLeaderboardPosition(response.leaderboard_position);
53
+      setSubmitted(true);
54
+      await loadLeaderboard();
55
+      setShowLeaderboard(true);
56
+    } catch (error) {
57
+      console.error('Failed to save score:', error);
58
+    } finally {
59
+      setLoading(false);
60
+    }
61
+  };
62
+
63
+  if (!isOpen || !gameStats) return null;
64
+
65
+  return (
66
+    <div className="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center z-50">
67
+      <div className="bg-gray-900 border-2 border-green-500 rounded-lg p-8 max-w-2xl w-full mx-4 shadow-2xl">
68
+        {/* Header */}
69
+        <h2 className="text-3xl font-bold text-green-400 mb-6 text-center font-terminal">
70
+          GAME OVER
71
+        </h2>
72
+
73
+        {/* Score Display */}
74
+        {!showLeaderboard && (
75
+          <>
76
+            <div className="bg-black/50 border border-green-500/50 rounded-lg p-6 mb-6">
77
+              <div className="grid grid-cols-2 gap-4 text-green-300 font-terminal">
78
+                <div>
79
+                  <span className="text-gray-500">Final Score:</span>
80
+                  <div className="text-2xl font-bold text-green-400">{gameStats.score}</div>
81
+                </div>
82
+                <div>
83
+                  <span className="text-gray-500">Moles Killed:</span>
84
+                  <div className="text-2xl font-bold">{gameStats.moles_killed}</div>
85
+                </div>
86
+                <div>
87
+                  <span className="text-gray-500">Moles Escaped:</span>
88
+                  <div className="text-xl text-red-400">{gameStats.moles_escaped}</div>
89
+                </div>
90
+                <div>
91
+                  <span className="text-gray-500">Commands Used:</span>
92
+                  <div className="text-xl">{gameStats.commands_used}</div>
93
+                </div>
94
+                <div className="col-span-2">
95
+                  <span className="text-gray-500">Time Played:</span>
96
+                  <div className="text-xl">{gameStats.time_taken}</div>
97
+                </div>
98
+              </div>
99
+            </div>
100
+
101
+            {/* Name Input */}
102
+            {!submitted ? (
103
+              <div className="mb-6">
104
+                <label className="block text-green-400 mb-2 font-terminal">
105
+                  Enter your name for the leaderboard:
106
+                </label>
107
+                <div className="flex gap-2">
108
+                  <input
109
+                    type="text"
110
+                    value={playerName}
111
+                    onChange={(e) => setPlayerName(e.target.value)}
112
+                    onKeyDown={(e) => {
113
+                      if (e.key === 'Enter') {
114
+                        e.preventDefault();
115
+                        handleSubmit(e);
116
+                      }
117
+                    }}
118
+                    placeholder="Anonymous"
119
+                    maxLength={20}
120
+                    className="flex-1 bg-black border border-green-500 text-green-300 px-4 py-2 rounded font-terminal focus:outline-none focus:border-green-400"
121
+                    autoFocus
122
+                    disabled={loading}
123
+                  />
124
+                  <button
125
+                    onClick={handleSubmit}
126
+                    disabled={loading}
127
+                    className="px-6 py-2 bg-green-600 hover:bg-green-700 text-white rounded font-terminal transition disabled:opacity-50"
128
+                  >
129
+                    {loading ? 'Saving...' : 'Submit'}
130
+                  </button>
131
+                </div>
132
+              </div>
133
+            ) : (
134
+              <div className="text-center mb-6">
135
+                <p className="text-green-400 font-terminal text-lg">
136
+                  Score saved! You ranked #{leaderboardPosition}
137
+                </p>
138
+                <button
139
+                  onClick={() => setShowLeaderboard(true)}
140
+                  className="mt-2 text-green-300 hover:text-green-200 underline font-terminal"
141
+                >
142
+                  View Leaderboard
143
+                </button>
144
+              </div>
145
+            )}
146
+          </>
147
+        )}
148
+
149
+        {/* Leaderboard */}
150
+        {showLeaderboard && (
151
+          <div className="bg-black/50 border border-green-500/50 rounded-lg p-4 mb-6 max-h-96 overflow-y-auto">
152
+            <h3 className="text-xl font-bold text-green-400 mb-4 font-terminal">Top Scores</h3>
153
+            <div className="space-y-2">
154
+              {leaderboard.map((entry) => (
155
+                <div
156
+                  key={`${entry.player_name}-${entry.completed_at}`}
157
+                  className={`flex justify-between items-center p-2 rounded ${
158
+                    entry.score === gameStats.score && submitted
159
+                      ? 'bg-green-900/50 border border-green-500'
160
+                      : 'hover:bg-gray-800/50'
161
+                  }`}
162
+                >
163
+                  <div className="flex items-center gap-4 font-terminal">
164
+                    <span className="text-green-500 font-bold w-8">#{entry.rank}</span>
165
+                    <span className="text-green-300">{entry.player_name}</span>
166
+                  </div>
167
+                  <div className="flex items-center gap-6 text-sm font-terminal">
168
+                    <span className="text-green-400">{entry.score} pts</span>
169
+                    <span className="text-gray-500">{entry.moles_killed} moles</span>
170
+                    <span className="text-gray-600">{entry.commands_used} cmds</span>
171
+                  </div>
172
+                </div>
173
+              ))}
174
+            </div>
175
+          </div>
176
+        )}
177
+
178
+        {/* Action Buttons */}
179
+        <div className="flex justify-center gap-4">
180
+          <button
181
+            onClick={onNewGame}
182
+            className="px-6 py-3 bg-green-600 hover:bg-green-700 text-white rounded-lg font-terminal transition transform hover:scale-105"
183
+          >
184
+            New Game
185
+          </button>
186
+          {!showLeaderboard && submitted && (
187
+            <button
188
+              onClick={() => setShowLeaderboard(true)}
189
+              className="px-6 py-3 bg-gray-700 hover:bg-gray-600 text-white rounded-lg font-terminal transition"
190
+            >
191
+              View Leaderboard
192
+            </button>
193
+          )}
194
+          {showLeaderboard && (
195
+            <button
196
+              onClick={() => setShowLeaderboard(false)}
197
+              className="px-6 py-3 bg-gray-700 hover:bg-gray-600 text-white rounded-lg font-terminal transition"
198
+            >
199
+              Back to Score
200
+            </button>
201
+          )}
202
+        </div>
203
+      </div>
204
+    </div>
205
+  );
206
+};
207
+
208
+export default GameOverModal;
frontend/src/hooks/useCommandExecution.tsmodified
@@ -1,10 +1,6 @@
1
+// src/hooks/useCommandExecution.ts
1
 import { useState, useCallback } from 'react';
2
 import { useState, useCallback } from 'react';
2
-import { gameApi, CommandResponse, TreeNode } from '@/lib/api';
3
+import { gameApi, CommandResponse, TreeNode, GameStats } from '@/lib/api';
3
-
4
-interface CommandExecutionState {
5
-  executing: boolean;
6
-  commandHistory: CommandHistoryEntry[];
7
-}
8
 
4
 
9
 interface CommandHistoryEntry {
5
 interface CommandHistoryEntry {
10
   command: string;
6
   command: string;
@@ -13,99 +9,87 @@ interface CommandHistoryEntry {
13
 }
9
 }
14
 
10
 
15
 export const useCommandExecution = (
11
 export const useCommandExecution = (
16
-  gameTreeId: number | null,
12
+  treeId: number | null,
17
   sessionId: number | null,
13
   sessionId: number | null,
18
-  onLocationChange?: (newPath: string) => void,
14
+  onLocationChange: (newLocation: string) => void,
19
-  onMoleKilled?: (response: CommandResponse) => void,
15
+  onMoleKilled: (response: CommandResponse) => void,
20
-  onTreeUpdate?: (updater: (tree: TreeNode) => TreeNode) => void
16
+  onTreeUpdate: (updater: (tree: TreeNode) => TreeNode) => void,
17
+  onGameComplete?: (stats: GameStats) => void
21
 ) => {
18
 ) => {
22
-  const [state, setState] = useState<CommandExecutionState>({
19
+  const [executing, setExecuting] = useState(false);
23
-    executing: false,
20
+  const [commandHistory, setCommandHistory] = useState<CommandHistoryEntry[]>([]);
24
-    commandHistory: [],
25
-  });
26
 
21
 
27
   const addToHistory = useCallback((entry: CommandHistoryEntry) => {
22
   const addToHistory = useCallback((entry: CommandHistoryEntry) => {
28
-    setState(prev => ({
23
+    setCommandHistory(prev => [...prev, entry]);
29
-      ...prev,
30
-      commandHistory: [...prev.commandHistory, entry],
31
-    }));
32
   }, []);
24
   }, []);
33
 
25
 
34
   const clearHistory = useCallback(() => {
26
   const clearHistory = useCallback(() => {
35
-    setState(prev => ({
27
+    setCommandHistory([]);
36
-      ...prev,
37
-      commandHistory: [],
38
-    }));
39
   }, []);
28
   }, []);
40
 
29
 
41
   const executeCommand = useCallback(async (cmd: string) => {
30
   const executeCommand = useCallback(async (cmd: string) => {
42
-    if (!gameTreeId || !cmd.trim() || state.executing) return;
31
+    if (!treeId || !cmd.trim() || executing) return;
43
 
32
 
44
-    setState(prev => ({ ...prev, executing: true }));
33
+    setExecuting(true);
45
-    
46
     try {
34
     try {
47
-      const response = await gameApi.executeCommand(
35
+      const response = await gameApi.executeCommand(treeId, cmd, sessionId || undefined);
48
-        gameTreeId,
49
-        cmd,
50
-        sessionId || undefined
51
-      );
52
-
53
-      // Build output with timer warnings
54
-      let fullOutput = response.output;
55
       
36
       
56
-      // Add timer warnings if present
37
+      // Build output with timer warnings
38
+      let output = response.output;
57
       if (response.timer_warnings && response.timer_warnings.length > 0) {
39
       if (response.timer_warnings && response.timer_warnings.length > 0) {
58
-        const warnings = response.timer_warnings.map(w => 
40
+        const warnings = response.timer_warnings.map(w => `⚠️ ${w.level}: ${w.message}`).join('\n');
59
-          `⚠️ ${w.level}: ${w.message}`
41
+        output = warnings + (output ? '\n' + output : '');
60
-        ).join('\n');
61
-        fullOutput = warnings + (fullOutput ? '\n' + fullOutput : '');
62
       }
42
       }
63
-
43
+      
64
-      // Add to command history
65
       addToHistory({
44
       addToHistory({
66
         command: cmd,
45
         command: cmd,
67
-        output: fullOutput,
46
+        output,
68
         success: response.success,
47
         success: response.success,
69
       });
48
       });
70
 
49
 
71
-      // Handle location change
50
+      // Update player location if it changed
72
-      if (response.current_path && onLocationChange) {
51
+      if (response.current_path) {
73
         onLocationChange(response.current_path);
52
         onLocationChange(response.current_path);
74
       }
53
       }
75
 
54
 
76
-      // Handle mole spawning
55
+      // Handle mole killed
77
-      if (response.mole_spawned && onMoleKilled) {
56
+      if (response.mole_spawned) {
78
-        // Format the output to include timer info on new line
79
-        if (response.timer_reason && !response.output.includes('New mole detected')) {
80
-          response.output += `\nNew mole detected ${response.timer_reason}!`;
81
-        }
82
         onMoleKilled(response);
57
         onMoleKilled(response);
83
       }
58
       }
84
 
59
 
85
-      // Legacy: Handle game won
60
+      // Handle game completion
86
-      if (response.game_won && !response.mole_spawned && onTreeUpdate) {
61
+      if (response.game_completed && response.final_stats && onGameComplete) {
87
-        onTreeUpdate((tree) => ({
62
+        onGameComplete(response.final_stats);
88
-          ...tree,
89
-          has_mole: true,
90
-        }));
91
       }
63
       }
92
 
64
 
93
-      return response;
65
+      // Handle mole location updates in tree
94
-    } catch {
66
+      if (response.new_mole_location) {
67
+        onTreeUpdate((tree) => {
68
+          const updateMoleInTree = (node: TreeNode, molePath: string): TreeNode => {
69
+            return {
70
+              ...node,
71
+              has_mole: node.path === molePath,
72
+              children: node.children.map(child => updateMoleInTree(child, molePath))
73
+            };
74
+          };
75
+          return updateMoleInTree(tree, response.new_mole_location);
76
+        });
77
+      }
78
+    } catch (error) {
79
+      console.error('Command execution failed:', error);
95
       addToHistory({
80
       addToHistory({
96
         command: cmd,
81
         command: cmd,
97
-        output: 'Error: Failed to execute command. Check your connection.',
82
+        output: 'Error: Failed to execute command',
98
         success: false,
83
         success: false,
99
       });
84
       });
100
-      return null;
101
     } finally {
85
     } finally {
102
-      setState(prev => ({ ...prev, executing: false }));
86
+      setExecuting(false);
103
     }
87
     }
104
-  }, [gameTreeId, sessionId, state.executing, addToHistory, onLocationChange, onMoleKilled, onTreeUpdate]);
88
+  }, [treeId, sessionId, executing, onLocationChange, onMoleKilled, onTreeUpdate, onGameComplete, addToHistory]);
105
 
89
 
106
   return {
90
   return {
107
-    executing: state.executing,
91
+    executing,
108
-    commandHistory: state.commandHistory,
92
+    commandHistory,
109
     executeCommand,
93
     executeCommand,
110
     addToHistory,
94
     addToHistory,
111
     clearHistory,
95
     clearHistory,
frontend/src/lib/api.tsmodified
@@ -39,6 +39,20 @@ export interface MoleDirection {
39
   angle: number;
39
   angle: number;
40
 }
40
 }
41
 
41
 
42
+export interface TimerWarning {
43
+  level: string;
44
+  message: string;
45
+}
46
+
47
+export interface GameStats {
48
+  score: number;
49
+  moles_killed: number;
50
+  moles_escaped: number;
51
+  commands_used: number;
52
+  time_taken: string;
53
+  directories_visited: number;
54
+}
55
+
42
 export interface CommandResponse {
56
 export interface CommandResponse {
43
   command: string;
57
   command: string;
44
   success: boolean;
58
   success: boolean;
@@ -50,6 +64,14 @@ export interface CommandResponse {
50
   score?: number;
64
   score?: number;
51
   moles_killed?: number;
65
   moles_killed?: number;
52
   new_mole_location?: string;
66
   new_mole_location?: string;
67
+  timer_remaining?: number;
68
+  timer_warnings?: TimerWarning[];
69
+  new_timer?: number;
70
+  timer_reason?: string;
71
+  timer_distance?: number;
72
+  game_completed?: boolean;
73
+  session_completed?: boolean;
74
+  final_stats?: GameStats;
53
 }
75
 }
54
 
76
 
55
 export interface GameCreationResponse {
77
 export interface GameCreationResponse {
@@ -57,6 +79,9 @@ export interface GameCreationResponse {
57
   session_id: number;
79
   session_id: number;
58
   mole_hint: string;
80
   mole_hint: string;
59
   home_directory: string;
81
   home_directory: string;
82
+  initial_timer?: number;
83
+  timer_reason?: string;
84
+  timer_distance?: number;
60
 }
85
 }
61
 
86
 
62
 export interface HintResponse {
87
 export interface HintResponse {
@@ -95,29 +120,6 @@ export interface CommandReferenceResponse {
95
   special_paths: SpecialPath[];
120
   special_paths: SpecialPath[];
96
 }
121
 }
97
 
122
 
98
-export interface TimerWarning {
99
-  level: string;
100
-  message: string;
101
-}
102
-
103
-export interface CommandResponse {
104
-  command: string;
105
-  success: boolean;
106
-  output: string;
107
-  current_path: string;
108
-  game_won?: boolean;
109
-  mole_spawned?: boolean;
110
-  mole_direction?: MoleDirection | null;
111
-  score?: number;
112
-  moles_killed?: number;
113
-  new_mole_location?: string;
114
-  timer_remaining?: number;
115
-  timer_warnings?: TimerWarning[];
116
-  new_timer?: number;
117
-  timer_reason?: string;
118
-  timer_distance?: number;
119
-}
120
-
121
 export interface TimerStatusResponse {
123
 export interface TimerStatusResponse {
122
   remaining: number;
124
   remaining: number;
123
   total: number;
125
   total: number;
@@ -148,14 +150,27 @@ export interface CheckTimerResponse {
148
   message?: string;
150
   message?: string;
149
 }
151
 }
150
 
152
 
151
-export interface GameCreationResponse {
153
+export interface LeaderboardEntry {
152
-  tree: FileSystemTree;
154
+  rank: number;
153
-  session_id: number;
155
+  player_name: string;
154
-  mole_hint: string;
156
+  score: number;
155
-  home_directory: string;
157
+  moles_killed: number;
156
-  initial_timer?: number;
158
+  moles_escaped: number;
157
-  timer_reason?: string;
159
+  commands_used: number;
158
-  timer_distance?: number;
160
+  time_taken: string;
161
+  completed_at: string;
162
+}
163
+
164
+export interface LeaderboardResponse {
165
+  leaderboard: LeaderboardEntry[];
166
+  total_games: number;
167
+}
168
+
169
+export interface SavePlayerNameResponse {
170
+  success: boolean;
171
+  player_name: string;
172
+  score: number;
173
+  leaderboard_position: number;
159
 }
174
 }
160
 
175
 
161
 export const gameApi = {
176
 export const gameApi = {
@@ -200,7 +215,7 @@ export const gameApi = {
200
     return response.data;
215
     return response.data;
201
   },
216
   },
202
 
217
 
203
-    getTimerStatus: async (treeId: number): Promise<TimerStatusResponse> => {
218
+  getTimerStatus: async (treeId: number): Promise<TimerStatusResponse> => {
204
     const response = await api.get(`/trees/filesystem-trees/${treeId}/timer_status/`);
219
     const response = await api.get(`/trees/filesystem-trees/${treeId}/timer_status/`);
205
     return response.data;
220
     return response.data;
206
   },
221
   },
@@ -211,4 +226,17 @@ export const gameApi = {
211
     const response = await api.get(url + params);
226
     const response = await api.get(url + params);
212
     return response.data;
227
     return response.data;
213
   },
228
   },
229
+  
230
+  savePlayerName: async (sessionId: number, playerName: string): Promise<SavePlayerNameResponse> => {
231
+    const response = await api.post('/trees/game-sessions/save_player_name/', {
232
+      session_id: sessionId,
233
+      player_name: playerName,
234
+    });
235
+    return response.data;
236
+  },
237
+
238
+  getLeaderboard: async (): Promise<LeaderboardResponse> => {
239
+    const response = await api.get('/trees/game-sessions/leaderboard/');
240
+    return response.data;
241
+  },
214
 };
242
 };