Compare commits

..

5 Commits

Author SHA1 Message Date
EmmanuelTiamzon 2b977bda34 update 2026-05-12 12:30:20 +02:00
EmmanuelTiamzon d66467a7f6 update 2026-04-14 17:29:49 +02:00
EmmanuelTiamzon dbc0fbd946 ajout tp2mvc, tp3 et update des tp 1 et 2 2026-04-14 12:28:43 +02:00
EmmanuelTiamzon d1e3f215aa update 2026-04-07 12:08:01 +02:00
EmmanuelTiamzon cc752a0fc9 commencement du tp2, ex0 fini 2026-04-07 12:07:22 +02:00
41 changed files with 3315 additions and 71 deletions
+22
View File
@@ -0,0 +1,22 @@
# TP1
## Ex1
1. Avec convert ou magick ou magick convert on fait :
```shell
magick Tux.xvg Tux.ppm
```
2. avec la commande head je prends les 3 premières lignes de Tux.ppm et les redirige vers header.txt
```shell
head -n 3 Tux.ppm >> header.txt
```
3. Mettre tout le fichier ppm sauf les 3 premières lignes dans un fichier body.bin
```shell
tail +3 Tux.ppm >> body.bin
```
4. Chiffrer avec openssl en AES-ECB le fichier .bin
```shell
```
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 49 KiB

File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
P6
216 256
65535
@@ -1,40 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css"
>
<script src="eratosthene.js"></script>
</head>
<body>
<main>
<form role="search">
<input name="limit" type="text">
<input type="submit" value="compute">
</form>
<article>
<p>time : <span id="time1"></span></p>
<p>time : <span id="time2"></span></p>
<p id="primes"></p>
</article>
</main>
<script>
document.querySelector("form").addEventListener("submit",ev => {
ev.preventDefault();
let start,end;
start = performance.now();
primes = eratosthene1(ev.target.limit.value);
end = performance.now();
document.getElementById("time1").textContent = end - start;
document.getElementById("primes").textContent = primes;
})
</script>
</body>
</html>
-23
View File
@@ -1,23 +0,0 @@
function eratosthene(n)
{
let primes = [];
let filterArray = [];
for(let i = 2; i <=n; i++){
// TODO
}
return primes;
}
function eratosthene1(n)
{
let numbers = Array.from({length : n - 2}, (v,k) => k + 2);
let p ,primes = [];
while(numbers.length){
[p,...numbers] = numbers;
numbers = numbers.filter( x => x%p != 0);
primes = [...primes,p];
}
return primes;
}
+7 -8
View File
@@ -48,15 +48,14 @@ class Ant {
} }
computeNextState() { computeNextState() {
if(this.tiles[this.x][this.y] === 1) { //On vérifie si la prochaine case est noire if(this.tiles[this.x][this.y] === 1) {
//On fait en sorte à ce qu'elle soit repeinte en blanc et tourne de 90° à gauche.
this.tiles[this.x][this.y] = 0; this.tiles[this.x][this.y] = 0;
this.rotateLeft(); this.rotateLeft();
} else { //Dans ce cas si la prochaine case est blanche } else {
// On fait en sorte à ce qu'elle soit repeinte en noir et tourne de 90° à droite. this.tiles[this.x][this.y] = 1;
this.tiles[this.x][this.y] = 1; this.rotateRight();
this.rotateRight(); }
}
} }
}
export default Ant; export default Ant;
+91
View File
@@ -0,0 +1,91 @@
let customers = [
{
'id': 1,
'f_name': 'Abby',
'l_name': 'Thomas',
'gender': 'M',
'married': true,
'age': 32,
'expense': 500,
'purchased': ['Shampoo', 'Toys', 'Book']
},
{
'id': 2,
'f_name': 'Jerry',
'l_name': 'Tom',
'gender': 'M',
'married': true,
'age': 64,
'expense': 100,
'purchased': ['Stick', 'Blade']
},
{
'id': 3,
'f_name': 'Dianna',
'l_name': 'Cherry',
'gender': 'F',
'married': true,
'age': 22,
'expense': 1500,
'purchased': ['Lipstik', 'Nail Polish', 'Bag', 'Book']
},
{
'id': 4,
'f_name': 'Dev',
'l_name': 'Currian',
'gender': 'M',
'married': true,
'age': 82,
'expense': 90,
'purchased': ['Book']
},
{
'id': 5,
'f_name': 'Maria',
'l_name': 'Gomes',
'gender': 'F',
'married': false,
'age': 7,
'expense': 300,
'purchased': ['Toys']
},
{
'id': 6,
'f_name': 'Homer',
'l_name': 'Simpson',
'gender': 'M',
'married': true,
'age': 39,
'expense': 500,
'purchased': ['Book']
}
];
// Question 1 :
function filterOldPeople(customers) {
customers.filter(returnOldPeople);
}
function returnOldPeople(customer) {
return customer.age > 60;
}
let oldCustomers = filterOldPeople(customers);
//console.log(oldCustomers);
// Question 2 :
function fullName(customer) {
customer.full_name = customer.f_name +" "+ customer.l_name;
}
customers.forEach(fullName);
//console.log(customers);
//Question 3 :
function isUnderTen(customer) {
return customer.age < 10;
}
customers.some(isUnderTen);
+55
View File
@@ -0,0 +1,55 @@
# TP javascript : DOM
Un peu [d'aide](./aide.md).
#### Ex0
On stocke dans un objet une liste de favoris :
```js
let favs = [
{
nom:"Google",
url:"http://www.google.fr"
},
{
nom:"Le Monde",
url:"http://www.google.fr"
},
{
nom:"L'Equipe",
url:"http://www.lequipe.fr"
}
];
```
Compléter le fichier `favoris.js` de manière à créer dans la page html la liste de liens
correspondant
![lien}](./img/liens.png?style=centerme)
Il vous faut créer dynamiquement les noeuds nécessaires avec l'api dom de javascript.
#### Ex1
Il s'agit de réaliser une version "simple" du jeu (whac-a-mole)[https://en.wikipedia.org/wiki/Whac-A-Mole].
Le principe du jeu est de frapper à l'aide d'un marteau sur le plus grand nombre
de taupes parmi celles qui sortent pour un temps très limité et aléatoirement
des trous situés sur un panneau de contrôle.
<div align="center">
<img src="./img/mole.png">
</div>
#### Ex2
Vous devez compléter les [sources](./src/ex1) d'un jeu qui consiste à essayer
d'éteindre toutes les lumières d'une grille en utilisant la règle suivante.
Quand on allume/éteint une lumière, on allume/éteint aussi ses voisines.
<div align="center">
<img src="./img/lights.png">
</div>
Chaque lumière, via l'interface dataset, possède un numéro (de 0 à size^2-1).
+132
View File
@@ -0,0 +1,132 @@
#### Selection d'éléments
```js
// le premier
document.querySelector(".box")
// ou tous
document.querySelectorAll(".box")
// avec l'id
document.getElementById("toto")
// selection d'un élément dans un autre
let container = document.querySeletor('.container')
container.querySelector('.box')
```
#### Traverser le dom
```js
var box = document.querySelector(".box")
box.nextElementSibling
box.previousElementSibling
box.parentElement
box.childNodes
```
#### Création/insertion d'éléments
```js
let p = document.createElement('p')
p.textContent="blabla"
document
.getElementById("myDiv")
.appendChild(p)
div.replaceChildren(p)
```
#### Gestionnaire évènementiels
```js
document.querySelector(".button").addEventListener("click", (e) => { /* ... */ })
document.querySelector(".button").addEventListener("mouseenter", (e) => { /* ... */ })
document.addEventListener("keyup", (e) => { /* ... */ })
```
#### window/document prêt
```js
document.addEventListener("DOMContentLoaded",() = > { .....})
// le dom a été construit (on n'attend pas le chargement du css, images, etc.
windon.addEventListener('load',()=>{...})
// le dom est prêt et toutes les ressources ont été chargées.
```
#### dataset
```html
<div id="user" data-id="1234567890" data-user="carinaanand" data-date-of-birth>
Carina Anand
</div>
```
```javascript
const el = document.querySelector("#user");
// el.id === 'user'
// el.dataset.id === '1234567890'
// el.dataset.user === 'carinaanand'
// el.dataset.dateOfBirth === ''
```
#### Local storage
```js
localStorage.setItem('monChat', 'Tom')
let cat = localStorage.getItem('myCat')
localStorage.clear()
```
#### Limiter les appels successifs à une fonction
```js
function debounce(f,wait)
{
let timeout
return function(...args){
clearTimeout(timeout)
timeout=setTimeout(()=>f(...args),wait)
}
}
```
#### Gestion du css depuis le DOM
```js
// Select the first .box and change its text color to #000
document.querySelector(".box").style.color = "#000";
// Set color to #000 and background to red
var box = document.querySelector(".box")
box.style.color = "#000"
box.style.backgroundColor = "red"
// Set all styles at once (and override any existing styles)
box.style.cssText = "color: #000; background-color: red"
// Hide and show an element by changing "display" to block and none
document.querySelector(".box").style.display = "none"
document.querySelector(".box").style.display = "block"
// Add, remove, and the toggle the "focus" class
var box = document.querySelector(".box")
box.classList.add("focus")
box.classList.remove("focus")
box.classList.toggle("focus")
box.classList.add("focus", "highlighted")
box.classList.remove("focus", "highlighted")
box.classList.add("focus", "highlighted")
box.classList.remove("focus", "highlighted")
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+18
View File
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css"
>
<title>Favoris</title>
<script src="./js/favoris.js"></script>
</head>
<body>
<main>
<h1>Favoris</h1>
</main>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
let favoris = [
{
nom:"Google" ,
url:"https://www.google.fr",
img:"https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Google_2015_logo.svg/200px-Google_2015_logo.svg.png"
},
{
nom:"Le Monde",
url:"https://www.lemonde.fr",
img:"https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Lemonde_fr_2005_logo.svg/200px-Lemonde_fr_2005_logo.svg.png?uselang=fr"
},
{
nom:"L'Equipe",
url:"https://www.lequipe.fr",
img:"https://upload.wikimedia.org/wikipedia/commons/thumb/3/32/L%27%C3%89quipe_wordmark.svg/200px-L%27%C3%89quipe_wordmark.svg.png"
}
]
window.addEventListener("load",()=>{
let laListe = document.createElement("ul");
favoris.forEach((favori) => {
let li = document.createElement("li");
let lien = document.createElement("a");
let image = document.createElement("img");
lien.href = favori.url;
lien.textContent = favori.nom;
image.src = favori.img;
lien.append(image);
li.append(lien);
laListe.append(li);
});
document.body.append(laListe);
})
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

+37
View File
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Whack-a-Mole Game</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css"
>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main>
<div class="game">
<h1>Whack-a-Mole!</h1>
<div class="score-board">
<span>Score: </span><span id="score">0</span>
</div>
<div class="holes">
<div class="hole"><div class="mole"><img src="img/mole2.png"></div></div>
<div class="hole"><div class="mole"><img src="img/mole2.png"></div></div>
<div class="hole"><div class="mole up"><img src="img/mole2.png"></div></div>
<div class="hole"><div class="mole"><img src="img/mole2.png"></div></div>
<div class="hole"><div class="mole"><img src="img/mole2.png"></div></div>
<div class="hole"><div class="mole"><img src="img/mole2.png"></div></div>
<div class="hole"><div class="mole"><img src="img/mole2.png"></div></div>
<div class="hole"><div class="mole"><img src="img/mole2.png"></div></div>
<div class="hole"><div class="mole"><img src="img/mole2.png"></div></div>
</div>
<button id="startButton">START</button>
</div>
</main>
<script src="script.js"></script>
</body>
</html>
+74
View File
@@ -0,0 +1,74 @@
const holes = document.querySelectorAll('.hole');
const moles = document.querySelectorAll('.mole');
const scoreBoard = document.getElementById('score');
const startButton = document.getElementById('startButton');
let lastHole;
let timeUp = false;
let score = 0;
let duration = 30
function randomTime(min, max) {
return Math.round(Math.random() * (max - min) + min);
}
function randomHole(holes) {
const idx = Math.floor(Math.random() * holes.length);
const hole = holes[idx];
if (hole === lastHole) {
return randomHole(holes);
}
lastHole = hole;
return hole;
}
function peep() {
const time = randomTime(1000, 1500);
const hole = randomHole(holes);
const mole = hole.querySelector('.mole');
mole.classList.add('up');
mole.classList.remove('whacked');
setTimeout(() => {
mole.classList.remove('up');
if(!timeUp) {
peep();
}
}, time);
}
function startGame() {
scoreBoard.textContent = 0;
score = 0;
timeUp = false;
moles.forEach(mole => {
mole.classList.remove('up');
mole.classList.remove('whacked');
});
peep();
setTimeout(()=>{
timeUp = true;
}, duration * 1000)
}
function bonk(e) {
if(!this.classList.contains('up')) {
return;
}
score ++;
scoreBoard.textContent = score;
this.classList.remove('up');
this.classList.add('whacked');
setTimeout(()=>{
this.classList.remove('whacked');
}, 800);
}
moles.forEach(mole => mole.addEventListener('click', bonk));
startButton.addEventListener('click', startGame);
+68
View File
@@ -0,0 +1,68 @@
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f0f0f0;
font-family: Arial, sans-serif;
}
.game {
text-align: center;
}
.holes {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
width: 400px;
margin: 20px auto;
}
.hole {
width: 100px;
height: 100px;
background : radial-gradient(rgb(150,250,150), #00ee00);
/* background-color: red;/*#8b4513;*/
/*background-image : url('img/mole2.png');*/
position: relative;
border-radius: 50%;
margin: 10px;
overflow: hidden;
}
.mole img{
width: 10px;
height: 10px;
/* background-color: #555;*/
position: absolute;
bottom: -100px;
left: 0px;
/*border-radius: 50%;*/
transition: bottom 0.3s;
}
.mole.up img{
background-image : url('img/mole2.png');
background-size:contain;
background-repeat: no-repeat;
width:90px;
height:90px;
bottom: 0px;
}
.mole.whacked img {
content: url('img/mole-whacked.png');
background-image : url('img/mole-whacked.png');
background-size:contain;
background-repeat: no-repeat;
width:90px;
height:90px;
bottom: -20px;
}
.score-board {
font-size: 1.5em;
margin: 20px;
}
+71
View File
@@ -0,0 +1,71 @@
// variables de vue
const button = document.querySelector("input[type=submit]");
const select = document.querySelector("select");
const board = document.querySelector(".board");
const message = document.querySelector("#message");
let lights = null
// state <-> tableau de booléen pour
// stocker l'état des lumières
// neighbors <-> tableau des voisins de chaque lumière
let state = [];
let neighbors = [];
let newBoard = ( (size ) => {
let frag = document.createDocumentFragment()
// TODO
// la fonction crée dans le dom la grille des lumières
board.replaceChildren(frag)
});
let calcNeighbours = ( (size) =>{
let res = [];
for(let i = 0; i < size; i ++){
for(let j = 0; j < size; j++){
let v = [];
v.push(i*size + j);
if ( (j - 1) >= 0) v.push(i*size + j - 1);
if ( (j + 1) < size) v.push(i*size + j + 1);
if ( (i - 1) >= 0) v.push(size*(i-1) + j );
if ( (i + 1) < size) v.push(size*(i+1) + j);
res.push(v)
}
}
return res
});
function play(){
let size = select.value;
newBoard(size);
neighbors = calcNeighbours(size);
lights = document.querySelectorAll(".board span");
state = Array.from({length: size*size}, () => Math.random() < 0.5 ? 1:0);
state.forEach((el,i)=>{
if (el) lights[i].classList.toggle("off");
});
message.textContent = "";
}
// abonnements
document
.querySelector(".board")
.addEventListener("click", ev => {
// TODO
// permet de choisir d'éteindre/allumer une lumière et
// ses voisines
// Il faut en outre detecter la fin de partie
});
button.addEventListener("click",play);
play();
+52
View File
@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<!-- Centered viewport -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css"
>
<link rel="stylesheet" href="style.css" type="text/css" media="screen" title="no title" charset="utf-8">
</head>
<body>
<main>
<h4>Lights</h4>
<p>Le but est d'éteindre l'ensemble des lumières.</p>
<p id="message"></p>
<article>
<div class="board">
<!--div class="row">
<span data-num="0"></span>
<span data-num="1"></span>
<span data-num="2"></span>
</div>
<div class="row">
<span data-num="3"></span>
<span data-num="4"></span>
<span data-num="5"></span>
</div>
<div class="row">
<span data-num="6"></span>
<span data-num="7"></span>
<span data-num="8"></span-->
</div>
</div>
</article>
<div role="group">
<select aria-label="Select size" required>
<option selected value="3">3x3</option>
<option value="2">2x2</option>
<option value="6">6x6</option>
<option value="7">7x7</option>
<option value="8">8x8</option>
<option value="10">10x10</option>
</select>
<input type="submit" value="New Game">
</div>
</main>
<script src="app.js"></script>
</body>
</html>
+50
View File
@@ -0,0 +1,50 @@
/*.grid {
display:grid;
grid-template-columns : 100px 100px 100px;
grid-template-rows: auto;
grid-gap: 10px;
}*/
.board > div {
display : flex;
}
.board span {
display: inline-block;
vertical-align:baseline;
width: 100px;
height: 100px;
/*padding : 1em;*/
margin : 0.1em;
cursor: pointer;
border: 1px solid #444;
border-radius: 50px;
background: radial-gradient(rgb(150,250,150), #00ee00);
}
.board span.off {
background: #009900;
}
.board span.off:hover {
background: #3AB903;
}
.board span:hover {
background: #3AB903;
background: radial-gradient(rgb(66, 255, 66), #065006);
}
/*
.wrapper {
display : flex;
justify-content: space-around;
}
.wrapper .grid {
flex-basis : auto;
}
*/
+172
View File
@@ -0,0 +1,172 @@
#### Ex3 : modele MVC
Le but est d'écrire une todolist en javascript, en respectant le pattern MVC. Il est important de mener à bout cet exercice, car il nous servira
de fil rouge notamment en ajoutant une api rest et ajax pour la communication avec le service.
<div align="center">
<img src="./img/todo.png">
</div>
Le contrôleur a accès à la vue et au modèle.
##### Le modèle
Cette "classe" (rien à compléter) utilise
l'objet [localStorage](https://developer.mozilla.org/fr/docs/Web/API/Window/localStorage)
pour sauvegarder la todolist.
Chaque todo est un objet json de la forme
```js
{ id : 1 , text : "apprendre le js", done : false }
```
La liste des méthodes publiques de cette classe
```js
/** @brief return the todolist
* @param filter "all" or "active" or "done"
* @return array of todos
*/
getTodos(filter)
/** @brief add (and save) a new todo with todoText
* @param todoText : text of the new todo
*/
add(todoText)
/** @brief delete a todo
* @param id id of the todo
*/
delete(id)
/** @brief update a todo
* @param id id of the todo
*/
edit(id,updatedText)
/** @brief toggle a todo (done<->active)
* @param id id of the todo
* @param updatedText text of the todo
*/
toggle(id)
```
##### La vue
Cette "classe" permet au contrôleur de gérer la vue.
Liste des méthodes publiques
```js
/** @brief change the active tab (all,active or done)
* @param filter the active tab (all, active or done)
*/
setFilterTabs(filter)
/** @brief update the todo list with todos
* @param todos array of todo
*/
renderTodoList(todos)
```
Le contrôleur peut s'abonner (notification de la vue au contrôleur)
aux événements suivants :
add/delete/edit/toggle todo : avec `bind{Add|Delete|Edit|Toggle}Todo`
Ces méthodes d'abonnement prennent en paramètre une fonction du contrôleur qui est appelé par la vue pour lui notifier
une requête.
```js
/** @brief subscribe to add event
* @param handler function(text)
*/
bindAddTodo(handler)
/** @brief suscribe to delete event
* @param handler function(id)
*/
bindDeleteTodo(handler)
/** @brief suscribe to edit event
* @param handler function(id,text)
*/
bindEditTodo(handler)
/** @brief suscribe to toggle event
* @param handler function(id)
*/
bindToggleTodo(handler)
```
##### Le contrôleur
C'est lui qui gére le routage. Il y a trois urls possibles `index.html/#/{all|active|done}` (listener sur l'évenement
dom `hashchange`.
Liste des méthodes
```js
/** @brief get the todolist from the model
* and use the view to render
*/
filterTodoList()
/** @brief binding function called by the view
* to add a new todo
* @param text text of the new todo
*/
addTodo(text)
/** @brief binding function called by the view
* to delete a todo
* @param id id of the todo
*/
deleteTodo(id)
/** @brief binding function to toggle the state
* of a todo
* @param id id of the todo
*/
toggleTodo(id)
/** @brief binding function to change the text
* of a todo
* @param id id of the todo
* @param changedText new text for the todo
*/
editTodo(id,changedText)
```
Par exemple, lorsque l'utilisateur ajoute une todo :
1. la vue est sensible à l'événement de soumission du formulaire,
2. la fonction reflexe récupére le texte saisie,
3. elle appelle la fonction avec laquelle le contrôleur s'est abonné, en lui passant le texte,
4. la fonction du contrôleur `addTodo` récupére le texte,
5. le contrôleur demande au modèle la création d'une nouvelle todo,
6. le contrôleur demande un rafraichissement de la vue.
#### Travail à faire
- Commpléter la méthode privée dans la vue `#createNewTodoElement` qui permet de créer un
nouvel élément dom représentant une todo. Ce qui est attendu est de la forme
```html
<li class="todo" data-id="1">
<label>
<input type="checkbox">
<span>apprendre un peu de javascript</span>
</label>
<i class="fas fa-trash"></i>
</li>
```
- Compléter la méthode `bindDeleteTodo` de la vue.
- Compléter la méthode `bindToggleTodo` de la vue.
- Compléter la méthode `bindEditTodo` de la vue.
#### Ex4
On veut maintenant utiliser l'oberserver Pattern. Quand le **modèle change**, il notifie automatiquement tous les observateurs.
> Le Modèle est un obervable.
> La Vue est un oberver.
- Ajouter dans le modèle un tableau d'obervateurs (callbacks), et une méthode notify.
- La vue devient un observeur.
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+63
View File
@@ -0,0 +1,63 @@
:focus-visible {outline:none;}
.is-active {
color : #8EB901;
}
#add_form > div {
display : flex;
justify-content:space-between;
}
.todo {
display : flex;
align-items: center;
}
.todo checkbox,.todo i {
flex-grow : 0;
flex-shrink : 0;
flex-basis : auto;
}
.todo span {
flex-grow : 1;
flex-shrink : 1;
flex-basis : auto;
}
.todolist {
padding-left: 0;
}
.todolist li {
list-style: none;
padding: var(--pico-form-element-spacing-vertical) 0;
display: flex;
justify-content: space-between;
align-items: center;
}
#todos_filter {
display:flex;
justify-content : center;
}
#todos_filter a {
margin:1em;
}
.todolist i {
cursor: pointer;
}
.todolist li:not(:last-child) {
border-bottom: 1.5px solid var(--pico-form-element-border-color);
}
.todolist > li > label:has(input:checked) {
text-decoration: line-through;
}
footer {
text-align: center;
}
+53
View File
@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Todo</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css"
>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css" integrity="sha512-q3eWabyZPc1XTCmF+8/LuE1ozpg5xxn7iO89yfSOd5/oKvyqLngoNGsx8jq92Y8eXJ/IRxQbEC+FGSYxtk2oiw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="./css/style.css">
</head>
<body>
<header>
<h1>To Do List</h1>
</header>
<main>
<article>
<header>
<form id='add_form'>
<fieldset role="group">
<input id="input_todo" type="text" placeholder="Buy milk and eggs...">
<button><i class="fas fa-plus"></i></button>
</fieldset>
<div>
<span>
<a id="active" href="#/active"><i class="far fa-circle"></i></a>
<a id="done" href="#/done"><i class="fas fa-circle"></i></a>
<a id="all" href="#/all"><i class="fas fa-adjust"></i></a>
</span>
<small><span id="count">0</span>/50 characters</small>
</div>
</form>
</header>
<!-- Render todos -->
<ul id="todos_list" class="todolist"></ul>
</article>
</main>
</body>
<script src="js/model.js"></script>
<script src="js/view.js"></script>
<script src="js/controller.js"></script>
</html>
+64
View File
@@ -0,0 +1,64 @@
class Controller {
constructor(model, view) {
this.model = model;
this.view = view;
this.filter = "all";
/** Abonnements à la vue **/
this.view.bindAddTodo(this.addTodo.bind(this));
this.view.bindDeleteTodo(this.deleteTodo.bind(this));
this.view.bindToggleTodo(this.toggleTodo.bind(this));
this.view.bindEditTodo(this.editTodo.bind(this));
/** filtrages par url **/
this.routes = ['all','active','done'];
/** Routage **/
window.addEventListener("load",this.routeChanged.bind(this));
window.addEventListener("hashchange",this.routeChanged.bind(this));
}
routeChanged(){
let route = window.location.hash.replace(/^#\//,'');
this.filter = this.routes.find( r => r === route) || 'all';
this.filterTodoList();
}
filterTodoList () {
let todos = this.model.getTodos(this.filter)
this.view.renderTodoList(todos)
this.view.setFilterTabs(this.filter)
}
addTodo(text) {
this.model.add(text)
this.filterTodoList()
}
deleteTodo(id) {
this.model.delete(parseInt(id))
this.filterTodoList()
}
toggleTodo(id) {
this.model.toggle(parseInt(id))
this.filterTodoList()
}
editTodo(id, text) {
this.model.edit(parseInt(id),text)
this.filterTodoList()
}
}
const model = new Model()
const view = new View()
const app = new Controller(model, view)
+48
View File
@@ -0,0 +1,48 @@
class Model {
constructor() {
this.todos = JSON.parse(localStorage.getItem('todos')) || []
}
#commit(todos) {
localStorage.setItem('todos', JSON.stringify(todos))
}
getTodos(filter){
if (filter === "active")
return this.todos.filter(todo => !todo.done)
if (filter === "done")
return this.todos.filter(todo => todo.done)
return this.todos
}
add(todoText) {
const todo = {
id: this.todos.length > 0 ? this.todos[this.todos.length - 1].id + 1 : 1,
text: todoText,
done : false,
}
this.todos.push(todo)
this.#commit(this.todos)
}
edit(id, updatedText) {
this.todos = this.todos.map(todo =>
todo.id === id ? { id: todo.id, text: updatedText, done: todo.done} : todo
)
this.#commit(this.todos)
}
delete(id) {
this.todos = this.todos.filter(todo => todo.id !== id)
this.#commit(this.todos)
}
toggle(id) {
this.todos = this.todos.map(todo =>
todo.id === id ? { id: todo.id, text: todo.text, done: !todo.done } : todo
)
this.#commit(this.todos)
}
}
+83
View File
@@ -0,0 +1,83 @@
class View {
charLimit = 70;
constructor() {
this.form = document.querySelector("#add_form")
this.input = document.querySelector("#input_todo")
this.list = document.querySelector("#todos_list")
this.tabs = document.querySelectorAll("#add_form span a")
this.loader = document.querySelector("#loader")
this.count = document.querySelector("#count")
this.input.addEventListener("input", (e) => {
if (e.target.value.length >= this.charLimit) {
e.target.value = e.target.value.substring(0,this.charLimit);
}
count.textContent = e.target.value.length;
});
count.nextSibling.textContent = `/${this.charLimit} characters`
}
#getTodo() {
return this.input.value
}
#resetInput() {
this.input.value = ''
count.textContent = 0
}
#createNewTodoElement(todo){
let li = document.createElement("li")
// TODO
return li
}
setFilterTabs(filter){
this.tabs.forEach( tab => {
if (filter === tab.id)
tab.classList.add("is-active")
else
tab.classList.remove("is-active")
})
}
renderTodoList(todos) {
let list = new DocumentFragment()
for (let todo of todos){
list.appendChild(this.#createNewTodoElement(todo))
}
this.list.replaceChildren(list)
}
/** Abonnements événements **/
bindAddTodo(handler) {
this.form.addEventListener("submit", (e=>{
e.preventDefault()
let text = this.#getTodo()
handler(text)
this.#resetInput()
}))
}
bindDeleteTodo(handler) {
// TODO
}
bindEditTodo(handler) {
// TODO
}
bindToggleTodo(handler) {
// TODO
}
}
+84
View File
@@ -0,0 +1,84 @@
#### Ex1 : modele MVC et pattern strategy
> Stratégie est un patron de conception comportemental qui permet de définir une
> famille dalgorithmes, de les mettre dans des classes séparées et de rendre
> leurs objets interchangeables. (source wikipédia)
Le but est de réaliser ce pattern de conception dans le jeu très simple du chifoumi.
<div align="center">
<img src="./img/chifoumi.png">
</div>
Pour l'instant, le jeu utilise le pattern MVC. Le modèle, qui calcule le coup de l'ordinateur utilise
un tirage aléatoire. On veut pouvoir utilisé d'autres méthodes, qui utilisent l'historique des coups
joués par le joueur.
1. Modifiez le modèle et la vue pour que le jeu affiche le pourcentage de victoires, matchs nuls et défaites du joueur depuis le début
de la partie.
On veut maintenant séparer la façon dont l'ordinateur choisit son coup de la logique du jeu. Le modèle utilisera
une stratégie donnée.
2. Créez un sous-répétoires `stratégies` dans lequel on stokera les différentes stratégies disponibles.
Les différentes stratégies réaliseront l'interface
```js
getChoice(playerHistory)
```
qui calcule le coup de l'ordinateur, en fonction de l'historique des différents coups du joueur.
3. Ecrivez 3 stratégies simples `RandomStrategy.js` (stratégie initiale), `CopyPlayerStrategy.js`
(joue le même coup que le joueur) `CounterMostUsedStrategy.js` (joue le coup le plus joué par le joueur) :
Chaque fichier implémente la stratégie correspondante sous la forme :
```js
export default class RandomStrategy {
getChoice(playerHistory) {
....
}
}
```
4. Mettez à jour le modèle pour qu'il puisse utiliser une stratégie donnée.
5. testez dans le main.
#### Ajout d'une stratégie
On va ajouter une stratégie markovienne d'ordre 1. On fait
l'hyptohése que le coup du joueur dépend de son coup précedent (ordre 1).
On met à jour au fur et à mesure la matrice de transitions entre
les coups joué par le joueur. Par exemple,
```js
{
rock: { rock: 2, paper: 5, scissors: 1 },
paper: { rock: 1, paper: 3, scissors: 4 },
scissors: { rock: 6, paper: 1, scissors: 2 }
}
```
signifie qu'après paper, le joueur a joué 1 fois rock, 3 fois paper, et 4 fois scissors.
On se sert de la matrice pour contrer la prédiction.
5. Écrivezz cette stratégie, et testez la.
#### Stratégie mixte
Le but est d'implanter une stratégie mixte. On se donne une probabilité `p`.
- la stratégie markovienne est jouée avec la probabilité `p`.
- la stratégie aléatoire est jouée avec la probabilité `1-p`
Écrivez la stratégie mixte, et testez.
#### Stratégie adaptative
La probabilité `p` dépend du score. On l'adapte au fur et à mesure. Si on gagne beaucoup (trop),
on joue au hasard, sinon avec Markov.
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

+41
View File
@@ -0,0 +1,41 @@
.game {
}
.choices{
display : flex;
align-items:stretch;
height:8em;
}
.choices > span:nth-child(2) {
margin-left:3em;
}
.choices span i{
font-size: 2em;
cursor: pointer;
border: none;
margin-right:0.25em;
transition: transform 0.2s, color 0.2s;
}
#player,#computer{
font-size: 3em;
border: none;
margin-right:0.25em;
}
.choices span i:hover {
color:#398712
}
#player {
color:#398712
}
#computer {
color:#D93526;
}
+40
View File
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Pierre Feuille Ciseaux</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css"
>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.4/css/all.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<main>
<h1>
Pierre - Feuille - Ciseaux
</h1>
<div class="choices">
<span>
<i data-choice="rock" class="far fa-hand-rock"></i>
<i data-choice="paper" class="far fa-hand-paper"></i>
<i data-choice="scissors" class="far fa-hand-scissors"></i>
</span>
<span>
<span id="player"></span>
<span id="computer"></span>
</span>
</div>
<div class="game">
<p id="score"></p>
<p id="result"></p>
</div>
</main>
<script type="module" src="js/main.js"></script>
</body>
</html>
+18
View File
@@ -0,0 +1,18 @@
export default class GameController {
constructor(model, view) {
this.model = model;
this.view = view;
this.view.bindPlay(this.handlePlay);
this.view.displayScore(this.model.getScore());
}
handlePlay = (playerChoice) => {
const computerChoice = this.model.getComputerChoice();
const result = this.model.getResult(playerChoice, computerChoice);
this.view.displayResult(playerChoice, computerChoice, result);
this.view.displayScore(this.model.getScore());
}
}
+9
View File
@@ -0,0 +1,9 @@
import GameModel from "./model.js";
import GameView from "./view.js";
import GameController from "./controller.js";
const app = new GameController(
new GameModel(),
new GameView()
);
+29
View File
@@ -0,0 +1,29 @@
export default class GameModel {
constructor() {
this.choices = ["rock", "paper", "scissors"];
this.score = { player: 0, computer: 0};
}
getComputerChoice() {
const index = Math.floor(Math.random() * this.choices.length);
return this.choices[index];
}
getResult(player, computer) {
if (player === computer) return "égalité";
if (
(player === "rock" && computer === "scissors") ||
(player === "paper" && computer === "rock") ||
(player === "scissors" && computer === "paper")
) {
this.score.player++;
return "gagné";
}
this.score.computer++;
return "perdu";
}
getScore() {
return this.score;
}
}
+50
View File
@@ -0,0 +1,50 @@
export default class GameView {
#icons = {
"rock" : "far fa-hand-rock",
"paper" : "far fa-hand-paper",
"scissors" : "far fa-hand-scissors"
}
constructor() {
this.resultEl = document.getElementById("result");
this.scoreEl = document.getElementById("score");
this.buttons = document.querySelectorAll(".choices span i");
this.player = document.getElementById("player");
this.computer = document.getElementById("computer");
}
bindPlay(handler) {
this.buttons.forEach(button => {
button.addEventListener("click", () => {
handler(button.dataset.choice);
});
});
}
displayResult(player, computer, result) {
let i = document.createElement("i");
i.classList.add(...this.#icons[player].split(' '))
this.player.replaceChildren(i)
i = document.createElement("i");
i.classList.add(...this.#icons[computer].split(' '))
this.computer.replaceChildren(i)
this.resultEl.innerHTML =
`Joueur <i class="${this.#icons[player]}"></i>, ordinateur <i class="${this.#icons[computer]}"></i> → ${result}`;
}
displayScore(score) {
let winRate = ((score.player * 100 / score.count) || 0).toFixed(2)
let lossRate = ((score.computer * 100 / score.count) ||0).toFixed(2)
let drawRate = (100 - winRate - lossRate).toFixed(2)
this.scoreEl.textContent =
`Score — Joueur: ${score.player} | Ordinateur: ${score.computer}`;
}
}
Submodule
+1
Submodule DEV.4.2 added at 5573c92520