Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions assets/js/Player.js
Original file line number Diff line number Diff line change
@@ -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);
125 changes: 125 additions & 0 deletions assets/js/Player.test.js
Original file line number Diff line number Diff line change
@@ -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 };
}
96 changes: 96 additions & 0 deletions lib/Player.README.md
Original file line number Diff line number Diff line change
@@ -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
Loading