井字游戏(PixiJS)

图片

一个脆弱的,愚蠢的,梦幻的排字员决定成为一名程序员,却一无所获……但是他没有放弃编程,而是决定从小程序开始……

这是我能想到的最好的描述。出于这个目的,我开始编写简单的程序来磨练自己的技能,以惯用的语言熟悉新的设计,老实说,它甚至开始带给我乐趣。

如果您没有开发经验,那么本文将是有用的;如果您已经有开发经验,那么请花一些时间来做一些更有价值的事情。

这不是训练。更像是博客。

目标是制作游戏井字游戏的3个版本。

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

在这里,您可以通过循环编写所有内容。我选择了一种更简单的方法。我的领域永远是静态的。因此,对细胞进行简单检查。值得注意的是,我退还该物件是为了检查以后谁赢了。在对象中,val和win属性。瓦尔负责结束比赛。

游戏结束。

// /
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。我喜欢它,然后开始阅读文档。我必须马上说,我对英语的了解很少,很难,但是俄语的文章很少(或者我看起来很差)。我不得不戳,并在谷歌翻译的帮助下度过难关。

我只看了Yulia Pucnina撰写的有关“用Pixi js制作胖动画”的有关该主题的报告

该报告来自2014年,您需要了解API可能会发生变化。一只眼睛看文档,另一只眼睛看视频。所以我学习了。编写这样一个简单的原型花了4个小时。接近代码。

我们将默认初始化为pixi

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无法通过名称或ID访问元素。因此,我们必须对容器中的整个集合进行排序,因此代码看起来很麻烦。该函数与前一个函数没有什么不同,除了它返回的参数。

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

总而言之,分几个阶段进行开发很有趣。这不是理想的开发周期,但需要我开始。

感谢您的阅读,很快再见。

参考文献


Github
报告
PixiJS网站

All Articles