From d192356130513dc51938bb649ac1ad4375807094 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Oct 2025 18:41:25 +0000 Subject: [PATCH 1/3] Initial plan From b75c6be89a6096ed4c368d5c7f713d86244e1084 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Oct 2025 18:48:47 +0000 Subject: [PATCH 2/3] Implement Player class as singleton pattern Co-authored-by: Pandax40 <59170180+Pandax40@users.noreply.github.com> --- assets/js/Player.js | 124 +++++++++++++++++++ assets/js/Player.test.js | 125 +++++++++++++++++++ lib/Player.README.md | 96 +++++++++++++++ player-demo.html | 258 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 603 insertions(+) create mode 100644 assets/js/Player.js create mode 100644 assets/js/Player.test.js create mode 100644 lib/Player.README.md create mode 100644 player-demo.html diff --git a/assets/js/Player.js b/assets/js/Player.js new file mode 100644 index 0000000..bdd48cb --- /dev/null +++ b/assets/js/Player.js @@ -0,0 +1,124 @@ +/** + * Player class implemented as a Singleton pattern + * Ensures only one instance of the Player exists throughout the application + */ +class Player { + // Private static instance + static #instance = null; + + // Private constructor to prevent direct instantiation + constructor() { + // Prevent direct instantiation + if (Player.#instance) { + throw new Error("Player is a singleton class. Use Player.getInstance() instead."); + } + + // Initialize player properties + this.name = ""; + this.score = 0; + this.level = 1; + this.isPlaying = false; + } + + /** + * Get the singleton instance of Player + * @returns {Player} The singleton instance + */ + static getInstance() { + if (!Player.#instance) { + Player.#instance = new Player(); + } + return Player.#instance; + } + + /** + * Set the player name + * @param {string} name - The player's name + */ + setName(name) { + this.name = name; + } + + /** + * Get the player name + * @returns {string} The player's name + */ + getName() { + return this.name; + } + + /** + * Set the player score + * @param {number} score - The player's score + */ + setScore(score) { + this.score = score; + } + + /** + * Get the player score + * @returns {number} The player's score + */ + getScore() { + return this.score; + } + + /** + * Increase the player score + * @param {number} points - Points to add to the score + */ + addScore(points) { + this.score += points; + } + + /** + * Set the player level + * @param {number} level - The player's level + */ + setLevel(level) { + this.level = level; + } + + /** + * Get the player level + * @returns {number} The player's level + */ + getLevel() { + return this.level; + } + + /** + * Start playing + */ + startPlaying() { + this.isPlaying = true; + } + + /** + * Stop playing + */ + stopPlaying() { + this.isPlaying = false; + } + + /** + * Check if player is currently playing + * @returns {boolean} True if playing, false otherwise + */ + getIsPlaying() { + return this.isPlaying; + } + + /** + * Reset player to initial state + */ + reset() { + this.name = ""; + this.score = 0; + this.level = 1; + this.isPlaying = false; + } +} + +// Prevent modification of the class +Object.freeze(Player); diff --git a/assets/js/Player.test.js b/assets/js/Player.test.js new file mode 100644 index 0000000..36d1f7f --- /dev/null +++ b/assets/js/Player.test.js @@ -0,0 +1,125 @@ +/** + * Test file for Player singleton class + * This file demonstrates that the Player class correctly implements the singleton pattern + */ + +// Import is not needed for script tags, but this would be the ES6 way: +// import Player from './Player.js'; + +/** + * Test 1: Verify that getInstance returns the same instance + */ +function testSingletonInstance() { + const player1 = Player.getInstance(); + const player2 = Player.getInstance(); + + console.assert(player1 === player2, "FAIL: getInstance should return the same instance"); + console.log("✓ Test 1 PASSED: getInstance returns the same instance"); +} + +/** + * Test 2: Verify that direct instantiation throws an error + */ +function testDirectInstantiationPrevention() { + try { + const player = new Player(); + console.error("✗ Test 2 FAILED: Direct instantiation should throw an error"); + } catch (error) { + console.assert( + error.message.includes("singleton"), + "FAIL: Error message should mention singleton" + ); + console.log("✓ Test 2 PASSED: Direct instantiation correctly prevented"); + } +} + +/** + * Test 3: Verify that state is shared across instances + */ +function testSharedState() { + const player1 = Player.getInstance(); + const player2 = Player.getInstance(); + + player1.setName("TestPlayer"); + player1.setScore(100); + player1.setLevel(5); + + console.assert(player2.getName() === "TestPlayer", "FAIL: Name should be shared"); + console.assert(player2.getScore() === 100, "FAIL: Score should be shared"); + console.assert(player2.getLevel() === 5, "FAIL: Level should be shared"); + console.log("✓ Test 3 PASSED: State is correctly shared across instances"); +} + +/** + * Test 4: Verify player methods work correctly + */ +function testPlayerMethods() { + const player = Player.getInstance(); + player.reset(); + + player.setName("Alice"); + console.assert(player.getName() === "Alice", "FAIL: setName/getName"); + + player.setScore(50); + console.assert(player.getScore() === 50, "FAIL: setScore/getScore"); + + player.addScore(25); + console.assert(player.getScore() === 75, "FAIL: addScore"); + + player.setLevel(3); + console.assert(player.getLevel() === 3, "FAIL: setLevel/getLevel"); + + console.assert(player.getIsPlaying() === false, "FAIL: Initial isPlaying should be false"); + player.startPlaying(); + console.assert(player.getIsPlaying() === true, "FAIL: startPlaying"); + player.stopPlaying(); + console.assert(player.getIsPlaying() === false, "FAIL: stopPlaying"); + + console.log("✓ Test 4 PASSED: All player methods work correctly"); +} + +/** + * Test 5: Verify reset functionality + */ +function testReset() { + const player = Player.getInstance(); + + player.setName("Bob"); + player.setScore(999); + player.setLevel(10); + player.startPlaying(); + + player.reset(); + + console.assert(player.getName() === "", "FAIL: Name should be empty after reset"); + console.assert(player.getScore() === 0, "FAIL: Score should be 0 after reset"); + console.assert(player.getLevel() === 1, "FAIL: Level should be 1 after reset"); + console.assert(player.getIsPlaying() === false, "FAIL: isPlaying should be false after reset"); + + console.log("✓ Test 5 PASSED: Reset works correctly"); +} + +/** + * Run all tests + */ +function runAllTests() { + console.log("\n=== Running Player Singleton Tests ===\n"); + + testSingletonInstance(); + testDirectInstantiationPrevention(); + testSharedState(); + testPlayerMethods(); + testReset(); + + console.log("\n=== All tests completed ===\n"); +} + +// Auto-run tests when page loads (if in browser) +if (typeof document !== 'undefined') { + document.addEventListener('DOMContentLoaded', runAllTests); +} + +// Export for Node.js testing (if needed) +if (typeof module !== 'undefined' && module.exports) { + module.exports = { runAllTests }; +} diff --git a/lib/Player.README.md b/lib/Player.README.md new file mode 100644 index 0000000..81f3c78 --- /dev/null +++ b/lib/Player.README.md @@ -0,0 +1,96 @@ +# Player Singleton Class + +## Descripció + +La classe `Player` implementa el patró de disseny **Singleton**, assegurant que només existeixi una instància única del jugador a tota l'aplicació. + +## Característiques + +- **Una sola instància**: Només pot existir una instància de Player a l'aplicació +- **Accés global**: Accés a la instància mitjançant `Player.getInstance()` +- **Prevenció d'instanciació directa**: No es pot crear amb `new Player()` +- **Classe congelada**: La classe està congelada per prevenir modificacions + +## Ús + +### Obtenir la instància + +```javascript +const player = Player.getInstance(); +``` + +### Mètodes disponibles + +#### Gestió del nom +```javascript +player.setName("LinuxUPC"); +const name = player.getName(); +``` + +#### Gestió de la puntuació +```javascript +player.setScore(100); +const score = player.getScore(); +player.addScore(50); // Afegeix punts a la puntuació actual +``` + +#### Gestió del nivell +```javascript +player.setLevel(5); +const level = player.getLevel(); +``` + +#### Estat del joc +```javascript +player.startPlaying(); +player.stopPlaying(); +const isPlaying = player.getIsPlaying(); +``` + +#### Reiniciar +```javascript +player.reset(); // Reinicia a l'estat inicial +``` + +## Exemple d'ús + +```javascript +// Obtenir la instància del jugador +const player1 = Player.getInstance(); +player1.setName("Alice"); +player1.setScore(100); + +// En qualsevol altra part del codi +const player2 = Player.getInstance(); +console.log(player2.getName()); // "Alice" +console.log(player2.getScore()); // 100 + +// player1 i player2 són el mateix objecte +console.log(player1 === player2); // true +``` + +## Demo + +Pots veure una demostració interactiva del patró Singleton obrint el fitxer `player-demo.html` en un navegador. + +## Proves + +El fitxer `Player.test.js` conté proves per verificar: +- Que getInstance() retorna sempre la mateixa instància +- Que la instanciació directa està previnguda +- Que l'estat es comparteix entre referències +- Que tots els mètodes funcionen correctament +- Que el reset funciona com s'espera + +## Patró Singleton + +El patró Singleton assegura que: +1. Una classe només tingui una única instància +2. Proporciona un punt d'accés global a aquesta instància +3. L'instància es crea de manera lazy (només quan es necessita) + +Aquest patró és útil quan necessitem exactament un objecte per coordinar accions a través del sistema, com ara: +- Gestió de configuració +- Gestió d'estat del jugador en un joc +- Connexions a bases de dades +- Gestors de recursos compartits diff --git a/player-demo.html b/player-demo.html new file mode 100644 index 0000000..8f3b5b4 --- /dev/null +++ b/player-demo.html @@ -0,0 +1,258 @@ + + + + + + Player Singleton Demo - LinuxUPC + + + + + + +
+ + LinuxUPC +

Player Singleton Demo

+
+
+ +
+

Demostració del Patró Singleton amb la Classe Player

+ +
+

Què és el Patró Singleton?

+

+ El patró Singleton és un patró de disseny que assegura que una classe només tingui + una instància i proporciona un punt d'accés global a aquesta instància. + Això és útil quan exactament un objecte és necessari per coordinar accions a través del sistema. +

+
+ +
+

Estat del Player

+
+
+
Nom
+
-
+
+
+
Puntuació
+
0
+
+
+
Nivell
+
1
+
+
+
Jugant
+
No
+
+
+
+ +
+

Provar el Singleton

+
+ + + + + + + +
+
Fes clic als botons per provar el patró Singleton...
+
+ +
+

Característiques Clau

+ +
+
+ + + + + From b48812249f361a9f4f674434fa686a940c4edff0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Oct 2025 18:51:58 +0000 Subject: [PATCH 3/3] Add input validation and fix CSS variables Co-authored-by: Pandax40 <59170180+Pandax40@users.noreply.github.com> --- assets/js/Player.js | 12 ++++++++++++ player-demo.html | 8 ++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/assets/js/Player.js b/assets/js/Player.js index bdd48cb..73b89d3 100644 --- a/assets/js/Player.js +++ b/assets/js/Player.js @@ -36,6 +36,9 @@ class Player { * @param {string} name - The player's name */ setName(name) { + if (typeof name !== 'string') { + throw new TypeError('Name must be a string'); + } this.name = name; } @@ -52,6 +55,9 @@ class Player { * @param {number} score - The player's score */ setScore(score) { + if (typeof score !== 'number' || isNaN(score)) { + throw new TypeError('Score must be a valid number'); + } this.score = score; } @@ -68,6 +74,9 @@ class Player { * @param {number} points - Points to add to the score */ addScore(points) { + if (typeof points !== 'number' || isNaN(points)) { + throw new TypeError('Points must be a valid number'); + } this.score += points; } @@ -76,6 +85,9 @@ class Player { * @param {number} level - The player's level */ setLevel(level) { + if (typeof level !== 'number' || isNaN(level) || level < 1) { + throw new TypeError('Level must be a positive number'); + } this.level = level; } diff --git a/player-demo.html b/player-demo.html index 8f3b5b4..651c867 100644 --- a/player-demo.html +++ b/player-demo.html @@ -12,18 +12,18 @@ max-width: 800px; margin: 2rem auto; padding: 2rem; - background: var(--background-secondary, #2a2a2a); + background: var(--background-200); border-radius: 8px; } .demo-section { margin: 1.5rem 0; padding: 1rem; - background: var(--background-tertiary, #1a1a1a); + background: var(--background-300); border-radius: 4px; } .demo-section h3 { margin-top: 0; - color: var(--text-primary, #fff); + color: var(--text); } .demo-section code { background: #000; @@ -40,7 +40,7 @@ } .demo-button { padding: 0.5rem 1rem; - background: var(--accent-color, #007bff); + background: var(--accent); color: white; border: none; border-radius: 4px;