Mine d'or Idle
Les jeux "Idle" (incrémentaux) ou "clicker" sont devenus extrêmement populaires vers 2013, nous sommes donc un peu en retard, mais nous pouvons toujours nous amuser à en créer un.
Le concept de ces jeux est d'offrir une récompense au joueur pour une action simple (comme un clic). Certaines personnes apprécient ces jeux, d'autres préfèrent ne pas perdre leur temps précieux. Je ne juge pas.
Dans notre cas, le joueur gère une mine d'or et sa récompense pour avoir cliqué sera un nouvel ouvrier de style voxel. Cette "récompense" vous coûtera 5 $.
Chaque ouvrier creuse inlassablement des pépites d'or et les transporte vers des coffres, où un ascenseur automatisé les récupère et les emmène vers un autre coffre à la surface.
De là, une autre équipe transporte les pépites vers l'entrepôt et le joueur reçoit 1 $ pour chacune d'elles (apparemment, le marché de l'or n'est pas très porteur en ce moment).
Pour seulement 20 $, vous pouvez ouvrir un nouveau puits.
Le jeu, comme l'avidité humaine, ne finit jamais, mais il lasse assez vite : vous ouvrez tous les puits et les remplissez d'ouvriers. À ce stade, vous pouvez soit regarder passivement votre richesse croître, soit consulter l'un des tutoriels ci-dessous.
Voici comment cela fonctionne (code complet ci-dessous) :
[1-7] Configuration du canvas HTML5 et de sa police.
[8-27] Images
[29-30] Dimensions des puits
[32] 5 $ pour embaucher un nouvel ouvrier
[33] maximum 3 ouvriers par puits
[34] 20 $ pour ouvrir un nouveau puits (quelle affaire !)
[35] 10 puits maximum - c'est ce qui peut tenir sur notre canvas
[36-37] dimensions des images
[38] chaque séquence d'animation comporte 20 images
[39] le ratio d'étirement horizontal et vertical du canvas pour s'adapter à l'écran de l'appareil
[40] le score est votre argent actuel
[41] initialisation du tableau des puits
[42] l'image (frame) d'animation actuelle (commence à 0)
[43-81] l'objet ascenseur (lift) :
[44] vitesse initiale
[45-59] Quand l'ascenseur arrive à un puits :
[47-52] s'il est sous terre, déplace l'or (si présent) du coffre vers l'ascenseur, vide le coffre du puits, lance le compteur de chargement
[54-58] sinon, déplace l'or de l'ascenseur vers le coffre du puits
[60-67] Déplacement de l'ascenseur :
[61] changement de la coordonnée y
[63-64] si l'ascenseur arrive à un puits, exécute la fonction correspondante
[65-66] s'il atteint le haut ou le bas, inverse la vitesse
[68-80] Dessiner et mettre à jour l'ascenseur :
[69] Dessine le bitmap
[70] Affiche le contenu du coffre
[71-75] Si le compteur de chargement est actif, dessine la flèche dans la direction actuelle
[76] et diminue le compteur
[77-78] sinon, déplace l'ascenseur.
[83-100] Le puits (Shaft)
[84] commence sans aucun ouvrier
[85] et un coffre vide
[86] fermé par défaut (nous l'ouvrirons plus tard)
[87-93] Dessiner le puits :
[88-92] S'il y a de la place pour plus d'ouvriers, affiche le bouton "embaucher ouvrier" (actif ou inactif, selon que vous avez assez d'argent)
[94-98] Ouverture d'un nouveau puits :
[95] passe à l'état "ouvert"
[96] augmente le compteur de puits ouverts
[97] embauche automatiquement le premier ouvrier
[98] et paye pour lui.
[102-154] Les ouvriers (Workers)
[103] commencent aléatoirement quelque part près de l'ascenseur
[104] tournés vers la droite
[107-153] Logique de l'ouvrier :
[110-117] S'il est en train de creuser :
[111] Augmente le compteur de creusage
[112] utilise la 5ème rangée de la feuille de sprites (spritesheet) :

[113] S'il a creusé pendant 100 images, termine le creusage et va vers l'ascenseur.
[118-130] S'il va vers la droite :
[119] utilise la 1ère rangée (haut) de la feuille de sprites
[120] se déplace vers la droite
[121] s'il est proche du côté droit, commence à creuser ou (si à la surface) dépose la charge dans l'entrepôt et retourne vers l'ascenseur
[131-146] S'il va vers la gauche :
[132] Utilise la 2ème rangée de la feuille de sprites
[133] se déplace vers la gauche
[134-144] s'il est proche du coffre :
[135] fait demi-tour
[136-7] ajoute l'or au coffre s'il est sous terre
[138-143] s'il y a de l'or dans le coffre, s'en saisit
[147-148] Si à la surface, descend de deux rangées dans la feuille de sprites (pour inverser la direction à laquelle il fait face). À la surface, il porte la pépite en allant à gauche et a les mains vides en allant à droite. Sous terre, c'est l'inverse.
[149-150] Si à la surface et les mains vides, utilise la 1ère rangée.
[152] dessine l'image depuis la feuille de sprites.
[156-169] Dessiner l'écran :
[157] dessine l'image d'arrière-plan pour les puits ouverts
[158] affiche le score
[159-162] dessine les puits ouverts
[165-168] dessine le bouton "nouveau puits" actif/inactif
[171-183] Boucle d'animation principale
[173-177] met à jour chaque ouvrier
[179-181] Augmente le compteur d'images et le réinitialise si nécessaire.
[182] Attend la prochaine image d'animation.
[185-203] Valeurs initiales pour les variables.
[205-214] En cas de redimensionnement de la fenêtre (ex: rotation de l'appareil mobile), change les dimensions de style du canvas pour remplir tout l'écran et calcule les aspects x/y mis à jour.
[216-230] Gestion du clic
[217-218] recalcule les coordonnées en tenant compte des aspects
[219] Quel bouton de puits a été cliqué ?
[221-224] si un bouton "embaucher ouvrier" a été cliqué, ajoute un ouvrier et paye pour lui
[225-227] sinon, ouvre un nouveau puits
[232-242] Au premier chargement de la fenêtre :
[233-238] crée les puits et définit le nombre maximum d'ouvriers
[235-236] la surface peut avoir 5 fois plus d'ouvriers que les puits.
| <html> |
| <body> |
| <canvas id='canvas' width='600' height='600' style='position:absolute; left:0; top:0'></canvas> |
| <script> |
| const canvas = document.getElementById('canvas'); |
| const context = canvas.getContext('2d'); |
| context.font = 'bold 16px sans-serif'; |
| const backgroundImg = new Image(); |
| backgroundImg.src = 'background.png'; |
| const liftImg = new Image(); |
| liftImg.src = 'lift.png'; |
| const arrowLeftImg = new Image(); |
| arrowLeftImg.src = 'arrow.png'; |
| const arrowRightImg = new Image(); |
| arrowRightImg.src = 'arrow_right.png'; |
| const spritesheetImg = new Image(); |
| spritesheetImg.src = 'spritesheet.png'; |
| const closeImg = new Image(); |
| closeImg.src = 'close.png'; |
| const workerActiveButtonsImg = new Image(); |
| workerActiveButtonsImg.src = 'worker_active.png'; |
| const workerInactiveButtonsImg = new Image(); |
| workerInactiveButtonsImg.src = 'worker_inactive.png'; |
| const shaftActiveButtonsImg = new Image(); |
| shaftActiveButtonsImg.src = 'shaft_active.png'; |
| const shaftInactiveButtonsImg = new Image(); |
| shaftInactiveButtonsImg.src = 'shaft_inactive.png'; |
| |
| const shaftHeight = 60, |
| shaftWidth = 450, |
| liftWidth = 40, |
| workerCost = 5, |
| max_workers = 3, |
| shaftCost = 20, |
| max_shafts = 10, |
| buttonWidth = 150, |
| spriteSize = 60, |
| max_frames = 19; |
| let aspectX, aspectY; |
| let score, shaftsOpen; |
| let shafts = []; |
| let frame = 0; |
| let lift = { |
| speed: .5, |
| arrivedAtShaft: function(shaftNumber) { |
| let shaft = shafts[shaftNumber]; |
| if (shaftNumber > 0) { |
| if (shaft.chest > 0) { |
| this.chest = this.chest + shaft.chest; |
| shaft.chest = 0; |
| this.loadingCounter = 100; |
| } |
| } else { |
| if (this.chest > 0) |
| this.loadingCounter = 100; |
| shaft.chest = shaft.chest + this.chest; |
| this.chest = 0; |
| } |
| }, |
| move: function() { |
| this.y = this.y + this.speed; |
| let currentShaft = this.y / shaftHeight; |
| if (currentShaft == Math.floor(currentShaft)) |
| this.arrivedAtShaft(currentShaft); |
| if (this.y >= (shaftsOpen - 1) * shaftHeight || this.y <= 0) |
| this.speed = -this.speed; |
| }, |
| update: function() { |
| context.drawImage(liftImg, 0, this.y); |
| context.fillText(this.chest, 10, this.y + 25); |
| if (this.loadingCounter) { |
| if (this.y > 0) |
| context.drawImage(arrowLeftImg, liftWidth * .5, this.y); |
| else |
| context.drawImage(arrowRightImg, liftWidth * .5, this.y); |
| this.loadingCounter--; |
| } else { |
| this.move(); |
| } |
| }, |
| }; |
| |
| function Shaft() { |
| this.workers = []; |
| this.chest = 0; |
| this.closed = true; |
| this.draw = function(shaftNumber) { |
| if (shafts[shaftNumber].workers.length < shafts[shaftNumber].max_workers) |
| if (score >= workerCost) |
| context.drawImage(workerActiveButtonsImg, shaftWidth, shaftHeight * shaftNumber); |
| else |
| context.drawImage(workerInactiveButtonsImg, shaftWidth, shaftHeight * shaftNumber); |
| }; |
| this.open = function() { |
| this.closed = false; |
| shaftsOpen++; |
| this.workers.push(new Worker()); |
| score = score - shaftCost; |
| }; |
| } |
| |
| function Worker() { |
| this.x = Math.floor(Math.random() * liftWidth); |
| this.mode = 'goRight'; |
| this.counter = 0; |
| this.load = 0; |
| this.update = function(shaftNumber) { |
| let row; |
| switch (this.mode) { |
| case 'dig': |
| this.counter++; |
| row = 4; |
| if (this.counter > 100) { |
| this.counter = 0; |
| this.mode = 'goLeft'; |
| } |
| break; |
| case 'goRight': |
| row = 0; |
| this.x++; |
| if (this.x >= shaftWidth - spriteSize * 1.5 + Math.random() * spriteSize) { |
| if (shaftNumber > 0) |
| this.mode = 'dig'; |
| else { |
| score = score + this.load; |
| this.load = 0; |
| this.mode = 'goLeft'; |
| } |
| } |
| break; |
| case 'goLeft': |
| row = 1; |
| this.x--; |
| if (this.x <= 2 * liftWidth) { |
| this.mode = 'goRight'; |
| if (shaftNumber > 0) { |
| shafts[shaftNumber].chest = shafts[shaftNumber].chest + 1; |
| } else { |
| if (shafts[0].chest > 0) { |
| this.load = 1; |
| shafts[0].chest = shafts[0].chest - 1; |
| } |
| } |
| } |
| break; |
| } |
| if (shaftNumber == 0) { |
| row = row + 2; |
| if (this.load == 0 && this.mode == 'goRight') |
| row = 0; |
| } |
| context.drawImage(spritesheetImg, frame * spriteSize, row * spriteSize, spriteSize, spriteSize, this.x, shaftNumber * shaftHeight, spriteSize, spriteSize); |
| }; |
| } |
| |
| function drawScreen() { |
| context.drawImage(backgroundImg, 0, 0, shaftWidth + buttonWidth, shaftHeight * shaftsOpen, 0, 0, shaftWidth + buttonWidth, shaftHeight * shaftsOpen); |
| context.fillText(score, 300, 15); |
| shafts.forEach((shaft, shaftNumber) => { |
| if (!shaft.closed) { |
| context.fillText(shaft.chest, liftWidth + 15, shaftNumber * shaftHeight + 25); |
| shaft.draw(shaftNumber); |
| } |
| }); |
| if (score >= shaftCost) |
| context.drawImage(shaftActiveButtonsImg, shaftWidth, shaftHeight * (shaftsOpen)); |
| else |
| context.drawImage(shaftInactiveButtonsImg, shaftWidth, shaftHeight * (shaftsOpen)); |
| } |
| |
| function animate() { |
| drawScreen(); |
| shafts.forEach((shaft, shaftNumber) => { |
| shaft.workers.forEach((worker) => { |
| worker.update(shaftNumber); |
| }); |
| }); |
| lift.update(); |
| frame++; |
| if (frame > max_frames) |
| frame = 0; |
| window.requestAnimationFrame(animate); |
| } |
| |
| function reset() { |
| shaftsOpen = 0; |
| score = 70; |
| lift.y = 0; |
| lift.chest = 0; |
| lift.loadingCounter = 0; |
| shafts.forEach((shaft) => { |
| shaft.closed = true; |
| shaft.workers = []; |
| }); |
| shafts[0].open(); |
| shafts[1].open(); |
| for (let i = shaftsOpen; i < max_shafts; i++) { |
| shafts[i].closed = true; |
| shafts[i].workers = []; |
| context.drawImage(closeImg, 0, i * shaftHeight); |
| } |
| animate(); |
| } |
| |
| function resize() { |
| let picSizeX = window.innerWidth; |
| let picSizeY = window.innerHeight; |
| canvas.style.width = window.innerWidth; |
| canvas.style.height = window.innerHeight; |
| aspectX = picSizeX / 600; |
| aspectY = picSizeY / 600; |
| } |
| |
| window.onresize = resize; |
| |
| window.onpointerdown = function(event) { |
| let x = event.offsetX / aspectX; |
| let y = event.offsetY / aspectY; |
| let shaftNumber = Math.floor(y / shaftHeight); |
| let shaft = shafts[shaftNumber]; |
| if (x > shaftWidth) |
| if (shaftNumber < shaftsOpen && score >= workerCost && shaft.workers.length <= shaft.max_workers) { |
| shaft.workers.push(new Worker()); |
| score = score - workerCost; |
| } else { |
| if (shaftNumber == shaftsOpen && score >= shaftCost) { |
| shaft.open(); |
| } |
| } |
| }; |
| |
| window.onload = function() { |
| for (let i = 0; i < max_shafts; i++) { |
| shafts[i] = new Shaft(); |
| if (i == 0) |
| shafts[i].max_workers = max_workers * 5; |
| else |
| shafts[i].max_workers = max_workers; |
| } |
| reset(); |
| resize(); |
| }; |
| </script> |
| </body> |
| </html> |