Meteor是一个流行的全栈Web框架, 它使您可以非常轻松地将您的想法原型化,并非常快速地从开发到生产。 它的反应性和DDP的使用使其成为构建简单的多人浏览器游戏的理想选择。
在本教程中,我将向您展示如何使用流星使用其默认的前端模板引擎Blaze构建多人TicTacToe 。 我将假定您已经使用Meteor玩了一点,当然,您对使用JavaScript编码感到很满意。
如果您对Meteor的使用经验为零,建议您先按照Meteor官方网站上的TODO应用教程进行操作。
您可以在随附的GitHub repo中找到完整应用程序的代码。
创建应用
如果未安装Meteor,则应根据您的操作系统按照其网站上的说明进行操作 。
生成脚手架
现在安装了Meteor,打开终端并运行以下命令:
meteor create TicTacToe-Tutorial
这将创建一个具有您应用程序名称的文件夹(在本例中为TicTacToe-Tutorial )。 此新文件夹包含应用程序的基本文件结构。 实际上里面有一个示例应用程序。
导航到文件夹:
cd TicTacToe-Tutorial
现在运行该应用程序:
meteor
我知道,我知道……这是一个很难记住的命令,并且您会经常使用它,因此您应该开始记住它!
如果一切正常,则控制台应该正在构建应用程序。 完成后,打开您的Web浏览器并转到http:// localhost:3000以查看该应用程序正在运行。 如果您以前从未这样做过,建议您试用该示例应用程序。 尝试弄清楚它是如何工作的。
让我们看一下文件结构。 打开应用程序的文件夹。 我们目前唯一关心的是客户端文件夹和服务器文件夹。 客户端文件夹中的文件将由客户端下载并执行。 服务器文件夹中的文件将仅在服务器上执行,客户端无法访问它们。
这些是新文件夹中的内容:
client/main.js # a JavaScript entry point loaded on the client
client/main.html # an HTML file that defines view templates
client/main.css # a CSS file to define your app's styles
server/main.js # a JavaScript entry point loaded on the server
package.json # a control file for installing NPM packages
.meteor # internal Meteor files
.gitignore # a control file for git
搭建板
井字游戏板是一个简单的三乘三桌; 没有什么花哨的,这对于我们的第一个多人游戏非常有用,因此我们可以专注于功能。
该面板将由客户端下载,因此我们将在客户端文件夹中编辑文件。 首先删除main.html上的内容,然后将其替换为以下内容:
client / main.html
<head>
<title>tic-tac-toe</title>
</head>
<body>
<table id="board">
<tr>
<td class="field"></td>
<td class="field"></td>
<td class="field"></td>
</tr>
<tr>
<td class="field"></td>
<td class="field"></td>
<td class="field"></td>
</tr>
<tr>
<td class="field"></td>
<td class="field"></td>
<td class="field"></td>
</tr>
</table>
</body>
进行更改后,请不要忘记保存文件! 否则,它们将不会被Meteor认可。
现在,让我们在板上添加一些CSS 。 打开main.css文件并添加以下内容:
客户端/ main.css
table
{
margin: auto;
font-family: arial;
}
.field
{
height: 200px;
width: 200px;
background-color: lightgrey;
overflow: hidden;
}
#ui
{
text-align: center;
}
#play-btn
{
width: 100px;
height: 50px;
font-size: 25px;
}
.mark
{
text-align: center;
font-size: 150px;
overflow: hidden;
padding: 0px;
margin: 0px;
}
.selectableField
{
text-align: center;
height: 200px;
width: 200px;
padding: 0px;
margin: 0px;
}
我们还添加了一些额外的ID和类,这些ID和类将在本教程的后面部分中使用。
最后,删除我们不需要的client / main.js ,然后在浏览器中打开应用程序以查看其外观。
一切都很好,但不是最佳解决方案。 让我们通过引入Blaze Templates进行一些重构。
创建模板
模板是具有自己功能的HTML代码,您可以在应用程序中的任何地方重复使用。 这是将您的应用分解为可重用组件的好方法。
在创建第一个模板之前,我们将在客户端文件夹内添加两个以上的文件夹。 我们将一个称为html ,将另一个称为js 。
在html文件夹中,创建一个包含以下内容的新board.html文件:
client / html / board.html
<template name="board">
<table id="board">
<tr>
<td class="field"></td>
<td class="field"></td>
<td class="field"></td>
</tr>
<tr>
<td class="field"></td>
<td class="field"></td>
<td class="field"></td>
</tr>
<tr>
<td class="field"></td>
<td class="field"></td>
<td class="field"></td>
</tr>
</table>
</template>
现在,在main.html文件夹上,将body标记内的内容替换为以下代码:
client / main.html
<head>
<title>tic-tac-toe</title>
</head>
<body>
{{>board}}
</body>
这将在body
标签内插入具有属性name="board"
模板。
但这是我们以前使用过的硬编码板。 直到现在,它都在模板内部,因此让我们利用模板助手来动态构建我们的电路板。
使用助手
我们将在电路板模板中声明一个帮助器 ,它将为我们提供一个数组,该数组的长度与我们希望电路板具有的尺寸相同。
在js文件夹中,创建一个名为board.js的文件,其内容如下:
客户端/js/board.js
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
Template.board.helpers({
sideLength: () => {
let side = new Array(3);
side.fill(0);
return side;
}
});
现在,我们将在开发板的模板HTML中使用此帮助器,以对该帮助器提供的数组中的每个元素重复一行。 为了帮助我们,我们将使用Every -in空格键块帮助器。
用以下内容替换board.html文件中的内容:
client / html / board.html
<template name="board">
<table id="board">
{{#each sideLength}}
{{#let rowIndex=@index}}
<tr>
{{#each sideLength}}
<td class="field" id="{{rowIndex}}{{@index}}">
{{{isMarked rowIndex @index}}}
</td>
{{/each}}
</tr>
{{/let}}
{{/each}}
</table>
</template>
注意,我们遍历数组两次,一次遍历行 ,一次遍历列 ,并在实例化时实例化相应的标签( tr
或td
)。 我们还将它们的id
属性设置为行的@index + 列的 @index 。 我们得到的是一个两位数的数字,它将帮助我们识别该元素及其在板上的位置。
在http:// localhost:3000上查看该应用程序,以了解到目前为止的情况。
用户界面
现在我们有了一个漂亮的面板,我们需要一个播放按钮和一个标签来显示当前游戏的信息。
让我们从在html文件夹内创建ui.html文件开始……您知道该练习。 现在,向其中添加以下内容:
client / html / ui.html
<template name ="ui">
<div id="ui">
{{#if inGame}}
<p id="status">
{{status}}
</p>
{{else}}
<button id="play-btn">Play</button>
{{/if}}
</div>
</template>
如您所见,我们使用#if空格键块帮助程序和inGame
帮助程序(我们尚未定义)作为条件。 p
标签内也有status
助手。 我们还将在以后定义它。
它是如何工作的? #if
如果inGame
帮助程序返回true
,则玩家将看到status
帮助程序中的内容。 否则,我们将仅显示播放按钮。
别忘了,要显示此组件,我们需要将其添加到我们的主要客户端模板中:
client / main.html
<head>
<title>tic-tac-toe</title>
</head>
<body>
{{>ui}}
{{>board}}
</body>
在登录
我们不会处理任何登录界面。 我们将安装一个非常有用的软件包brettle:accounts-anonymous-auto ,它将自动将所有用户匿名登录到我们的应用程序。
转到控制台并运行以下命令:
meteor add brettle:accounts-anonymous-auto
现在,在添加此软件包后首次打开应用程序时,它将创建一个新用户,并且每次在同一浏览器上打开应用程序时,它都会记住您。 如果我们不保留该用户的任何数据,则最好在注销时将其删除。 但是我们不会在本教程中进行介绍。
建立游戏
最后,我们将开始构建游戏本身! 让我们回顾一下我们将要实现的功能,以清楚地了解下一步的发展。
我们需要以下功能:
- 制作游戏
- 加入现有游戏
- 采取行动
- 建立胜利条件
- 向玩家显示游戏状态
- 销毁完成的游戏实例
为了利用Meteor的延迟补偿,我们会将大部分代码放在客户端和服务器均可访问的位置。
为此,我们将在项目的根目录下创建一个名为lib的文件夹。 无论我们放入哪里,客户端都会下载,因此我们必须非常谨慎。 您不希望偶然给客户端提供任何API密钥或访问隐藏功能。
游戏合集
流星使用Mongo Collections 。 如果您对Mongo不太熟悉,但是您使用了其他任何面向文档的数据库 ,就可以了。 否则,将集合视为表,其中每一行都独立于下一行。 一行可以有六列,而同一表中的另一行可以有四列完全不同的列。
我们需要创建一个集合,并且我们需要客户端和服务器都可以访问它。 因此,我们将在lib文件夹中创建一个games.js文件,并在其中创建一个名为“游戏”的集合实例,并将其存储在全局变量Games
:
lib / games.js
import { Mongo } from 'meteor/mongo';
Games = new Mongo.Collection("games");
现在,您可能想知道为什么我们要赋予玩家访问数据库和游戏逻辑的权限。 好吧,我们只授予播放器本地访问权限。 Meteor为客户提供了一个本地小型mongo数据库 ,我们只能使用Publish-Subscribe模式来填充它,稍后我将向您展示。 那是客户唯一有权访问的东西。 即使客户端写入其本地数据库,如果信息与服务器数据库中的信息不匹配,也会被覆盖。
也就是说,Meteor默认情况下会安装几个非常不安全的软件包。 一种称为autopublish ,它会自动发布所有集合并订阅客户端。 另一个称为不安全 ,它为客户端提供对数据库的写访问权限。
这两个软件包都非常适合用于原型制作,但是我们应该立即将其卸载。 转到控制台并运行以下命令:
meteor remove insecure
meteor remove autopublish
有了这种方式,现在我们需要一种将客户端中的操作与服务器上的操作同步的方法。 输入流星方法 。
games.play方法
Meteor.methods是一个对象,我们可以在其中注册可以由客户端使用Meteor.call函数调用的方法。 它们将首先在客户端上执行,然后在服务器上执行。 因此,借助本地的Mongo数据库,客户将能够立即看到更改的发生。 然后,服务器将在主数据库上运行相同的代码。
让我们在games
集合下面创建一个空的games.play
方法:
lib / games.js
Meteor.methods({
"games.play"() {
}
});
制作游戏
创建一个名为gameLogic.js的lib文件夹中的文件,并在我们将创建GameLogic
带班newGame
方法,在这里我们将插入一个新的文档到我们的游戏合集:
lib / gameLogic.js
class GameLogic
{
newGame() {
if(!this.userIsAlreadyPlaying()) {
Games.insert({
player1: Meteor.userId(),
player2: "",
moves: [],
status: "waiting",
result: ""
});
}
}
}
在这段代码中,我们要在插入新游戏之前询问玩家是否已经在玩游戏,因为我们不会为每个玩家一次支持多个游戏。 这是非常重要的一步,否则我们可能最终会遇到一个巨大的错误。
让我们在newGame()
下面添加userIsAlreadyPlaying
方法:
lib / gameLogic.js
userIsAlreadyPlaying() {
const game = Games.findOne({$or:[
{player1: Meteor.userId()},
{player2: Meteor.userId()}]
});
if(game !== undefined)
return true;
return false;
}
让我们看一下开始新游戏的过程。
当玩家按下“播放”按钮时,我们会寻找现有游戏加入他们。 如果所述玩家找不到要加入的游戏,则会创建一个新游戏。 在我们的模型中, player1
是创建游戏的玩家, player2
是空字符串,并且status
默认为“正在等待”。
因此,如果另一个玩家按下播放按钮,他们将寻找一个游戏,该游戏包含一个空的player2
字段和一个status
字段,值为“ waiting”。 然后,我们将该玩家设置为player2
并相应地更改其status
。
现在,我们必须使game.js中的Meteor方法可以访问我们的GameLogic
类。 我们将导出类的实例,然后将其导入games.js文件中。 在类之外,在gameLogic.js文件的底部添加以下行:
export const gameLogic = new GameLogic();
在games.js文件顶部添加以下行:
import { gameLogic } from './gameLogic.js';
现在,我们可以向空的games.play()方法添加逻辑。 首先,我们寻找状态为“正在等待”的游戏,然后如果未找到其他游戏,则调用newGame()
:
lib / games.js
Meteor.methods({
"games.play"() {
const game = Games.findOne({status: "waiting"});
if(game === undefined) {
gameLogic.newGame();
}
}
});
刊物
为了找到游戏,我们需要向客户授予games
集合的访问权限。 为此,我们将创建一个Publication 。 出版物使我们仅向客户显示我们希望他们看到的数据。 然后,我们为客户端订阅 发布 ,以使他们能够访问该数据。
为了使玩家能够访问游戏集合,我们将创建一个“游戏”出版物。 但是,当将玩家添加到新游戏中时,我们将授予他们访问该特定游戏中所有字段的权限。 因此,还将有一个“我的游戏”出版物。
转到服务器文件夹内的main.js文件,并将其内容替换为以下内容:
服务器/ main.js
import { Meteor } from 'meteor/meteor';
Meteor.publish('Games', function gamesPublication() {
return Games.find({status: "waiting"}, {
fields:{
"status": 1,
"player1": 1,
"player2": 1
}
});
});
Meteor.publish('MyGame', function myGamePublication() {
return Games.find({$or:[
{player1: this.userId},
{player2: this.userId}]
});
});
现在我们需要订阅“游戏”出版物。 我们将在UI模板的onCreated方法回调中进行此操作。
使用以下代码在client / js /中创建ui.js文件:
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
Template.ui.onCreated(() => {
Meteor.subscribe('Games');
});
播放事件
模板提供了一个事件对象,我们可以在其中注册…。 你猜怎么了? 答对了! 活动。 我们将在UI模板中创建一个事件。 每当玩家单击ID为'play-btn'的DOM元素时,我们都会将会话变量inGame
设置为true,我们将调用games.play
方法,并订阅MyGame
集合。
会话变量可以在客户端代码中的任何位置使用,甚至可以在模板之间使用。 要使用它们,我们需要添加Session包 :
meteor add session
转到ui.js文件,并在onCreated
方法之后添加以下行:
客户端/js/ui.js
Template.ui.events({
"click #play-btn": () => {
Session.set("inGame", true);
Meteor.call("games.play");
Meteor.subscribe('MyGame');
}
});
导入每个文件中使用的软件包是一个好习惯。 由于我们在ui.js文件中使用Session
包,因此我们应该导入它。 只需在顶部添加以下行:
import { Session } from 'meteor/session';
好! 现在我们需要添加几个助手。 还记得ui.html吗? 快速浏览。 我们使用了inGame
帮助程序和status
帮助程序。 让我们在events
对象下面声明它们:
客户端/js/ui.js
Template.ui.helpers({
inGame: () => {
return Session.get("inGame");
},
status: () => {
}
});
如您所见, inGame
帮助器返回存储在inGame
会话变量中的值。 我们暂时将status
助手保留为空。
参加游戏
毕竟,到目前为止,您已经做好了准备,加入游戏应该很简单。
首先,我们将joinGame
方法添加到GameLogic
类中:
lib / gameLogic.js
joinGame(game) {
if(game.player2 === "" && Meteor.userId() !== undefined) {
Games.update(
{_id: game._id},
{$set: {
"player2": Meteor.userId(),
"status": game.player1
}
}
);
}
}
如您所见,我们传递一个游戏变量,并将player2
字段设置为玩家的_id
,将status
字段设置为_id_
的player1
。 这样我们才能知道轮到谁了。
现在,我们将从games.play()
调用此方法。 转到games.js文件,并用以下内容替换games.play
方法的内容:
lib / games.js
Meteor.methods({
"games.play"() {
const game = Games.findOne({status: "waiting"});
if(game === undefined) {
gameLogic.newGame();
} else if(game !== undefined && game.player1 !== this.userId && game.player2 === "") {
gameLogic.joinGame(game);
}
}
});
因此,现在,我们在三个条件下添加了else if :如果我们找到一个游戏, 而 player1
不是该玩家, 而 player2
是一个空字符串,则我们加入游戏。
采取行动–逻辑
当我们为每个新游戏定义模型时,我们声明了一个带有空数组( []
)的moves字段作为默认值。 移动将是由进行移动的玩家的_id
和所选位置组成的JSON对象。
转到games.js文件,并在games.play()
下面添加以下方法。 请记住, Meteor.methods
需要一个JSON对象,因此方法应以逗号分隔:
lib / games.js
"games.makeMove"(position) {
check(position, String);
gameLogic.validatePosition(position);
let game = Games.findOne({status: this.userId});
if(game !== undefined) {
gameLogic.addNewMove(position);
if(gameLogic.checkIfGameWasWon()) {
gameLogic.setGameResult(game._id, this.userId);
} else {
if(game.moves.length === 8) {
gameLogic.setGameResult(game._id, "tie");
} else {
gameLogic.updateTurn(game);
}
}
}
}
让我们逐行介绍此方法。 它以字符串position
作为参数。 首先,我们使用检查包确保接收到的是字符串,而不是可能危害服务器的恶意代码,然后验证位置。
之后,我们找到一个游戏,其中status
字段与进行此操作的玩家的_id
相同; 这样我们知道轮到他们了。 如果我们找到了该游戏,或者换句话说,轮到该玩家了,我们将把该移动添加到moves
数组中。 然后,我们检查该举动后游戏是否获胜。 如果确实获胜,那么我们将当前玩家设为获胜者。 否则,如果没有获胜,但数组中已经有八步棋,那么我们将宣布平局。 如果还没有八步棋,我们会更新回合以让下一位棋手移动。
就像我们对ui.js文件中的Session
包所做的一样 。 我们应该将check
包导入games.js文件中。 您知道怎么回事...在顶部添加以下行。
import { check } from 'meteor/check';
我们正在使用GameLogic
类中的许多方法,这些方法尚未定义。 因此,让我们继续进行。
转到gameLogic.js,并在GameLogic
类中添加以下方法:
validatePosition()
validatePosition(position) {
for (let x = 0; x < 3; x++) {
for (let y = 0; y < 3; y++) {
if (position === x + '' + y)
return true;
}
}
throw new Meteor.Error('invalid-position', "Selected position does not exist... please stop trying to hack the game!!");
}
在这里,我们只需遍历3×3网格以确保发送的位置在其限制之内。 如果找不到客户端发送的头寸,则在网格中会抛出错误。
addNewMove()
addNewMove(position) {
Games.update(
{status: Meteor.userId()},
{
$push: {
moves: {playerID: Meteor.userId(), move: position}
}
}
);
}
在这里,我们使用$ push Mongo运算符,将包含当前玩家_id
和position
的新动作推入数组。
setGameResult()
setGameResult(gameId, result) {
Games.update(
{_id: gameId},
{
$set: {
"result": result,
"status": "end"
}
}
);
}
使用$组再次操作,我们更新结果字段的值result
,其可以是参数_id
的玩家或“扎”之一,我们设置status
,以“结束”。
updateTurn()
updateTurn(game) {
let nextPlayer;
if(game.player1 === Meteor.userId())
nextPlayer = game.player2;
else
nextPlayer = game.player1;
Games.update(
{status: Meteor.userId()},
{
$set: {
"status": nextPlayer
}
}
);
}
这很简单。 我们将两个玩家作为参数,然后找出哪个是当前玩家,然后将status
字段设置为另一玩家的_id
。
赢得比赛
在games.makeMove
方法中还有一个方法需要声明; 获胜算法。 还有其他更有效的方法来计算TicTacToc游戏中的获胜者,但我决定采用本教程可以想到的最直观,最简单的解决方案。
转到gameLogic.js文件,并在GameLogic
类中添加以下方法:
lib / gameLogic.js
checkIfGameWasWon() {
const game = Games.findOne({status: Meteor.userId()});
const wins = [
['00', '11', '22'],
['00', '01', '02'],
['10', '11', '12'],
['20', '21', '22'],
['00', '10', '20'],
['01', '11', '21'],
['02', '12', '22']
];
let winCounts = [0,0,0,0,0,0,0];
for(let i = 0; i < game.moves.length; i++) {
if(game.moves[i].playerID === Meteor.userId()) {
const move = game.moves[i].move;
for(let j = 0; j < wins.length; j++) {
if(wins[j][0] == move || wins[j][1] == move || wins[j][2] == move)
winCounts[j] ++;
}
}
}
for(let i = 0; i < winCounts.length; i++) {
if(winCounts[i] === 3)
return true;
}
return false;
}
让我们仔细看看这个方法。
首先,我们找到当前的游戏。 然后,我们声明一个包含所有可能的获胜组合的矩阵,以及一个包含七个零的数组的变量:每个组合一个。 之后,我们将循环浏览当前玩家的所有举动,并将其与每种组合的每个位置进行比较。 对于每个巧合,我们在对应的winCount
索引位置加1。 如果任何一个winCount
指数加起来winCount
3,我们就会知道当前玩家已经赢了。
如果您第一次没有得到,请不要担心。 休息片刻,喝杯咖啡,稍后再以新鲜的眼睛再次阅读。 对代码的解释可能会造成混淆。 有时,最好只是阅读代码并弄清楚它的作用。
采取行动–控制器
我们对此游戏的玩家控制仅是简单的单击。 因此,实现这一点应该是小菜一碟。 让我们转到board.js文件,并在helpers
程序之后将事件模板对象添加到我们的文件中:
客户端/js/board.js
Template.board.events({
"click .selectableField": (event) => {
Meteor.call("games.makeMove", event.target.id);
}
});
简单吧? 当玩家单击类为“ selectableField”的DOM元素时,我们将调用games.makeMove
方法,并将DOM元素的ID作为位置参数传递。 请记住,我们是在元素在网格中的位置之后命名ID。 如果需要,请查看board.html文件以刷新内存。
显示动作
现在,在同一文件中,我们将创建一个名为isMarked
的帮助isMarked
,该帮助程序将在mark
和selectableFields
之间切换。 这样,我们将能够看到已选择了哪些位置,并选择了空位置。
将此助手添加到sideLength
助手下方:
客户端/js/board.js
isMarked: (x, y) => {
if(Session.get("inGame")) {
let myGame = Games.findOne();
if(myGame !== undefined && myGame.status !== "waiting") {
for(let i = 0; i < myGame.moves.length; i++) {
if(myGame.moves[i].move === x + '' + y) {
if(myGame.moves[i].playerID === Meteor.userId())
return "<p class='mark'>X</p>";
else
return "<p class='mark'>O</p>";
}
}
if(myGame.status === Meteor.userId())
return "<div class='selectableField' id='"+x+y+"'></div>";
}
}
}
并将助手添加到模板:
client / html / board.html
...
<td class="field" id="{{rowIndex}}{{@index}}">
{{{isMarked rowIndex @index}}}
</td>
...
让我们来看一下这个功能。 我们将一行和一列作为参数(x,y)。 如果我们是inGame
,我们将寻找该游戏。 如果找到它,并且status
为“正在等待”,我们将遍历所有移动,如果给定的行+列与我们的moves
之一匹配,我们将在板上画一个X。 如果它与其他玩家的动作之一匹配,我们将画一个O。
在每局比赛中,我们的移动将始终为X,而对手的将始终为O。 虽然,您的对手会看到他们的举动是X。 因为我们在不同的设备上(甚至在不同的国家/地区)玩游戏,所以我们实际上并不关心谁拥有X或O。 这里重要的是每个玩家都知道哪些是他们的举动,哪些是对手的。
显示状态
我们快完成了! 还记得ui.js文件中的空status
助手吗? 用以下代码填充它:
客户端/js/ui.js
status: () => {
if(Session.get("inGame")) {
let myGame = Games.findOne();
if(myGame.status === "waiting")
return "Looking for an opponent...";
else if(myGame.status === Meteor.userId())
return "Your turn";
else if(myGame.status !== Meteor.userId() && myGame.status !== "end")
return "opponent's turn";
else if(myGame.result === Meteor.userId())
return "You won!";
else if(myGame.status === "end" && myGame.result !== Meteor.userId() && myGame.result !== "tie")
return "You lost!";
else if(myGame.result === "tie")
return "It's a tie";
else
return "";
}
}
这是很明显的,但我以防万一。 如果我们是inGame
,我们将寻找当前的游戏。 如果status
等于“等待中”,我们告诉玩家等待对手。 如果status
等于玩家的_id
,我们告诉他们轮到他们了。 如果status
不是他们的_id
并且比赛还没有结束,我们告诉他们轮到对手了。 如果结果等于玩家的_id
,我们告诉玩家他们赢了。 如果比赛结束,结果不是他们的_id
,也不是“平局”,那么他们输了。 如果结果等于“平局”,我们告诉他们这是平局………! ;)
现在,您可以试一下。 是! 继续打开一个普通的浏览器窗口和一个私有选项卡,与自己对战。 尽量不要过分开心,否则您将终生孤身一人(我发誓,这是对的)。
注销
Buuuuuut,我们尚未完成。 不! 如果我们断开连接并自己离开其他玩家怎么办? 所有这些完成的游戏如何填补我们数据库中的宝贵空间呢? 我们需要跟踪玩家的连接并采取相应的措施。
但是首先,我们需要一种删除游戏并从游戏中删除玩家的方法。 转到gamesLogic.js,并在GameLogic
类中添加以下方法:
lib / gameLogic.js
removeGame(gameId) {
Games.remove({_id: gameId});
}
removePlayer(gameId, player) {
Games.update({_id: gameId}, {$set:{[player]: ""}});
}
removeGame
方法将gameId
作为参数并将其删除。
removePlayer()
将gameId
和player
(可以是player1
或player2
的字符串)作为参数,并清空该特定游戏中该玩家的字段。
为了跟踪用户的连接,我们将安装一个有用的软件包mizzao:user-status 。 转到控制台,使用以下命令关闭正在运行的应用程序 ctrl + C 并运行以下命令:
meteor add mizzao:user-status
该程序包具有connectionLogout
回调,该回调提供了一个参数,其中包含重要信息,例如断开连接的用户的userId
。
转到服务器文件夹中的main.js文件,并在底部添加以下回调。
/server/main.js
UserStatus.events.on("connectionLogout", (fields) => {
const game = Games.findOne(
{$or:[
{player1: fields.userId},
{player2: fields.userId}]
});
if(game != undefined) {
if(game.status !== "waiting" && game.status !== "end") {
if(game.player1 === fields.userId) {
gameLogic.setGameResult(game._id, game.player2);
gameLogic.removePlayer(game._id, "player1");
} else if(game.player2 === fields.userId) {
gameLogic.setGameResult(game._id, game.player1);
gameLogic.removePlayer(game._id, "player2");
}
} else {
if(game.player1 === "" || game.player2 === "") {
gameLogic.removeGame(game._id);
} else {
if(game.player1 === fields.userId)
gameLogic.removePlayer(game._id, "player1");
else if(game.player2 === fields.userId)
gameLogic.removePlayer(game._id, "player2");
}
}
}
});
所以,如果我们能找到一个游戏,断开播放器或者是player1
或player2
,我们检查,如果那场比赛的状态不是“等待”,游戏还没有走到尽头。 如果有的话,我们将胜利给予对手并删除与之断开连接的玩家。 否则,我们将删除游戏(如果任何玩家字段为空)或。 如果不是这种情况,我们将从游戏中移除断开连接的玩家。
与其他软件包一样,我们应该导入UserStatus
软件包。 我们还在connectionLogout
回调中使用了GameLogic
类中的一些方法,因此请继续将它们都导入server / main.js文件的顶部:
import { UserStatus } from 'meteor/mizzao:user-status';
import { gameLogic } from '../lib/gameLogic.js';
包起来
最后,您应该有一款可以正常工作的游戏! 照原样,您可以上传它,然后与您的朋友(或您自己)一起尝试。
如果您刚才所做的任何事情对您几乎没有意义,那就不用担心。 如果您继续研究代码,这将很快变得有意义。 您只需要一些时间来整理一些概念。 那是一个完全自然的过程。 如果您遇到困难,请不要忘记签出完整应用程序的代码 。
当您对代码足够满意时,应该开始尝试添加一些功能。 也许实施不同的获胜算法,可能会让您增加董事会的人数。 也许对玩家实施持久性以保存统计数据并保留游戏记录。 您甚至可以实现登录界面,并让玩家选择用户名。 挑战朋友呢? 当然,您也可以使用相同的概念来创建完全不同的游戏。
我希望知道您的想法,所以请告诉我! 我希望您喜欢本教程,在评论中留下您的疑问和评论。 下一个见!
From: https://www.sitepoint.com/building-multiplayer-tictactoe-game-with-meteor/