Tic Tac Toe (PixiJS)

imagen

Un tip贸grafo vulnerable, est煤pido y so帽ador decidi贸 convertirse en programador y no sali贸 nada de eso ... Pero no abandon贸 la programaci贸n y decidi贸 comenzar con peque帽os programas ...

Esta es la mejor descripci贸n que se me ocurri贸. Fue para este prop贸sito que comenc茅 a escribir programas simples para perfeccionar mis habilidades, conocer nuevos dise帽os en mi idioma habitual y, para ser honesto, incluso comenz贸 a darme placer.

Si tiene poca experiencia en desarrollo, entonces el art铆culo ser谩 煤til, y si ya tiene experiencia en desarrollo, dedique tiempo a algo m谩s valioso.

Esto no es entrenamiento. M谩s como un blog.

El objetivo era hacer 3 versiones del juego tic tac toe.

1 - El m谩s simple ( sin una hermosa imagen, usando el DOM )
2 - Da la oportunidad de jugar juntos (una computadora )
3 - Transfiera todo esto al lienzo

No describir茅 el tic-tac-toe, espero que todos conozcan el principio del juego. Todos los enlaces 煤tiles (repositorio, documentaci贸n) estar谩n al final del art铆culo.

驴Qu茅 sali贸 de esto? Hm ...

Primera versi贸n


imagen

Este es el m谩s simple. Para ser sincero, las versiones posteriores no son dif铆ciles ...

Necesitamos un dise帽o del contenedor en el que tendremos que colocar nuestro campo de juego. Agregu茅 un elemento de datos a cada elemento desde Pens茅 que necesitar铆a un identificador, pero no lo us茅.

<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>

隆Quiero advertirte de inmediato! Este c贸digo no debe considerarse como el 煤nico verdadero y escribir de lo contrario se considera un error. Esta es mi soluci贸n y nada m谩s.

Entonces. Primero, debemos vincular un clic en la celda. Durante el clic vamos (el bot tambi茅n, pero a su vez) y verificamos la celda.

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);
			}
		}
	});
}

El bot camina al azar.

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));
}

Comprobaci贸n de celda

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}
}

Aqu铆 puedes escribir todo a trav茅s de bucles. Eleg铆 una forma m谩s simple. Mi campo siempre es est谩tico. Por lo tanto, una simple verificaci贸n de las celdas. Vale la pena se帽alar que devuelvo el objeto para verificar en el futuro qui茅n gan贸. En el objeto, val y ganar propiedades. Val es responsable de terminar el juego.

Fin del juego.

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

Durante el clic, verificamos si checkMap devolvi贸 val: true. Si es as铆, completa el juego.

Segunda versi贸n


Dos jugadores en la misma computadora.

Tom茅 parte de la l贸gica del controlador de clics en una funci贸n separada y pas茅 el contexto de la llamada a la funci贸n, porque necesitamos determinar en qu茅 bot贸n se hizo clic.

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);
			}
		}
	});
}


Me divid铆 en dos funciones, pero tienen duplicaci贸n de c贸digo. Idealmente, divida por 3. Uno principal y dos trabajando con contexto.

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;
}

Tercera versi贸n


Quiz谩s este es el punto m谩s interesante porque Ahora el juego realmente parece un juego, no la interacci贸n de elementos DOM.

Eleg铆 trabajar PixiJS. No puedo decir nada sobre el + y - de esta biblioteca, pero mir茅 un ejemplo en el que hab铆a 60,000 elementos y todos estaban animados. La animaci贸n es simple, pero el FPS se mantuvo en 50-60. Me gust贸 y comenc茅 a leer la documentaci贸n. Debo decir de inmediato que mi conocimiento del idioma ingl茅s es m铆nimo, fue dif铆cil, pero hay muy pocos art铆culos en ruso (o me he visto mal). Tuve que empujar y con la ayuda de un traductor de google para atravesar las espinas.

Solo vi un informe sobre este tema de Yulia Pucnina "Animaci贸n gorda con Pixi js" .

El informe es de 2014 y debe comprender que la API podr铆a cambiar. Un ojo en la documentaci贸n y el segundo en el video. Entonces estudi茅. Me llev贸 4 horas escribir un prototipo tan simple. M谩s cerca del c贸digo.

Hacemos la inicializaci贸n predeterminada pixi

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

y tambi茅n cree un contenedor (el contenedor principal con celdas) y p贸ngalo en nuestro lienzo

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

En el bucle, creamos nuestras celdas, les establecemos los tama帽os, las coordenadas necesarias y tambi茅n agregamos el valor predeterminado a la celda como una cadena vac铆a desde Esto ser谩 煤til en el futuro y colgar谩 los controladores en las celdas, despu茅s de habilitar el indicador de interactividad del contenedor.

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 es responsable del progreso de cada jugador. No encontr茅 una mejor manera que declarar mis propios estilos para cada texto. All铆 el color cambia, pero no entend铆 c贸mo cambiar el color. Cada vez se deben asignar nuevos estilos al texto. Tambi茅n hay un control de celdas.

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();
}

En cuanto a la verificaci贸n en s铆. checkMap. Seg煤n tengo entendido, pixiJS no puede acceder a un elemento por nombre o id. Tenemos que clasificar toda la colecci贸n en el contenedor debido a esto, el c贸digo parece engorroso. La funci贸n no es diferente de las anteriores, excepto por los par谩metros que devuelve.

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};
}

Bueno, las dos 煤ltimas funciones son responsables de terminar el juego y limpiar el lienzo. Me parece que la explicaci贸n aqu铆 es superflua.

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;
    }
}

En resumen, fue interesante llevar a cabo el desarrollo en varias etapas. No es un ciclo de desarrollo ideal, sino con lo que necesitaba para comenzar.

Gracias por leer y hasta pronto.

Referencias



Informe de Github
de. Sitio web de PixiJS

All Articles