diff --git a/R1.02/tp/tp5/README.md b/R1.02/tp/tp5/README.md new file mode 100644 index 0000000..d0d9b2c --- /dev/null +++ b/R1.02/tp/tp5/README.md @@ -0,0 +1,98 @@ +<h1> TP Js 1</h1> + + +> Récupérez les fichiers sources utilisés pour le tp [ici](src/). + +## Exercices + +Le TP est une implantation en javasript du jeu de la vie. Il s'agit d'un automate cellulaire, dont les règles sont les suivantes : + - Une cellule morte possédant exactement trois voisines vivantes devient vivante. + - Une cellule vivante possédant deux ou trois voisines vivantes reste vivante, sinon elle meurt. + +Une cellule est une unité de la grille (carrée). Un tour est appelé une génération. + + +>Pour plus d'informations, voir la page [Wikipédia](https://fr.wikipedia.org/wiki/Jeu_de_la_vie) + +> Le jeu est déjà codé, vous allez ajouter de nouvelles fonctionnalités au jeu dans ce TP. + +### Exercice 1 : Inclure du JS + +Chargez le fichier `script.js` dans la page html `index.html` + +> Il faut utiliser la balise `<script>` avec l'attribut `src` <br> +> Attention ! Chargez le fichier une fois que le DOM (la page HTML) est chargée. + +Vous devez avoir le résultat suivant : + + + +### Exercice 2 : Ajouter un bouton reset + +Ajoutez un bouton `reset` dans le HTML. Dans la fonction `main()` ajoutez ce qui est nécessaire pour pourvoir remettre à zéro le jeu. <br> + +> Pour remettre à zéro, il faut s'abonner à l'événement `click` sur le bouton reset, regardez comment le code est fait pour les autres boutons. <br> +> Il faut ensuite: +> - stopper `autoplayInterval`, un timer est démarré avec la fonctionnalité auto play +> - détruire la grille courante avec la fonction `gridManager.destroyGrid();` +> - créer un nouvel object `GridManager` avec `gridManager = new GridManager(gridSize, DIV_CONTAINER_ID)` +> - initialiser le jeu avec `gridManager.setInitialState(INITIAL_STATE)` +><br><br> + +> Vous avez des exemples dans le code. + + +### Exercice 3 : Changer la taille de la grille + +Vous allez ajouter un bouton qui permet d'afficher lors du clic une popin. Vous allez pouvoir renseigner la taille de la grille. + +> - Vous aurez besoin de la fonction [prompt](https://www.w3schools.com/jsref/met_win_prompt.asp). +> - La valeur que vous récupérer est un type `string` (équivalent à char[] en C), il faut donc parser le type string en integer avec la fonction [parseInt()](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/parseInt) +> - Vous allez réutiliser la fonction reset que vous avez écrite précédemment. + +> La grille est d'une taille minimum de 30. + + +### Exercice 4 : Afficher la taille de la grille dans l'interface + +Vous allez afficher la taille à côté de la chaine de caractères `Taille de la grille:`. + +> - Utilisez `document.getElementById(GRID_SIZE_VAL_ID)` +> - Utilisez la propriété `element.textContent = 'Hello World!` + +### Exercice 5 : Afficher la génération courante + +Vous allez afficher le numéro de la génération courante à côté de la chaine de caractères `Génération numéro:`. + +> - Ajoutez ce qu'il manque dans la fonction `computeNextGeneration(gridManager, generation)` +> - Pensez à incrémenter la variable `génération` + +### Exercice 6 : Vitesse d'execution du jeu + +Vous allez créer un bouton : + - x1 + - x2 + - x10 + - x100 + +Lorsque le jeu est en mode auto play, il faut changer la vitesse à laquelle il faut appeler la fonction `computeNextGeneration(gridManager, generation)`. Si le jeu n'est pas en mode auto play mettre à jour la valeur de interval + +> - Il faut pensez à stopper le `setInterval` et le démarrer avec la nouvelle vitesse de jeu +> - La vitesse normale du jeu est défini par la variable `GENERATION_INTERVAL` +> - x2 = `GENERATION_INTERVAL/2`, x10 = `GENERATION_INTERVAL/10`, etc. + +### Exercice 7 : (Bonus) Export de l'état de la grille + +On veut pouvoir sauvegarder la partie pour la reprendre plus tard. Pour cela il faut enregistrer l'état du jeu. <br> + +Créez une fonction d'export qui permet de : + - récupérez la taille de la grille, connaitre les coordonnées des cellules actives, la génération courante et la vitesse d'éxecution du jeu si mode auto play + - sauvegardez l'état du jeu en `localStorage` lorsque l'on clique sur un bouton `Sauvegarder` + + + > - Réutilisez la fonction `logCurrentGridState` qui permet d'afficher les coordonnées des cellules actives dans la console + > - Un peu d'aide sur le [localStorage](https://developer.mozilla.org/fr/docs/Web/API/Window/localStorage) + +### Exercice 8 : (Bonus) Reprendre une partie + +A partir de l'exercice 7, écrire une fonction qui permet de reprendre une partie lorsque l'on clique sur un bouton `Reprendre` et si des données sont stockées en localStorage. diff --git a/R1.02/tp/tp5/img/jeu.png b/R1.02/tp/tp5/img/jeu.png new file mode 100644 index 0000000..4586347 Binary files /dev/null and b/R1.02/tp/tp5/img/jeu.png differ diff --git a/R1.02/tp/tp5/src/index.html b/R1.02/tp/tp5/src/index.html new file mode 100644 index 0000000..e47af8e --- /dev/null +++ b/R1.02/tp/tp5/src/index.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<html lang="fr"> + + <head> + <meta charset="utf-8"> + <meta name="viewport" content="initial-scale=1,witdh=device-width"> + <title>Jeu de la vie</title> + <style> + * { + font-size : 1.1rem; + } + .menu { + text-align: center; + } + + .row { + display: flex; + align-items: center; + justify-content: center; + } + + .cell { + width: 1.25rem; + height: 1.25rem; + border: 1px solid black; + box-sizing : border-box; + } + </style> + </head> + + <body> + <menu class="menu"> + <button id="next-gen">Prochaine Génération</button> + <button id="autoplay">Auto Play</button> + <p> + Génération numéro: <span id="generation-value"></span> | Taille de la grille: <span id="grid-size-value"></span> + </p> + </menu> + + <div id="container"></div> + <script src="script.js"></script> + </body> + +</html> diff --git a/R1.02/tp/tp5/src/script.js b/R1.02/tp/tp5/src/script.js new file mode 100644 index 0000000..cd5bcb1 --- /dev/null +++ b/R1.02/tp/tp5/src/script.js @@ -0,0 +1,207 @@ +// --------------------------------------------------------------------- +// --------------------------------------------------------------------- + +/** + * Cette partie du code, vous n'avez pas besoin d'y toucher. Elle permet + * de gérer la grille et l'affichage des cellules + */ + +class GridManager { + ACTIVE_COLOR = 'black'; + INACTIVE_COLOR = 'grey'; + + gridContainerId; + gridSize; + grid = []; + + constructor(gridSize, gridContainerId) { + if (!gridSize || gridSize < 30) { + throw new Error('The grid size must be at least 30'); + } + + if (!gridContainerId) { + throw new Error('gridContainerId must be set'); + } + + this.gridSize = gridSize; + this.gridContainerId = gridContainerId; + this.createGrid(); + } + + createGrid() { + const container = document.getElementById(this.gridContainerId); + + for (let i = 0; i < this.gridSize; i++) { + const row = document.createElement('div'); + row.className = 'row'; + + const gridRow = []; + + for (let j = 0; j < this.gridSize; j++) { + const cell = document.createElement('div'); + cell.className = 'cell'; + cell.style.backgroundColor = this.INACTIVE_COLOR; + row.appendChild(cell); + + gridRow.push(cell); + } + + container.appendChild(row); + this.grid.push(gridRow); + } + } + + destroyGrid() { + for (let x = 0; x < this.gridSize; x++) { + for (let y = 0; y < this.gridSize; y++) { + const node = this.grid[y][x]; + node.parentNode.removeChild(node); + } + } + + const container = document.getElementById(this.gridContainerId); + while (container.firstChild) { + container.removeChild(container.lastChild); + } + + this.grid = []; + } + + setInitialState(initialState) { + const coords = initialState.split(';').map(coord => coord.split(',')); + coords.forEach((coord) => this.activeCell(+coord[0], +coord[1])); + } + + isInGridRange(x, y) { + return x >= 0 && x < this.gridSize && y >= 0 && y < this.gridSize; + } + + isActiveCell(x, y) { + return this.isInGridRange(x, y) && this.grid[y][x].style.backgroundColor === this.ACTIVE_COLOR; + } + + activeCell(x, y) { + if (!this.isInGridRange(x, y)) { + return; + } + + this.grid[y][x].style.backgroundColor = this.ACTIVE_COLOR; + } + + deactiveCell(x, y) { + if (!this.isInGridRange(x, y)) { + return; + } + + this.grid[y][x].style.backgroundColor = this.INACTIVE_COLOR; + } + + getNumberActiveNeighbourCells(x, y) { + const neighbours = [ + [x-1, y-1], [x, y-1], [x+1, y-1], + [x-1, y], [x+1, y], + [x-1, y+1], [x, y+1], [x+1, y+1], + ]; + + return neighbours.map(cell => this.isActiveCell(cell[0], cell[1])).filter(cell => cell === true).length; + } + + logCurrentGridState() { + const activeCells = []; + + for (let x = 0; x < this.gridSize; x++) { + for (let y = 0; y < this.gridSize; y++) { + if (this.isActiveCell(x, y)) { + activeCells.push(`${x},${y}`); + } + } + } + + console.log(activeCells.join(';')); + } +} + + +// --------------------------------------------------------------------- +// --------------------------------------------------------------------- + + +const INITIAL_STATE = '11,1;12,1;10,2;9,3;9,4;9,5;10,6;11,7;12,7;2,4;1,5;2,5;18,28;17,28;19,27;20,26;20,25;20,24;19,23;18,22;17,22;27,25;28,24;27,24;11,28;12,28;10,27;9,26;9,25;9,24;10,23;11,22;12,22;2,25;1,24;2,24;18,1;17,1;19,2;20,3;20,4;20,5;19,6;18,7;17,7;27,4;28,5;27,5'; +const GENERATION_INTERVAL = 1000; // 1 seconde +const DIV_CONTAINER_ID = 'container'; +const BTN_AUTOPLAY_ID = 'autoplay'; +const BTN_NEXT_GEN_ID = 'next-gen'; +const GENERATION_VAL_ID = 'generation-value'; +const GRID_SIZE_VAL_ID = 'grid-size-value'; + +function computeNextGeneration(gridManager, generation) +{ + + // incrémenter la valeur de la génération et l'afficher à côté de 'Génération numéro:' + + const nextGrid = []; + + for (let x = 0; x < gridManager.gridSize; x++) { + const row = []; + for (let y = 0; y < gridManager.gridSize; y++) { + const isActive = gridManager.isActiveCell(x, y); + const numberActiveNeighbourCells = gridManager.getNumberActiveNeighbourCells(x, y); + + if (!isActive) { + row.push(numberActiveNeighbourCells === 3 ? true : false); + } else { + row.push(numberActiveNeighbourCells === 2 || numberActiveNeighbourCells === 3 ? true : false); + } + } + + nextGrid.push(row); + } + + for (let x = 0; x < nextGrid.length; x++) { + for (let y = 0; y < nextGrid[x].length; y++) { + nextGrid[x][y] ? gridManager.activeCell(x,y) : gridManager.deactiveCell(x,y); + } + } + + gridManager.logCurrentGridState(); + return generation; +} + +// Fonction principale du jeu + +function main() +{ + let autoplayInterval; + let gridSize = 30; + let generation = 0; + + let gridManager = new GridManager(gridSize, DIV_CONTAINER_ID); + gridManager.setInitialState(INITIAL_STATE); + + // Lorsqu'un utilisateur clique sur 'Auto Play' + document.getElementById(BTN_AUTOPLAY_ID).addEventListener('click', () => { + if (autoplayInterval) { + return; + } + + autoplayInterval = setInterval(() => { + generation = computeNextGeneration(gridManager); + }, GENERATION_INTERVAL); + }); + + // Lorsqu'un utilisateur clique sur 'Prochaine Génération' + document.getElementById(BTN_NEXT_GEN_ID).addEventListener('click', () => { + if (autoplayInterval) { + clearInterval(autoplayInterval); + autoplayInterval = null; + } + + computeNextGeneration(gridManager); + }); + +} + + +// Le jeu est démarré ici + +main();