PageSourceSearch

https://ilmenit.github.io/pressure/js/tutorial-ui.js

js ilmenit.github.io collected 2026-10-03 09:20:36 UTC 17,336 bytes, 505 lines download raw bytes

1/**
2 * tutorial-ui.js
3 * Manages tutorial UI components and rendering
4 */
5class TutorialUIManager {
6    constructor(tutorialService) {
7        this.tutorialService = tutorialService;
8        this.game = tutorialService.game;
9        this.board = tutorialService.board;
10        
11        // Tutorial UI elements
12        this.tutorialOverlay = null;
13        this.messageBox = null;
14        this.progressIndicator = null;
15        this.continueButton = null;
16        this.skipButton = null;
17        this.tryAgainButton = null;
18        this.tutorialControls = null;
19        
20        // Original game controls container to restore later
21        this.originalGameControls = null;
22        
23        // Event handlers for proper cleanup
24        this.continueHandler = null;
25        this.skipHandler = null;
26        this.tryAgainHandler = null;
27    }
28    
29    /**
30     * Create and setup tutorial UI elements
31     */
32    setupTutorialUI() {
33        this.createTutorialElements();
34        this.ensureTutorialStylesLoaded();
35        this.setupTutorialControls();
36    }
37    
38    /**
39     * Create tutorial UI elements
40     */
41    createTutorialElements() {
42        // Clean up any existing elements first
43        this.cleanupTutorialUI();
44        
45        // Create overlay container
46        this.tutorialOverlay = document.createElement('div');
47        this.tutorialOverlay.id = 'tutorial-overlay';
48        this.tutorialOverlay.className = 'tutorial-overlay';
49        
50        // Create message box
51        this.messageBox = document.createElement('div');
52        this.messageBox.id = 'tutorial-message';
53        this.messageBox.className = 'tutorial-message';
54        
55        // Add message box to overlay
56        this.tutorialOverlay.appendChild(this.messageBox);
57        
58        // Add overlay to game screen
59        const gameScreen = document.getElementById('game-screen');
60        if (gameScreen) {
61            gameScreen.appendChild(this.tutorialOverlay);
62        }
63    }
64    
65    /**
66     * Setup tutorial controls by replacing the game controls
67     */
68    setupTutorialControls() {
69        // Find the game controls container
70        const gameControls = document.querySelector('.game-controls');
71        if (!gameControls) {
72            console.error('Game controls container not found');
73            return;
74        }
75        
76        // Save original game controls to restore later
77        this.originalGameControls = gameControls.cloneNode(true);
78        
79        // Create tutorial controls
80        this.tutorialControls = document.createElement('div');
81        this.tutorialControls.className = 'tutorial-controls';
82        
83        // Create continue button
84        this.continueButton = document.createElement('button');
85        this.continueButton.id = 'tutorial-continue-btn';
86        this.continueButton.className = 'tutorial-btn';
87        this.continueButton.textContent = 'Continue';
88        this.continueButton.style.display = 'none';
89        
90        // Create try again button
91        this.tryAgainButton = document.createElement('button');
92        this.tryAgainButton.id = 'tutorial-try-again-btn';
93        this.tryAgainButton.className = 'tutorial-btn';
94        this.tryAgainButton.textContent = 'Try Again';
95        this.tryAgainButton.style.display = 'none';
96        
97        // Create skip button
98        this.skipButton = document.createElement('button');
99        this.skipButton.id = 'tutorial-skip-btn';
100        this.skipButton.className = 'tutorial-btn';
101        this.skipButton.textContent = 'Skip Tutorial';
102        
103        // Add event handlers
104        this.continueHandler = () => {
105            if (this.tutorialService && this.tutorialService.isActive) {
106                this.tutorialService.advanceToNextStep();
107            }
108        };
109        
110        this.skipHandler = () => {
111            if (this.tutorialService && this.tutorialService.isActive) {
112                this.tutorialService.end();
113            }
114        };
115        
116        this.tryAgainHandler = () => {
117            if (this.tutorialService && this.tutorialService.isActive) {
118                this.hideTryAgainButton();
119                this.tutorialService.stepManager.retryCurrentStep();
120            }
121        };
122        
123        this.continueButton.addEventListener('click', this.continueHandler);
124        this.skipButton.addEventListener('click', this.skipHandler);
125        this.tryAgainButton.addEventListener('click', this.tryAgainHandler);
126        
127        // Add buttons to controls
128        this.tutorialControls.appendChild(this.continueButton);
129        this.tutorialControls.appendChild(this.tryAgainButton);
130        this.tutorialControls.appendChild(this.skipButton);
131        
132        // Replace game controls with tutorial controls
133        gameControls.innerHTML = '';
134        gameControls.appendChild(this.tutorialControls);
135        
136        // Show the game controls container to ensure tutorial controls are visible
137        gameControls.style.display = 'flex';
138    }
139    
140    /**
141     * Ensure tutorial styles are loaded
142     */
143    ensureTutorialStylesLoaded() {
144        // Check if the styles are already loaded
145        if (!document.getElementById('tutorial-external-css')) {
146            // Create link element to load external CSS
147            const link = document.createElement('link');
148            link.id = 'tutorial-external-css';
149            link.rel = 'stylesheet';
150            link.type = 'text/css';
151            link.href = 'css/tutorial-styles.css';
152            
153            // Add to head
154            document.head.appendChild(link);
155        }
156    }
157    
158    /**
159     * Hide game controls during tutorial
160     */
161    hideGameControls() {
162        // This function is no longer needed as we're replacing the controls
163        // rather than hiding them
164    }
165    
166    /**
167     * Restore game controls
168     */
169    restoreGameControls() {
170        // Restore original game controls if they exist
171        if (this.originalGameControls) {
172            const gameControls = document.querySelector('.game-controls');
173            if (gameControls) {
174                // Replace with the original controls
175                gameControls.innerHTML = '';
176                
177                // Clone all child nodes from original game controls
178                Array.from(this.originalGameControls.childNodes).forEach(node => {
179                    gameControls.appendChild(node.cloneNode(true));
180                });
181                
182                // Restore event listeners for undo/redo buttons
183                const undoBtn = gameControls.querySelector('#undo-btn');
184                const redoBtn = gameControls.querySelector('#redo-btn');
185                const menuBtn = gameControls.querySelector('#menu-btn');
186                
187                if (undoBtn && this.game.undoMove) {
188                    undoBtn.addEventListener('click', this.game.undoMove.bind(this.game));
189                }
190                
191                if (redoBtn && this.game.redoMove) {
192                    redoBtn.addEventListener('click', this.game.redoMove.bind(this.game));
193                }
194                
195                if (menuBtn && this.game.ui && this.game.ui.openMenu) {
196                    menuBtn.addEventListener('click', this.game.ui.openMenu.bind(this.game.ui));
197                }
198            }
199            
200            // Clear the reference
201            this.originalGameControls = null;
202        }
203    }
204    
205    /**
206     * Show the game screen
207     */
208    showGameScreen() {
209        // Hide all screens first to avoid potential conflicts
210        const screens = document.querySelectorAll('.screen');
211        screens.forEach(screen => {
212            if (screen) screen.classList.add('hidden');
213        });
214        
215        // Show game screen specifically
216        const gameScreen = document.getElementById('game-screen');
217        if (gameScreen) {
218            gameScreen.classList.remove('hidden');
219        }
220        
221        // Hide any opponent display from tournament mode
222        const opponentDisplay = document.getElementById('opponent-display');
223        if (opponentDisplay) {
224            opponentDisplay.classList.add('hidden');
225        }
226    }
227    
228    /**
229     * Show the main menu
230     */
231    showMainMenu() {
232        // Hide all screens first
233        const screens = document.querySelectorAll('.screen');
234        screens.forEach(screen => {
235            if (screen) screen.classList.add('hidden');
236        });
237        
238        // Show main menu
239        const mainMenu = document.getElementById('main-menu');
240        if (mainMenu) {
241            mainMenu.classList.remove('hidden');
242            
243            // Focus on the first button in the main menu for better UX
244            setTimeout(() => {
245                const firstButton = mainMenu.querySelector('button');
246                if (firstButton) {
247                    firstButton.focus();
248                }
249            }, 100);
250        }
251    }
252    
253    /**
254     * Show instructions in the message box with integrated step progress
255     */
256    showInstructions(title, message, currentStep, totalSteps) {
257        if (!this.messageBox) return;
258        
259        // Format the title with step count
260        const titleElement = `<span class="tutorial-step-title">Tutorial ${currentStep} of ${totalSteps}: ${title}</span>`;
261        
262        // Convert newlines to HTML breaks
263        const formattedMessage = message.replace(/\n/g, '<br>');
264        
265        const content = `${titleElement}${formattedMessage}`;
266        this.messageBox.innerHTML = content;
267    }
268    
269    /**
270     * Show the continue button
271     */
272    showContinueButton() {
273        if (this.continueButton) {
274            this.continueButton.style.display = 'block';
275            if (this.tryAgainButton) {
276                this.tryAgainButton.style.display = 'none'; // Hide try again button when showing continue
277            }
278            
279            // Focus the continue button for better accessibility
280            setTimeout(() => {
281                this.continueButton.focus();
282            }, 100);
283        }
284    }
285    
286    /**
287     * Hide the continue button
288     */
289    hideContinueButton() {
290        if (this.continueButton) {
291            this.continueButton.style.display = 'none';
292        }
293    }
294    
295    /**
296     * Show the try again button
297     */
298    showTryAgainButton() {
299        if (this.tryAgainButton) {
300            this.tryAgainButton.style.display = 'block';
301            if (this.continueButton) {
302                this.continueButton.style.display = 'none'; // Hide continue button when showing try again
303            }
304            
305            // Focus the try again button for better accessibility
306            setTimeout(() => {
307                this.tryAgainButton.focus();
308            }, 100);
309        }
310    }
311    
312    /**
313     * Hide the try again button
314     */
315    hideTryAgainButton() {
316        if (this.tryAgainButton) {
317            this.tryAgainButton.style.display = 'none';
318        }
319    }
320    
321    /**
322     * Show completion message
323     */
324    showCompletionMessage() {
325        // Update message box
326        this.showInstructions("Tutorial Complete", `
327            <div class="tutorial-complete">
328                <div class="tutorial-complete-icon">🏆</div>
329                <p>You win the match when opponents has no tokens to move.</p>
330                <p>Try now the Tournament Mode!</p>
331            </div>
332        `, this.tutorialService.stepManager.getTotalSteps(), this.tutorialService.stepManager.getTotalSteps());
333        
334        // Hide the continue and skip buttons since we're returning to menu automatically
335        if (this.continueButton) this.continueButton.style.display = 'none';
336        if (this.skipButton) this.skipButton.style.display = 'none';
337    }
338    
339    /**
340     * Clean up all tutorial UI elements
341     */
342    cleanupTutorialUI() {
343        // Remove event listeners
344        if (this.continueButton) {
345            this.continueButton.removeEventListener('click', this.continueHandler);
346        }
347        
348        if (this.skipButton) {
349            this.skipButton.removeEventListener('click', this.skipHandler);
350        }
351        
352        if (this.tryAgainButton) {
353            this.tryAgainButton.removeEventListener('click', this.tryAgainHandler);
354        }
355        
356        // Remove overlay if it exists
357        if (this.tutorialOverlay && this.tutorialOverlay.parentNode) {
358            this.tutorialOverlay.parentNode.removeChild(this.tutorialOverlay);
359        }
360        
361        // Restore original game controls
362        this.restoreGameControls();
363        
364        // Reset references
365        this.tutorialOverlay = null;
366        this.messageBox = null;
367        this.continueButton = null;
368        this.skipButton = null;
369        this.tryAgainButton = null;
370        this.tutorialControls = null;
371        this.continueHandler = null;
372        this.skipHandler = null;
373        this.tryAgainHandler = null;
374        
375        // Remove any tutorial classes from cells
376        this.clearHighlights();
377        
378        // Remove visual guidance
379        this.clearVisualGuidance();
380    }
381    
382    /**
383     * Clear all highlights
384     */
385    clearHighlights() {
386        
387        // Clear cell highlights
388        document.querySelectorAll('.cell.tutorial-highlight').forEach(cell => {
389            cell.classList.remove('tutorial-highlight');
390        });
391        
392        // Clear token highlights
393        document.querySelectorAll('.token.tutorial-active').forEach(token => {
394            token.classList.remove('tutorial-active');
395        });
396        
397        // Clear inactive token highlights
398        document.querySelectorAll('.token.tutorial-inactive-highlight').forEach(token => {
399            token.classList.remove('tutorial-inactive-highlight');
400        });
401        
402        // Only remove last-move-indicator elements if in active tutorial
403        document.querySelectorAll('.last-move-indicator').forEach(el => {
404            if (el.parentNode) {
405                el.parentNode.removeChild(el);
406            }
407        });
408    }
409    
410    /**
411     * Clear visual guidance elements
412     */
413    clearVisualGuidance() {
414        // Remove paths
415        const paths = document.querySelectorAll('.tutorial-path');
416        paths.forEach(path => {
417            if (path.parentNode) {
418                path.parentNode.removeChild(path);
419            }
420        });
421    }
422    
423    /**
424     * Highlight specific positions
425     */
426    highlightPositions(positions) {
427        if (!positions || positions.length === 0) return;
428        
429        positions.forEach(pos => {
430            // Check position validity
431            if (!pos || typeof pos.row !== 'number' || typeof pos.col !== 'number') return;
432            
433            // Find the cell
434            const cell = this.board.boardElement.querySelector(
435                `.cell[data-row="${pos.row}"][data-col="${pos.col}"]`
436            );
437            
438            if (cell) {
439                // Add highlight class
440                cell.classList.add('tutorial-highlight');
441                
442                // If there's a token, highlight it too
443                const token = cell.querySelector('.token');
444                if (token) {
445                    token.classList.add('tutorial-active');
446                }
447            }
448        });
449    }
450    
451    /**
452     * Highlight inactive tokens
453     */
454    highlightInactiveToken() {
455        // Find all inactive tokens
456        for (let row = 0; row < this.board.size; row++) {
457            for (let col = 0; col < this.board.size; col++) {
458                const token = this.board.getTokenAt(row, col);
459                if (token && !token.isActive && !token.isCaptured) {
460                    // Find the cell
461                    const cell = this.board.boardElement.querySelector(
462                        `.cell[data-row="${row}"][data-col="${col}"]`
463                    );
464                    
465                    if (cell) {
466                        // Find the token element
467                        const tokenElement = cell.querySelector('.token');
468                        if (tokenElement) {
469                            tokenElement.classList.add('tutorial-inactive-highlight');
470                        }
471                    }
472                }
473            }
474        }
475    }
476    
477    /**
478     * Highlight captured tokens
479     */
480    highlightCapturedToken() {
481        // Find all captured tokens
482        for (let row = 0; row < this.board.size; row++) {
483            for (let col = 0; col < this.board.size; col++) {
484                const token = this.board.getTokenAt(row, col);
485                if (token && token.isCaptured) {
486                    // Find the cell
487                    const cell = this.board.boardElement.querySelector(
488                        `.cell[data-row="${row}"][data-col="${col}"]`
489                    );
490                    
491                    if (cell) {
492                        // Highlight the cell
493                        cell.classList.add('tutorial-highlight');
494                        
495                        // Find the token element
496                        const tokenElement = cell.querySelector('.token');
497                        if (tokenElement) {
498                            tokenElement.classList.add('tutorial-active');
499                        }
500                    }
501                }
502            }
503        }
504    }
505}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.