diff --git a/assets/js/Player.js b/assets/js/Player.js new file mode 100644 index 0000000..73b89d3 --- /dev/null +++ b/assets/js/Player.js @@ -0,0 +1,136 @@ +/** + * 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) { + if (typeof name !== 'string') { + throw new TypeError('Name must be a string'); + } + 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) { + if (typeof score !== 'number' || isNaN(score)) { + throw new TypeError('Score must be a valid number'); + } + 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) { + if (typeof points !== 'number' || isNaN(points)) { + throw new TypeError('Points must be a valid number'); + } + this.score += points; + } + + /** + * Set the player level + * @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; + } + + /** + * 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..651c867 --- /dev/null +++ b/player-demo.html @@ -0,0 +1,258 @@ + + +
+ + +
+ + 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. +
+Player.getInstance() sempre retorna la mateixa instàncianew Player()