تيك تاك تو (PixiJS)

صورة

قرر أحد كتاب الطباعة الضعيفة والغباء والحالم أن يصبح مبرمجًا ولم يأت شيء منه ... لكنه لم يتخل عن البرمجة وقرر البدء ببرامج صغيرة ...

هذا هو أفضل وصف يمكن أن أتوصل إليه. ولهذا الغرض ، بدأت في كتابة برامج بسيطة لصقل مهاراتي ، والتعرف على التصميمات الجديدة بلغتي المعتادة ، ولأكون صادقًا ، فقد بدأت تجلب لي السعادة.

إذا كان لديك القليل من الخبرة في التطوير ، فستكون المقالة مفيدة ، وإذا كانت لديك بالفعل تجربة تطوير ، فاقضي بعض الوقت في شيء أكثر فائدة.

هذا ليس تدريب. أشبه بمدونة.

كان الهدف هو عمل 3 نسخ من لعبة tic tac toe.

1 - أبسط ( بدون صورة بصرية جميلة باستخدام DOM )
2 - امنح الفرصة للعب معًا (جهاز كمبيوتر واحد )
3 - نقل كل هذا إلى لوحة قماشية

لن أصف تيك تاك تو ، آمل أن يعرف الجميع مبدأ اللعبة. ستكون جميع الروابط المفيدة (المستودع والوثائق) في نهاية المقالة.

ماذا جاء من هذا؟ جلالة ...

الاصدار الاول


صورة

هذا هو الأبسط. لنكون صادقين ، الإصدارات اللاحقة ليست صعبة ...

نحن بحاجة إلى تخطيط من الحاوية حيث سنحتاج إلى وضع ملعبنا. أضفت عنصر بيانات إلى كل عنصر منذ ذلك الحين اعتقدت أنني سأحتاج إلى معرف ، لكنني لم أستخدمه.

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

أريد أن أحذرك على الفور! لا ينبغي اعتبار هذا الرمز هو الرمز الوحيد الصحيح ، وتعتبر الكتابة خلاف ذلك خطأ. هذا هو الحل ولا أكثر.

وبالتالي. أولاً ، نحتاج إلى ربط نقرة على الخلية. أثناء النقر ، نذهب (البوت أيضًا ، ولكن بدوره) ونتحقق من الخلية.

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

يمشي البوت بشكل عشوائي.

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

فحص الخلية

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

هنا يمكنك كتابة كل شيء من خلال الحلقات. اخترت طريقة أبسط. مجال عملي ثابت. لذلك ، فحص بسيط للخلايا. ومن الجدير بالذكر أنني أعيد الشيء لأتحقق في المستقبل من ربح. في الكائن ، فال وربح الخصائص. فال مسؤول عن إنهاء اللعبة.

نهاية اللعبة.

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

أثناء النقر ، لدينا تحقق لمعرفة ما إذا كانت checkMap قد أعادت val: true. إذا كان الأمر كذلك ، أكمل اللعبة.

الإصدار الثاني


لاعبان على نفس الكمبيوتر.

أخذت جزءًا من المنطق من معالج النقرات إلى دالة منفصلة وتمرير سياق المكالمة إلى الوظيفة ، لأننا بحاجة إلى تحديد الزر الذي تم النقر عليه.

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


لقد قسمت إلى دالتين ، ولكن لديهم تكرار التعليمات البرمجية. من الناحية المثالية ، اقسم على 3. واحد رئيسي واثنان يعملان مع السياق.

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

الإصدار الثالث


ربما هذه هي النقطة الأكثر إثارة للاهتمام لأنه الآن تبدو اللعبة حقًا لعبة ، وليس تفاعل عناصر DOM.

اخترت العمل PixiJS. لا أستطيع أن أقول أي شيء عن + و- في هذه المكتبة ، لكني نظرت إلى مثال واحد حيث كان هناك 60.000 عنصر وكانت جميعها متحركة. الرسوم المتحركة بسيطة ، لكن FPS بقيت في 50-60. أعجبني ذلك وبدأت أقرأ الوثائق. يجب أن أقول على الفور أن معرفتي باللغة الإنجليزية ضئيلة للغاية ، فقد كانت صعبة ، ولكن هناك عدد قليل جدًا من المقالات باللغة الروسية (أو كنت أبدو ضعيفًا). اضطررت إلى كزة وبمساعدة مترجم جوجل لأعبر الأشواك.

شاهدت تقرير واحد فقط حول هذا الموضوع من قبل يوليا Pucnina "الرسوم المتحركة الدهون مع بيكسي شبيبة" .

التقرير من عام 2014 وعليك أن تفهم أن واجهة برمجة التطبيقات قد تتغير. عين على التوثيق والثانية على الفيديو. لذلك درست. استغرق الأمر 4 ساعات لكتابة مثل هذا النموذج الأولي البسيط. أقرب إلى الرمز.

نجعل بيكسي التهيئة الافتراضية

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

وكذلك إنشاء غلاف (الحاوية الرئيسية مع الخلايا) ووضعه في قماشنا

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

في الحلقة ، ننشئ خلايانا ، ونعينها الأحجام والإحداثيات اللازمة ، ونضيف أيضًا القيمة الافتراضية إلى الخلية كسلسلة فارغة منذ سيكون هذا مفيدًا في المستقبل وتعليق المعالجات على الخلايا ، بعد تمكين علامة التفاعل للحاوية.

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 هو المسؤول عن تقدم كل لاعب. لم أجد طريقة أفضل من الإعلان عن الأنماط الخاصة بي لكل نص. هناك يتغير اللون ، لكنني لم أفهم كيفية تغيير اللون. في كل مرة يجب تخصيص أنماط جديدة للنص. أيضا هناك فحص للخلايا.

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

فيما يتعلق بالتحقق نفسه. checkMap. كما أفهمها ، لا يمكن لـ pixiJS الوصول إلى عنصر بالاسم أو المعرّف. علينا أن نفرز المجموعة بأكملها في الحاوية لهذا السبب ، يبدو الرمز مرهقًا. لا تختلف الوظيفة عن الوظائف السابقة ، باستثناء المعلمات التي تُرجعها.

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

حسنًا ، آخر وظيفتين مسؤولتان عن إنهاء اللعبة وتنظيف القماش. يبدو لي أن التفسير هنا غير ضروري.

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

للتلخيص ، كان من المثير للاهتمام إجراء التطوير في عدة مراحل. إنها ليست دورة تطوير مثالية ، ولكن بما أحتاجه للبدء.

شكرا لك على القراءة ونراكم قريبا.

المراجع



تقرير جيثب
. موقع PixiJS

All Articles