Цель состоит в том, чтобы создать игру Tic Tac Toe с использованием современной внешней среды MVC, такой как AngularJS, и сделать ее доступной для игры в Интернете. Меня гораздо больше интересует играбельность онлайн, чем логика крестиков-ноликов. Я достаточно изучил Angularfire, чтобы создать одну игру, в которую играют два человека, но недостаточно, чтобы создать несколько самоуправляемых игр для двух игроков. Давайте подарим этим крестикам-ноликам душу!

Во-первых, давайте начнем с того, что я узнал во время «Лекции по Firebase». Это выглядит примерно так:

var app = angular.module('myApp', ["firebase"]); app.controller('MyAppCtrl', function($scope, $firebase) { var ref = new Firebase("https://myfirebase.firebaseio.com/"); $scope.fbRoot = $firebase(ref); $scope.fbRoot.$on("loaded", function() { var IDs = $scope.fbRoot.$getIndex(); if(IDs.length == 0) { //these values get stored on Firebase $scope.fbRoot.$add({ board:['','','','','','','','',''], play: true, turns: 0, p1score: 0, p2score: 0, ties: 0, winner: '', nextPlayer: 'Player 1, your move!', player1: 'X', player2: 'O', }); //event handler for all changes occurring on Firebase $scope.fbRoot.$on("change", function() { IDs = $scope.fbRoot.$getIndex(); $scope.obj = $scope.fbRoot.$child(IDs[0]); }); } else { $scope.obj = $scope.fbRoot.$child(IDs[0]); } }); //... })

Потрясающий. Так что же произойдет в этом «//…»? Один игрок выполнит действие, и чтобы отправить это действие своему противнику, Javascript выполнит $scope.obj.$save();. Это изменяет объект Firebase, и другой игрок сможет «увидеть» эти изменения благодаря обработчику события $on(«change»).

Это здорово, но создает только одну игру, никогда не удаляет себя и не очень масштабируемо. Итак, я построил лучшую мышеловку.

Чтобы решить проблему масштабируемости, я хочу перенести большую часть материала Firebase на фабрику. Я делаю это, чтобы обойти проблемы, возникающие из-за асинхронных запросов. Поскольку оно должно отправлять и получать сообщения с сервера Firebase, приложение больше не работает в том порядке, в котором оно было написано. Вот взгляд на завод, который я построил:

var app = angular.module("TTT",[ "firebase"]); app.factory("GameCreator", ["$q","$firebase",function($q, $firebase){ var deferred = $q.defer(); //note that you are also able to call "new Firebase('https://fb.firebase.io.com/whatever');" var ref = new Firebase("https://angularfiretictactoe.firebaseio.com/");//this is my fb $firebase(ref).$on("loaded",function(value){ var games = $firebase(ref); //get keys for all games var IDs = games.$getIndex(); //function for creating a new game var initGame = function(n){ //add a new game to the list of games games.$add({ //the board's state and turns, shared by all users board:[['','',''],['','',''],['','','']], playerOnesTurn:true, turns:0, //player objects are in the cloud for access in directives, and so //that players can VIEW each others' properties. If you want your //players to have avatars, you would reference those avatars in the //playerone and playertwo objects playerone:{piece:'x', ready:false,won:false}, playertwo:{piece:'o',ready:false,won:false}, //helpers to sync and assign player xIsAvailable:false, oIsAvailable:true }); //my opponent did something. His/her actions need to appear on my screen. games.$on("change",function(){ deferred.resolve( games.$child(games.$getIndex()[n]) ); }); }; var n = IDs.length; //if no games, then build a game if(n == 0){ initGame(n); } //else, find out if either a game is available, or a new game needs to be built else{ //if last - 1 has a spot, put me in that game, else create me a new game var checkThisGame = games.$child(IDs[n-1]); checkThisGame.$on('loaded',function(){ if(checkThisGame.oIsAvailable == true){ checkThisGame.oIsAvailable = false; checkThisGame.$save(); deferred.resolve(checkThisGame); } else{ initGame(n); } }); } }); return deferred.promise; }])

Важно отметить, что по умолчанию все в вашем объекте Firebase является объектом Firebase, поэтому вы не можете напрямую обращаться к примитивам (например, логическим, таким как oIsAvailable). Чтобы получить доступ к примитиву, вы должны использовать обработчик события $on(‘loaded’). Это потому, что объект появляется только после того, как ваш код вызывает Firebase и говорит: «Эй, мне нужны мои вещи». Если вы попытаетесь получить доступ к oIsAvailable без $on(‘loaded’), вы получите неопределенную ошибку.

Эту деталь легко упустить из виду в менее масштабируемой версии, потому что существование только одной игры снижает потребность в более чем одном «загруженном» обработчике событий. Давайте посмотрим, что произойдет, когда мы поместим эту фабрику в наш контроллер.

app.controller ('BoardCtrl', function($scope,$timeout,GameCreator,$window) { GameCreator.then(function(returnedData){ returnedData.$on('loaded',function(){ $scope.game = returnedData; var piece; if(returnedData.oIsAvailable == true){ piece = 'x'; } else { piece = 'o'; } $scope.myPiece = { val:piece }; //remove this game from Firebase if a user leaves the page $window.onbeforeunload = function (event) { //delete this game from the firebase I/O returnedData.$remove(); //return null because we do not need to see an alert return null; } //implement the rest of your tic tac toe logic here: win conditions, etc... }); });

Обратите внимание, что этот код ожидает загрузки возвращенных данных и только после этого представляет игру пользователю. Он настраивает некоторую информацию на стороне клиента, а затем реализует логику крестиков-ноликов (опущено для краткости). Наконец, окно получает прослушиватель событий при выходе, поэтому игра удаляется из Firebase.

Отсюда этот код может пойти во многие места, потому что теперь Firebase выступает в роли свахи. Настройте этот код по своему вкусу и наслаждайтесь!

Первоначально опубликовано на сайте itsakap.wordpress.com 10 апреля 2014 г.