Tic Tac Toe (PixiJS)

Bild

Ein verletzlicher, dummer, verträumter Schriftsetzer beschloss, Programmierer zu werden, und es kam nichts heraus ... Aber er gab das Programmieren nicht auf und beschloss, mit kleinen Programmen zu beginnen ...

Dies ist die beste Beschreibung, die ich finden konnte. Zu diesem Zweck begann ich, einfache Programme zu schreiben, um meine Fähigkeiten zu verbessern, neue Designs in meiner üblichen Sprache kennenzulernen, und um ehrlich zu sein, machte es mir sogar Spaß.

Wenn Sie wenig Entwicklungserfahrung haben, ist der Artikel hilfreich. Wenn Sie bereits Entwicklungserfahrung haben, verbringen Sie Zeit mit etwas Wertvollerem.

Dies ist kein Training. Eher wie ein Blog.

Das Ziel war es, 3 Versionen des Spiels Tic Tac Toe zu machen.

1 - Das einfachste ( ohne ein schönes Bild, mit dem DOM )
2 - Geben Sie die Möglichkeit, zusammen zu spielen (ein Computer )
3 - Übertragen Sie all dies auf Leinwand.

Ich werde Tic-Tac-Toe nicht beschreiben. Ich hoffe, jeder kennt das Prinzip des Spiels. Alle nützlichen Links (Repository, Dokumentation) befinden sich am Ende des Artikels.

Was ist daraus geworden? Hm ...

Erste Version


Bild

Das ist das einfachste. Um ehrlich zu sein, sind die nachfolgenden Versionen nicht schwierig ...

Wir brauchen ein Layout aus dem Container, in dem wir unser Spielfeld platzieren müssen. Ich habe seitdem jedem Element ein Datenelement hinzugefügt Ich dachte, ich würde einen Bezeichner brauchen, aber ich habe ihn nicht benutzt.

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

Ich möchte Sie sofort warnen! Dieser Code sollte nicht als der einzig wahre angesehen werden, und das Schreiben ansonsten wird als Fehler angesehen. Dies ist meine Lösung und nichts weiter.

Damit. Zuerst müssen wir einen Klick auf die Zelle binden. Während des Klicks gehen wir (der Bot auch, aber wiederum) und überprüfen die Zelle.

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

Der Bot geht zufällig.

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

Zellenprüfung

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

Hier könnte man alles durch Schleifen schreiben. Ich habe einen einfacheren Weg gewählt. Mein Feld ist immer statisch. Daher eine einfache Überprüfung der Zellen. Es ist erwähnenswert, dass ich das Objekt zurückschicke, um in Zukunft zu überprüfen, wer gewonnen hat. Im Objekt val und win Eigenschaften. Val ist dafür verantwortlich, das Spiel zu beenden.

Ende des Spiels.

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

Während des Klicks wird überprüft, ob checkMap den Wert val: true zurückgegeben hat. Wenn ja, beende das Spiel.

Zweite Version


Zwei Spieler am selben Computer.

Ich habe einen Teil der Logik vom Klick-Handler in eine separate Funktion übernommen und den Aufrufkontext an die Funktion übergeben, da wir bestimmen müssen, auf welche Schaltfläche geklickt wurde.

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


Ich habe in zwei Funktionen unterteilt, aber sie haben Codeduplizierung. Teilen Sie im Idealfall durch 3. Ein Hauptteil und zwei, die mit dem Kontext arbeiten.

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

Dritte Version


Vielleicht ist dies der interessanteste Punkt, weil Jetzt sieht das Spiel wirklich wie ein Spiel aus, nicht wie das Zusammenspiel von DOM-Elementen.

Ich habe mich für PixiJS entschieden. Ich kann nichts über das + und - dieser Bibliothek sagen, aber ich habe mir ein Beispiel angesehen, in dem es 60.000 Elemente gab und die alle animiert waren. Die Animation ist einfach, aber der FPS blieb bei 50-60. Es gefiel mir und ich fing an, die Dokumentation zu lesen. Ich muss sofort sagen, dass meine Kenntnisse der englischen Sprache minimal sind, es war schwierig, aber es gibt nur sehr wenige Artikel auf Russisch (oder ich habe schlecht ausgesehen). Ich musste stochern und mit Hilfe eines Google-Übersetzers durch die Dornen kommen.

Ich habe nur einen Bericht zu diesem Thema von Yulia Pucnina "Fat Animation with Pixi js" gesehen .

Der Bericht stammt aus dem Jahr 2014 und Sie müssen verstehen, dass sich die API ändern kann. Ein Auge auf die Dokumentation und das zweite auf das Video. Also habe ich studiert. Es dauerte 4 Stunden, um einen so einfachen Prototyp zu schreiben. Näher am Code.

Wir machen Standard-Initialisierungspixi

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

Erstellen Sie außerdem einen Wrapper (den Hauptcontainer mit Zellen) und legen Sie ihn in unserer Zeichenfläche ab

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

In der Schleife erstellen wir unsere Zellen, legen die erforderlichen Größen und Koordinaten fest und fügen der Zelle den Standardwert als leere Zeichenfolge hinzu, weil Dies wird sich in Zukunft als nützlich erweisen und Handler an die Zellen hängen, nachdem das Interaktivitätsflag des Containers aktiviert wurde.

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 ist für den Fortschritt jedes Spielers verantwortlich. Ich habe keinen besseren Weg gefunden, als für jeden Text meine eigenen Stile zu deklarieren. Dort ändert sich die Farbe, aber ich habe nicht verstanden, wie man die Farbe ändert. Jedes Mal, wenn dem Text neue Stile zugewiesen werden müssen. Es gibt auch eine Überprüfung der Zellen.

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

In Bezug auf die Überprüfung selbst. checkMap. Nach meinem Verständnis kann pixiJS nicht über Name oder ID auf ein Element zugreifen. Wir müssen die gesamte Sammlung im Container sortieren, deshalb sieht der Code umständlich aus. Die Funktion unterscheidet sich nicht von den vorherigen, mit Ausnahme der Parameter, die sie zurückgibt.

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

Nun, die letzten beiden Funktionen sind dafür verantwortlich, das Spiel zu beenden und die Leinwand zu reinigen. Es scheint mir, dass die Erklärung hier überflüssig ist.

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

Zusammenfassend war es interessant, die Entwicklung in mehreren Schritten durchzuführen. Es ist kein idealer Entwicklungszyklus, aber mit dem, was ich anfangen musste.

Vielen Dank fürs Lesen und bis bald.

Verweise


Github
Bericht
von. PixiJS-Website

All Articles