Tic Tac Toe (PixiJS)

image

Un typographe vulnérable, stupide et rêveur a décidé de devenir programmeur et rien n'en est sorti ... Mais il n'a pas abandonné la programmation et a décidé de commencer par de petits programmes ...

C'est la meilleure description que j'ai pu trouver. C'est dans ce but que j'ai commencé à écrire des programmes simples pour perfectionner mes compétences, me familiariser avec de nouveaux designs dans ma langue habituelle, et pour être honnête, cela a même commencé à me faire plaisir.

Si vous avez peu d'expérience en développement, alors l'article sera utile, et si vous avez déjà une expérience en développement, passez du temps sur quelque chose de plus intéressant.

Ce n'est pas de la formation. Plus comme un blog.

Le but était de faire 3 versions du jeu tic tac toe.

1 - Le plus simple ( sans un beau visuel, en utilisant le DOM )
2 - Donner l'occasion de jouer ensemble (un ordinateur )
3 - Transférer tout cela sur toile

Je ne décrirai pas le tic-tac-toe, j'espère que tout le monde connaît le principe du jeu. Tous les liens utiles (référentiel, documentation) seront à la fin de l'article.

Qu'est-il arrivé? Hm ...

Première version


image

C'est le plus simple. Pour être honnête, les versions suivantes ne sont pas difficiles ...

Nous avons besoin d'une disposition du conteneur dans laquelle nous devrons placer notre terrain de jeu. J'ai ajouté un élément de données à chaque élément depuis J'ai pensé que j'aurais besoin d'un identifiant, mais je ne l'ai pas utilisé.

<div class="app">
	<div class="app_block" data-item="0"></div>
	<div class="app_block" data-item="1"></div>
	<div class="app_block" data-item="2"></div>
	<div class="app_block" data-item="3"></div>
	<div class="app_block" data-item="4"></div>
	<div class="app_block" data-item="5"></div>
	<div class="app_block" data-item="6"></div>
	<div class="app_block" data-item="7"></div>
	<div class="app_block" data-item="8"></div>
</div>

Je veux vous avertir tout de suite! Ce code ne doit pas être considéré comme le seul vrai et écrire autrement est considéré comme une erreur. Ceci est ma solution et rien de plus.

Donc. Tout d'abord, nous devons lier un clic sur la cellule. Pendant le clic, nous allons (le bot aussi, mais à leur tour) et vérifions la cellule.

var items = document.getElementsByClassName("app_block"); //  
var movePlayer = true; //  
var game = true;//  

//         .
for (var i = 0; i < items.length; i++) {
	items[i].addEventListener("click", function() {
		var collecion = document.querySelectorAll(".app_block:not(.active)");

		//   
		if(collecion.length == 1) {
			exit({win: "other"});
		}

		//     
		if( !this.classList.contains("active") ){

			//   
			if( movePlayer) {

				//   
				if(this.innerHTML == "") {
					//  
					this.classList.add("active");
					this.classList.add("active_x");
					this.innerHTML = "x"
				}
				//    
				var result = checkMap();
				if( result.val) {
					game = false;
					setTimeout(function() {
						exit(result);
					}, 10);
				}

				movePlayer = !movePlayer;
			}
			
			//    ,   
			if(game) {
				setTimeout(function() {
					botMove();
				}, 200);
			}
		}
	});
}

Le bot marche au hasard.

function botMove() {
	var items = document.querySelectorAll(".app_block:not(.active)");

	var step = getRandomInt(items.length);

	items[ step ].innerHTML = "0";
	items[ step ].classList.add("active");
	items[ step ].classList.add("active_o");

	var result = checkMap();
	if( result.val) {
		setTimeout(function() {
			exit(result);
		}, 1);
	}

	movePlayer = !movePlayer;
}

function getRandomInt(max) {
	return Math.floor(Math.random() * Math.floor(max));
}

Vérification de cellule

function checkMap() {
	var block = document.querySelectorAll(".app_block");
	var items = [];
	for (var i = 0; i < block.length; i++) { 
		items.push(block[i].innerHTML);
	}

	if ( items[0] == "x" && items[1] == 'x' && items[2] == 'x' ||
		 items[3] == "x" && items[4] == 'x' && items[5] == 'x' ||
		 items[6] == "x" && items[7] == 'x' && items[8] == 'x' ||
		 items[0] == "x" && items[3] == 'x' && items[6] == 'x' ||
		 items[1] == "x" && items[4] == 'x' && items[7] == 'x' ||
		 items[2] == "x" && items[5] == 'x' && items[8] == 'x' ||
		 items[0] == "x" && items[4] == 'x' && items[8] == 'x' ||
		 items[6] == "x" && items[4] == 'x' && items[2] == 'x' )
		return { val: true, win: "player"}
	if ( items[0] == "0" && items[1] == '0' && items[2] == '0' ||
		 items[3] == "0" && items[4] == '0' && items[5] == '0' ||
		 items[6] == "0" && items[7] == '0' && items[8] == '0' ||
		 items[0] == "0" && items[3] == '0' && items[6] == '0' ||
		 items[1] == "0" && items[4] == '0' && items[7] == '0' ||
		 items[2] == "0" && items[5] == '0' && items[8] == '0' ||
		 items[0] == "0" && items[4] == '0' && items[8] == '0' ||
		 items[6] == "0" && items[4] == '0' && items[2] == '0' )
		return { val: true, win: "bot"}

	return {val: false}
}

Ici, vous pouvez tout écrire à travers des boucles. J'ai choisi une manière plus simple. Mon domaine est toujours statique. Par conséquent, une simple vérification des cellules. Il est à noter que je retourne l'objet afin de vérifier dans le futur qui a gagné. Dans l'objet, valez et gagnez les propriétés. Val est responsable de la fin du jeu.

Fin du jeu.

// /
function exit(obj) {
	alert(obj.win + " - game over");
	location.reload();
};

Pendant le clic, nous vérifions si checkMap a renvoyé val: true. Si oui, terminez le jeu.

Deuxième version


Deux joueurs sur le même ordinateur.

J'ai pris une partie de la logique du gestionnaire de clics dans une fonction distincte et transmis le contexte d'appel à la fonction, car nous devons déterminer le bouton sur lequel vous avez cliqué.

var items = document.getElementsByClassName("app_block");
var movePlayer = true;
var game = true;

for (var i = 0; i < items.length; i++) {
	items[i].addEventListener("click", function() {
		var collecion = document.querySelectorAll(".app_block:not(.active)");
		if(collecion.length == 1) {
			exit({win: "other"});
		}

		if( !this.classList.contains("active") ){
			if( movePlayer) {
				firstPlayer(this);
			} else {
				secondPlayer(this);
			}
		}
	});
}


J'ai divisé en deux fonctions, mais elles ont une duplication de code. Idéalement, divisez par 3. Un principal et deux travaillant avec le contexte.

function firstPlayer(that) {
	if(that.innerHTML == "") {
		that.classList.add("active");
		that.classList.add("active_x");
		that.innerHTML = "x"
	}

	var result = checkMap();
	if( result.val) {
		game = false;
		setTimeout(function() {
			exit(result);
		}, 10);
	}

	movePlayer = !movePlayer;
}

function secondPlayer(that) {
	if(that.innerHTML == "") {
		that.classList.add("active");
		that.classList.add("active_o");
		that.innerHTML = "0"
	}

	var result = checkMap();
	if( result.val) {
		game = false;
		setTimeout(function() {
			exit(result);
		}, 10);
	}

	movePlayer = !movePlayer;
}

Troisième version


C'est peut-être le point le plus intéressant car Maintenant, le jeu ressemble vraiment à un jeu, pas à l'interaction des éléments DOM.

J'ai choisi de travailler PixiJS. Je ne peux rien dire sur les + et - de cette bibliothèque, mais j'ai regardé un exemple dans lequel il y avait 60 000 éléments et ils étaient tous animés. L'animation est simple, mais le FPS est resté à 50-60. J'ai bien aimé et j'ai commencé à lire la documentation. Je dois dire tout de suite que ma connaissance de la langue anglaise est minime, c'était difficile, mais il y a très peu d'articles en russe (ou j'ai mal regardé). J'ai dû fouiller et avec l'aide d'un traducteur google pour traverser les épines.

Je n'ai regardé qu'un seul reportage sur ce sujet par Yulia Pucnina "Fat animation with Pixi js" .

Le rapport date de 2014 et vous devez comprendre que l'API pourrait changer. Un œil sur la documentation et le second sur la vidéo. J'ai donc étudié. Il a fallu 4 heures pour écrire un prototype aussi simple. Plus proche du code.

Nous faisons l'initialisation par défaut pixi

const app = new PIXI.Application({
	width: 720,
	height: 390,
	resolution: window.devicePixelRation || 1,
});
document.body.appendChild(app.view);

et également créer un emballage (le conteneur principal avec des cellules) et le mettre dans notre toile

let wrapper = new PIXI.Container();
app.stage.addChild(wrapper);

Dans la boucle, nous créons nos cellules, leur définissons les tailles et coordonnées nécessaires et ajoutons également la valeur par défaut à la cellule sous forme de chaîne vide, car cela sera utile à l'avenir et suspendra les gestionnaires sur les cellules, après avoir activé l'indicateur d'interactivité du conteneur.

for (let i = 0; i < 9; i++) {
    let container = new PIXI.Container();
	let block = new PIXI.TilingSprite( PIXI.Texture.from("images/bg.png") , 240, 130);
    
	container.x = (i % 3) * 240;
    container.y = Math.floor(i / 3) * 130;
    container.addChild(block);
    
    let text = new PIXI.Text("");
    text.anchor.set(0.5);
    text.x = container.width / 2;
    text.y = container.height / 2;
    container.addChild(text);
    
    container.interactive = true;    
    container.on("mousedown", function () {
        addValueInBlock(this);
    });
    
    wrapper.addChild(container);
}

addValueInBlock est responsable de la progression de chaque joueur. Je n'ai pas trouvé de meilleur moyen que de déclarer mes propres styles pour chaque texte. Là, la couleur change, mais je n'ai pas compris comment changer la couleur. Chaque fois, de nouveaux styles doivent être attribués au texte. Il y a aussi une vérification des cellules.

function addValueInBlock(that) {
    if(firstPlayer) {
        //    - X
        if( that.children[1].text == " " ) {
            that.children[1].style = {
                fill: "#d64c42",
                fontFamily: "Arial",
                fontSize: 32,
                fontWeight: "bold",
            };
            that.children[1].text = "x"
            
            firstPlayer = !firstPlayer;
        }
        
    } else {
        //    - 0
        
        if( that.children[1].text == " " ) {
            that.children[1].style = {
                fill: "#e2e3e8",
                fontFamily: "Arial",
                fontSize: 32,
                fontWeight: "bold",
            };
            that.children[1].text = "0"
            
             firstPlayer = !firstPlayer;
        }
    }
    endGame();
}

Concernant la vérification elle-même. checkMap. Si je comprends bien, pixiJS ne peut pas accéder à un élément par son nom ou son identifiant. Nous devons trier toute la collection dans le conteneur à cause de cela, le code semble lourd. La fonction n'est pas différente des précédentes, à l'exception des paramètres qu'elle renvoie.

function checkMap() {
    let items = wrapper.children;
    
	if ( items[0].children[1].text == "x" && items[1].children[1].text == 'x' && items[2].children[1].text == 'x' ||
		 items[3].children[1].text == "x" && items[4].children[1].text == 'x' && items[5].children[1].text == 'x' ||
		 items[6].children[1].text == "x" && items[7].children[1].text == 'x' && items[8].children[1].text == 'x' ||
		 items[0].children[1].text == "x" && items[3].children[1].text == 'x' && items[6].children[1].text == 'x' ||
		 items[1].children[1].text == "x" && items[4].children[1].text == 'x' && items[7].children[1].text == 'x' ||
		 items[2].children[1].text == "x" && items[5].children[1].text == 'x' && items[8].children[1].text == 'x' ||
		 items[0].children[1].text == "x" && items[4].children[1].text == 'x' && items[8].children[1].text == 'x' ||
		 items[6].children[1].text == "x" && items[4].children[1].text == 'x' && items[2].children[1].text == 'x' ) {
        return {active: true, win: "player 1"};
    }
		
	if ( items[0].children[1].text == "0" && items[1].children[1].text == '0' && items[2].children[1].text == '0' ||
		 items[3].children[1].text == "0" && items[4].children[1].text == '0' && items[5].children[1].text == '0' ||
		 items[6].children[1].text == "0" && items[7].children[1].text == '0' && items[8].children[1].text == '0' ||
		 items[0].children[1].text == "0" && items[3].children[1].text == '0' && items[6].children[1].text == '0' ||
		 items[1].children[1].text == "0" && items[4].children[1].text == '0' && items[7].children[1].text == '0' ||
		 items[2].children[1].text == "0" && items[5].children[1].text == '0' && items[8].children[1].text == '0' ||
		 items[0].children[1].text == "0" && items[4].children[1].text == '0' && items[8].children[1].text == '0' ||
		 items[6].children[1].text == "0" && items[4].children[1].text == '0' && items[2].children[1].text == '0' ) {
        return {active: true, win: "player 2"};
    }
    
	return {active: false};
}

Eh bien, les deux dernières fonctions sont responsables de la fin du jeu et du nettoyage de la toile. Il me semble que l'explication ici est superflue.

function endGame() {
    var result = checkMap();
    console.log(result);
    if( result.active ) {
        setTimeout(function() {
            alert(result.win + " - win");
            clearMap();
        }, 100);
    }
}

function clearMap() {
    console.log("sdf");
    let items = wrapper.children;
    
    for(let i = 0; i < items.length; i++) {
        console.log( items[i].children[1].text );
        items[i].children[1].text = "";
        firstPlayer = true;
    }
}

Pour résumer, il était intéressant de réaliser le développement en plusieurs étapes. Ce n'est pas un cycle de développement idéal, mais avec ce dont j'avais besoin pour commencer.

Merci d'avoir lu et à bientôt.

Références



Rapport Github
de. Site Web PixiJS

All Articles