Рубрики
Без рубрики

Создайте многопользовательское приложение с помощью Socket.io (часть 2): Создание игрового игрового сервера

В последней части этого учебника мы расширим запас облегченного Socket.io Chat Application в элементарном игровом сервере Matchmaking.

Автор оригинала: Codementor Team.

Добро пожаловать в часть второй! Обязательно пересматривайте и прочитайте чертную часть здесь, а также сталкиваются с гневом неоднозначной путаницы! (Вы были предупреждены). Сегодня мы намерены завершить двигатель и достичь следующих целей:

  • Заставить только одну (1) игру на пользователя
  • Разрешить пользователю присоединиться к игре (быть сопоставленным с игрой другой пользователя и быть включенным)
  • Разрешить пользователю оставить игру, которую они присоединились, как с помощью выхода на страницу и через кнопку.

Бонус: Ошибки также происходят во время развития Отказ С момента подушки и модификации существующих socket.io Приложение чата, я заметил, что наш передний экран имеет ошибку в разметке. Кажется, есть элемент под экраном, который нажимает окно чата над экраном – это выглядит ужасно. Прежде чем заканчиваться, мы будем устранять неполадки и исправить эту ошибку, чтобы поле ввода чата постоянно оставалась в нижней части экрана. У нас есть большая земля, чтобы покрыть, так что давайте переедем.

Начиная

Откройте папку Project, которую вы собрали из репо (или построены вместе со мной от подкидывания оригинал Socket.io Применение чата или Использование моего собственного репо Github (построена конец части одной части) для справки).

Изменение файла набой клиента для прослушивания испускаемых событий из файла сервера

Первое, что мы хотим сделать, это изменить Main.js (файл JavaScript на стороне клиента) для прослушивания испускаемых событий из файла сервера (index.js). Скопируйте и измените Socket.on («GameCreated» ). Мы собираемся создать новое событие под названием УДЕЛЕНО заменить GameCreated В вашей копии и избавиться от Console.log – в этот момент Регистрация () Функция выдвигает сообщения на интерфейс, поэтому мы повторно используем этот код, чтобы обрабатывать «Присоединиться к игре» и «Уничтожить игру». Затем замените сообщение журнала более подходящим индикатором для наших пользователей. Я решил излучить следующее:

'You are already in an Existing Game: ' + data.gameId

Понятно? Хорошо. Теперь скопируйте эту модифицированную УДЕЛЕНО Событие и изменить имя события на Jousuccess Отказ Опять же, как и прежде, измените текст журнала в более подходящее описание. Продолжайте, сделав копию sendgame Функция, замените имя и имя излучателя (я называл мой joingame ).

Наконец, joingame Функция и сопряжение Socket.on listener. Измените имена событий в remegame и текстовая строка журнала, чтобы отразить новое действие. Вместе, как функция, так и события выше, когда объединены, должны выглядеть как это в вашем файле Main.js:

//Join into an Existing Game
function joinGame(){
socket.emit('joinGame');
};

socket.on('joinSuccess', function (data) {
log('Joining the following game: ' + data.gameId);
});


//Response from Server on existing User found in a game
socket.on('alreadyJoined', function (data) {
log('You are already in an Existing Game: ' + data.gameId);
});


function leaveGame(){
socket.emit('leaveGame');
};

socket.on('leftGame', function (data) {
log('Leaving Game ' + data.gameId);
});

Добавление кнопок

Добавим в кнопки «Присоединиться» и «покинуть игру», а также исправить проблему со дном экрана клиппирования:

Многопользовательское приложение

Раскаяние предыдущие joingame Кнопка, которую мы создали в файле index.html. Использование Инспектор (в Chrome или Firefox) мы можем наблюдать, что добавляли кнопки, выдвинуты элементы вниз. Чтобы решить это, мы можем разместить кнопки, такие что они сидят в абсолютном положении. Везде, где мы выбрали их разместить их, не имеет значения, ключевое вынос – это то, что нам нужно изменить атрибут положения на CSS – по умолчанию он устанавливается на относительную, поэтому элементы нажимаются вниз на скриншоте. Я предлагаю, мы изменим его на абсолютное позиционирование, так же, как настройка коробки ввода чата. Поскольку мы устанавливаем кнопки в родительский Div, давайте изменим, что родитель, чтобы иметь абсолютное позиционирование, например, в нашем стиле.css:

.gameButtons {

position: absolute;
}

Примечание: не стесняйтесь также включать .присоединиться к игре после .создать игру В строке 18 в стиле.css так, чтобы кнопку добавленного соответствия CSS выглядит. Давайте также сокращаем кнопки-размер шрифта от 43PX до 20 пикселей.

О, но теперь у нас есть одна маленькая новая проблема, наши кнопки клип первое сообщение чата возле вершины нашего окна:

Многопользовательское приложение

У нас есть варианты. Одна идея, которую я изначально подумал, это подтолкнуть ДИВ зоны чата вниз:

Многопользовательское приложение

Хотя, честно говоря, мне не очень нравится идея возиться с Chatarea Элемент просто так кнопки, которые я добавил, будет выглядеть правильно. Я думал, что, возможно, мы могли, вместо этого просто изменить свойство CSS Float – пока я не решил, что вы не можете применить атрибут Float для абсолютной позиционирования. Нет – мой подход был изменять Геймбуттоны CSS, установив справа на 0PX (0 также работает, просто что указать, что этот элемент должен позиционировать справа без (пиксель/EM/REM/ETC). Смещение …

Многопользовательское приложение

Особенность валидации

Давайте перерываемся с интерфейса на мгновение и подталкиваю с помощью файла сервера, index.js. Я предлагаю мы сначала решаем функцию проверки, которая помешает пользователям изготовить несколько игр. Чтобы сделать это, измените наши Socket.on («Майнгей») фрагмент на линии 91. Мы собираемся сканировать GameCollection объект, чтобы увидеть, если GameObject Уже существует, где Socket.username равняется нашему нынешнему пользователю, и если он делает, вызвать УДЕЛЕНО (В строке 311 файла Main.js), который мы создали ранее, потому что мы хотим предотвратить создание нескольких игр одного и того же пользователя – этот же механизм, который мы строят, будут дублироваться, когда пользователь пытается присоединиться к нескольким играм Отказ

Во-первых, давайте отправим сообщение на экран через console.log (Gamageerver) в нашу Макияж функция, чтобы мы могли наблюдать с сервера, что GameCollection Работает как следует … мы видим, что подсчет игры увеличивается, но что Имид просто перезаписан каждый проход:

Многопользовательское приложение

Глядя на строку 93, кажется, что мы устанавливаем идентификатор, чтобы перезаписать данные в этом положении каждый раз, а не толкать данные в объект массива. Как оказывается, для демонстрационных целей, оказалось, что он функционирует правильно, но теперь я вижу, что мне пришлось изменить несколько компонентов, чтобы все работать правильно.

По линии 22, измените объект (скобки Squigly) {} к массиву (прямые кронштейны) [] Отказ Это так, что мы можем добавить несколько объектов в тот же элемент массива. В строке 94 добавьте в новую переменную для самого временного игрового объекта для формирования – это позволит нам создать объект, прежде чем добавить его на фактическое GameCollection множество. Мы также собираемся сохранить конкретные элементы каждого GameObject до «толкания» объекта в наш Список игры множество. Также обязательно измените имена старых переменных, которые использовались к концу блока, которое испускается обратно на наш интерфейс. Наш модифицированный Creategame Блок выглядит так, как это выглядит (обязательно используйте json.Stringifyify, чтобы мы могли на самом деле увидеть элементы в объекте, когда написать на экран в этом Console.log () Заявление):

//when the client requests to make a Game
socket.on('makeGame', function () {

console.log(JSON.stringify(gameCollection));

var gameObject = {};
gameObject.id = (Math.random()+1).toString(36).slice(2, 18);
gameObject.playerOne = socket.username;
gameCollection.totalGameCount ++;
gameCollection.gameList.push({gameObject});

console.log("Game Created by "+ socket.username + " w/ " + gameObject.id);

io.emit('gameCreated', {
username: socket.username,
gameId: gameObject.id
    });
});

Теперь, когда просматривать консольный журнал для сервера, мы можем видеть, что каждый объект игры добавлен к GameCollection Наряду с нашим userid для игрока. Теперь мы можем просмотреть объект, чтобы увидеть, существует ли игрок – и бросить ему ошибку, если так. Но как это сделать ???

Мой первый подход я пытался использовать Индекс с массивом. Однако я обнаружил, что я не получал тяги с выбором любых объектов, я продолжал попасть в ужасную «Освещенность): X не определяется« с помощью «x», будучи моей попыткой разыскивать цепь объекта к GameObject Отказ

Как правило, когда усиление обострения увеличивается, использование console.log () Следует также увеличить до того, чтобы быть быстрее представить неуловимую проблему, вызывающую указанное обострение. (Другими словами, подтвердите здравомыслие – я интерпретирую фразу «чек здравомыслия», чтобы означать «Убедитесь, что я строю это правильно и докажешь утверждение, ради моего здравомыслия!»)

Таким образом, либеральное использование console.log () Как я пошел, произошел; Я нашел это GameCollection.gamelist [0] ['GameObject'] ['Playerone']; Был правильным способом ссылаться на все пути к имени игрока, который был фантастической новостью. Естественно, я решил плоть ли ограничитель игры с контуром для цикла. Хотя, прежде чем я попаду в цикл, сначала добавьте следующее на строке 93:

var noGamesFound = true;

Для петли

Используя цикл для цикла, мы проверим, если PlayerOne равно Socket.username. Если имя пользователя найден, мы сделаем это nogamesfound переменная к ложе. Обернуть вещи вместе, мы теперь отделяем все код от var gameobject = {} вплоть до IO.emit и обернуть, что в заявлении, если только выполняет только если наш nogamesfound Переменная равен true. Вот что должен напоминать обновленный код:

//when the client requests to make a Game
socket.on('makeGame', function () {
console.log(JSON.stringify(gameCollection.gameList));

var noGamesFound = true;
for(var i = 0; i < gameCollection.totalGameCount; i++){
var tempName = gameCollection.gameList[i]['gameObject']['playerOne'];
if (tempName == socket.username){
noGamesFound = false;

console.log("This User already has a Game!");

socket.emit('alreadyJoined', {
gameId: gameCollection.gameList[i]['gameObject']['id']});

    }
}

if (noGamesFound == true) {

var gameObject = {};
gameObject.id = (Math.random()+1).toString(36).slice(2, 18);
gameObject.playerOne = socket.username;
gameCollection.totalGameCount ++;
gameCollection.gameList.push({gameObject});

console.log("Game Created by "+ socket.username + " w/ " + gameObject.id);

io.emit('gameCreated', {
username: socket.username,
gameId: gameObject.id
    });
    }
});

Теперь мы пытаемся создать несколько игр, и сервер отказывается от них, затем бросает сообщение для индикатора обратно на нашем пользователю, как можно увидеть здесь (подтвердите, что идентификаторы игры на обоих фотографиях):

Вид на передний план, обращенный к нашим пользователям – показывая, что они уже в игре.

Многопользовательское приложение

Просмотр консоли, показывающий сторону сервера без нескольких игр.

Многопользовательское приложение

Позволяя другим игрокам присоединиться к другой игре

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

Давайте быстро вернемся в JavaScript на стороне клиента и настроить joingame Функция для стрельбы в обработчике Click для кнопки «Присоединиться к игре», которую мы добавили в предыдущий. Скопируйте Creategame Нажмите Handler и изменить его на $ joingame Как показано на линии 236 main.js:

$createGame.click(function () {
sendGame();
});

$joinGame.click(function () {
joinGame();
});

Теперь нам нужно, чтобы построить фактический обработчик для того, когда joingame Мероприятие испускается от клиента.

В самом нижней части index.js, внутри Socket.on («Joingame») Скопируйте все содержимое цикла на петлю и поместите его в функцию обратного вызова из joingame Обработчик розетки. Точно так же, как раньше создайте новую переменную наверху, чуть выше, чтобы за петлю мы справились, так что он читает, var; Отказ

Найдите минутку, чтобы вернуться в фактическое GameObject Строитель и добавьте еще одну строку, чтобы создать игрока два слота в нашем игровом объекте. Скопируйте линию выше и замените PlayerOne PlayertWo и вместо того, чтобы быть равным Socket.username Установите его равным «NULL» _ (когда наши пользователи создают игру, игрок два всегда будет пустым, это позволяет нам очистить заполнитель для игрового объекта заранее).

Хорошо, теперь вернитесь к тому, чтобы скопированы на петлю в нижней части index.js внутри Socket.on («Joingame») Отказ Изменить tempname к plyr1tmp Отказ Скопируйте эту строку и замените plyr1tmp с plyr2tmp ; также заменить Playerone с Playertwo на конце одной и той же линии. На следующей строке заменить tempname с участием:

plyr1Tmp == socket.username || plyr2Tmp == socket.username

(С использованием || Значит или) Мы говорим: «Если plyr1tmp равно socket.username или если plyr2tmp равно socket.username, сделай что-то …»

На следующей строке заменить nogamesfound с новым уже есть Переменная такая, что она читает, уже есть; Отказ И наоборот, в строке 149, измените как имя переменной, так и значение такого, что: Если (уже имею) ... Отказ

Давайте сделаем перерыв и подтвердим, что вещи работают, как ожидалось. На данный момент моя функция Joingame выглядит так:

socket.on('joinGame', function (){
console.log(socket.username + "wants to join a game");

var alreadyInGame = false;

for(var i = 0; i < gameCollection.totalGameCount; i++){
    var plyr1Tmp = gameCollection.gameList[i]['gameObject']['playerOne'];
    var plyr2Tmp = gameCollection.gameList[i]['gameObject']['playerTwo'];
    if (plyr1Tmp == socket.username || plyr2Tmp == socket.username){

        console.log(socket.username + " already has a Game!");

        socket.emit('alreadyJoined', {
        gameId: gameCollection.gameList[i]['gameObject']['id']
        });

    }

if (alreadyInGame == false){

console.log("Add them into a Game!!!")

        }
    });
});

Время поп-викторина! Вы видите ошибку в логике до сих пор с joingame механизм? (Если вы хороши, вы можете увидеть несколько явных проблем).

Давайте спросим вместе вслух: «Как мы имеем дело с пользователем, который нажимает joingame , когда игры не были сделаны?»

Ваше предположение так же хорошо, как мой, но я думаю, что я хотел бы атаковать это, имея код создать новый игровой объект, если не нужно присоединиться. Чтобы помочь содействовать этому напустя, давайте будем упреждать и отделить строительный механизм от розетки. Для этого на линии 26 index.js добавить:

function buildGame(socket) {};

Хорошо, вырезать и вставьте (переместить) код, который находится в Если 'nogamesfound' утверждение. Поместите этот код между скобками Squigly {} нашей недавно созданной функции выше. Это позволяет нам позвонить в BuildGame Функция в любом месте нашего кода, поэтому нам не нужно дублировать его в других местах. Продолжай и добавьте в BuildGame (сокет) Вернуться в то, где исходный код был перемещен из (должен быть на или рядом с линии 128). Поскольку все настраиваются в данный момент, вы должны увидеть такое вывод в окне консоли:

Многопользовательское приложение

Подтвердить, BuildGame Функция должна любить:

function buildGame(socket) {


     var gameObject = {};
     gameObject.id = (Math.random()+1).toString(36).slice(2, 18);
     gameObject.playerOne = socket.username;
     gameObject.playerTwo = null;
     gameCollection.totalGameCount ++;
     gameCollection.gameList.push({gameObject});

     console.log("Game Created by "+ socket.username + " w/ " + gameObject.id);
 
   io.emit('gameCreated', {
      username: socket.username,
      gameId: gameObject.id
    });


};

Объявляя игру как к обоим игрокам

Давайте на самом деле создам средства для добавления пользователя к тому, что в нашем GameCollection множество. Наша цель здесь состоит в том, чтобы заменить «NULL» с именем игрока 2 и объявить игру как к обоим игрокам.

Чтобы сделать это, давайте сначала создадим новую функцию, желательно под BuildGame Функция, которую мы сделали ранее. Первое, что мы хотим сделать в этом объекте, сосредоточено количество существующих игровых объектов. Если этот счет равен нулю, то мы должны использовать BuildGame Функция для создания новой игры на месте (С моей точки зрения, если пользователь нажал, чтобы присоединиться к игре, но никакие игры не существовали, то, естественно, что они должны иметь игру, созданную от их имени, что теперь они присоединяются, в отличие от сообщения об ошибке, которые говорят им нажать ОК Тогда нажмите «Создать игру» – зачем заставить нижний пользовательский опыт, если нам не нужно?) Отказ

Прикрепите оператор еще в конце этого условного, если это создает игру, если никто не будет. В этом же выступление о остальном мы начнем с генератора случайного номера. Этот генератор должен иметь возможность подсчитывать число между 0 и количеством общего существующих игр. Наша цель для этого случайного числа – подключить его к GameCollection.gamelist [x] , где Х это случайное значение. Выбирая случайное значение, мы выбираем случайную игру для запроса (В какой-то момент в будущем вы должны выбрать расширение, вы можете выбрать другой метод, чтобы соответствовать пользователям. Например, если мы спасаем информацию о пользователе в базу данных, какой-то атрибут, такой как рейтинг навыков, может использоваться для определения и пары игроки вместе).

Вот линия, которую я использовал для создания случайной переменной:

var rndPick = Math.floor(Math.random() * gameCollection.totalGameCount);

Math.floor требуется для округления нашей стоимости до целого числа. Math.random () Только даст нам номер только с плавающей запятой между 0 и 1 (подумайте .3784727927942), умножая его через общее количество игр, мы можем надежно выбрать число от 0 до X, где X – общее количество игровых объектов.

Теперь, когда мы выбрали случайную игру, чтобы запросить, мы можем использовать простоту, если для проверки, если эта конкретная игра имеет значение PlayertWO NULL, и если это так, установите имя PlayertWo для этой конкретной игры в качестве сокета. Уведомление так:

if (gameCollection.gameList[rndPick]['gameObject']['playerTwo'] == null) {
    gameCollection.gameList[rndPick]['gameObject']['playerTwo'] = socket.username;
    socket.emit('joinSuccess', {
        gameId: gameCollection.gameList[rndPick]['gameObject']['id'] });

console.log( socket.username + " has been added to: " + gameCollection.gameList[rndPick]['gameObject']['id']);

} 

Как только значение будет установлено, перейдите к использованию Socket.emit ('jousuccess') Наряду с соответствующим объектом обратного вызова для трансляции присоединенного Имид пользователю.

Что произойдет, если что, если вы определили выше, возвращает игру, которая заполнена, вы спрашиваете? Прикрепите иначе заявление, чтобы рекурсивно позвонить Gapereeker функция против самого себя – так оно петли до тех пор, пока игра не найдена; Круг итерации кода происходит и естественно, мы создаем новую ошибку.

Спросите, «Что произойдет, когда я пытаюсь присоединиться к комнате, если все номера все полны?» Теперь добавьте в рекурсию. Любые пользователи за пределы двух, которые нечетны, будут генерировать эту ошибку в результате:

Многопользовательское приложение

Один быстрый и грязный обходной путь должен был создать переменную, которая действует как счетчик:

var loopLimit = 0; 

Увеличивать эту переменную на каждом проходе в верхней части joingame Функция, чтобы каждый раз мы используем функцию, добавляет к loplimiter Отказ В нашей первоначальной проверке, если количество игровых объектов равно 0, мы можем использовать

(Примечание: более элегантное решение было бы найти комнату, где игрок два равен нуле. Это статистически возможно (однако, вряд ли может быть) ударить те же игры несколько раз в этом проходе и эффективно пропустить потенциальные открытые игры. Мои рассуждения против Сделать, что решение было то, что я ленивый для одного, и двое, я чувствую, что сделание произвольного небольшого количества будет более эффективным на ресурсах серверов – представьте, что наличие 20 пользователей одновременно войти в это приложение, где имеется ровно даже 4000 Пользователи по всему 2000 разные игры. Если все 20 из них нажали присоединение, и код итерации через A для LOOP из более чем 2000 игровых объектов, наш сервер должен будет вычислять доступность в 40 000 раз вместо 400!)

Ваш код для Gapereeker На этом этапе должны выглядеть аналогично, если не идентично этому:

function gameSeeker (socket) {
    ++loopLimit;
    if (( gameCollection.totalGameCount == 0) || (loopLimit >= 20)) {
        buildGame(socket);
        loopLimit = 0;

    }
     else {
        var rndPick = Math.floor(Math.random() * gameCollection.totalGameCount);
        if (gameCollection.gameList[rndPick]['gameObject']['playerTwo'] == null){
            gameCollection.gameList[rndPick]['gameObject']['playerTwo'] = socket.username;
            socket.emit('joinSuccess', {
                gameId: gameCollection.gameList[rndPick]['gameObject']['id'] });

            console.log( socket.username + " has been added to: " + gameCollection.gameList[rndPick]['gameObject']['id']);
    
        } 
        else {
            gameSeeker(socket);
        }
        }
}

Аналогично, дополнение Socket.on («Joingame») это требует этого Gapereeker Функция должна выглядеть так, что:

socket.on('joinGame', function (){

console.log(socket.username + " wants to join a game");

    var alreadyInGame = false;

    for(var i = 0; i < gameCollection.totalGameCount; i++){
        var plyr1Tmp = gameCollection.gameList[i]['gameObject']['playerOne'];
        var plyr2Tmp = gameCollection.gameList[i]['gameObject']['playerTwo'];
        if (plyr1Tmp == socket.username || plyr2Tmp == socket.username){
        alreadyInGame = true;
        
        console.log(socket.username + " already has a Game!");

        socket.emit('alreadyJoined', {
        gameId: gameCollection.gameList[i]['gameObject']['id']
        });

    }
}
    if (alreadyInGame == false){
        gameSeeker(socket);

    }
});

Быстрое устранение неполадок

Midway через Flashing вместе и устранение неисправностей функциональности соединения, я обнаружил, что код, по-прежнему необходимый для разбора игрока двух во время серии Create Game. Другими словами, мой код для обеих кнопок теперь идентичен. На основании решения, определенного выше для кнопки Join Game, я принял решение удалить кнопку «Создать игровую кнопку, как сейчас является избыточным».

Когда он стоит создать игру или найти игру при использовании кнопки «Присоединиться к игре». Поэтому вы захотите удалить Socket.on («Creategame») чуть выше дополнения Socket.on («Joingame») Расположен внизу index.js. Откройте main.js и удалите $ Creategame Обработчик Click на линии 236, сам инициализатор «var» находится на линии 17, а sendgame Функция находится в строке 287. Чтобы завершить очистку, откройте файл index.html и удалите кнопку создания игры в строке 16. Добавьте следующую новую кнопку вместо этого:

Exit Game

Давайте также изменяем линию 19 стилей .CS, чтобы заменить .Creategame с .leavegame Отказ Давайте сделаем работу кнопки. Скопировать $ joingame В строке 18 от Main.js и измените их как для ReakeGame на .classname и на $ переменная название. На полпути вниз по одному файлу, скопируйте $ joingame Нажмите Обработчик и замените на remegame Как вы сделали до. Интернет-конец должен быть сделан (я надеюсь), давайте завершим сервер!

Доработка сервера

Для демо того, я собираюсь создать стратегию выхода, как это: Если Playerone выходит, то уничтожить их конкретный игровой объект, если Playertwo, с другой стороны, уходит, то мы освобождаем свое место и позвольте другую пользователю присоединиться к.

(В ситуации реальной мировой мировой совет, я советую, чтобы игровой объект был разрушен, когда у листья участника, и что оставшийся игрок награжден «выиграть» состояние игровых правил, чтобы обновить их выигрыш/потери соответственно.)

Поскольку подключение этого приложения к постоянному хранилищу документов (например, базу данных) находится за пределами объема этого учебника, мы можем согласиться на обеспечение условного уничтожения. Таким образом, вы можете увидеть, как сохранить GameObjects Открыть, если один человек уходит, а также видим, как уничтожить тот же объект, если создатель объекта, игрок один, листья.

Зайдите на несколько минут, чтобы выйти через возможные условия обработки ошибок, которые нам нужно построить для кнопки выхода. Догадаться? Если вы сказали «Нам нужно учитывать, если пользователь в настоящее время не в игре» Ты настолько на месте!

Скажите обратно в main.js и добавьте создать два новых сокета. События: отметить и Gamedestroyed Отказ На или после строки 326 в вашем файле Main.js, именно под remegame Socket.on Handler:

socket.on('notInGame', function () {
  log('You are not currently in a Game.');
});

socket.on('gameDestroyed', function (data) { 
  log( data.gameOwner+ ' destroyed game: ' + data.gameId);

});

Связывая функции к спине

Давайте связываем эти функции до задней части. К настоящему времени вы должны понимать, что вам нужно вернуться в файл index.js (сервера), чтобы создать обработчик socket.on ( remegame ). На самом вершине функции обратного вызова для этого нового обработчика продолжайте и добавьте это, если утверждение:

if (gameCollection.totalGameCount == 0){
    socket.emit('notInGame');
}

Если мы не добавим в это, если заявление, наш сервер бросает «неопределенный» тип «jureError» всякий раз, когда есть нулевые игровые объекты в GameCollection; Ожидайте, что этот крайний случай произойдет, когда никакие игровые объекты не существуют, и пользователь пытается «покинуть игру». Никакие игровые объекты не существуют, поэтому любой GameObjects Вы пытаетесь вызвать «неопределенные», отсюда и ошибка.

Следуя это, добавьте все остальное блок. В этом еще блоке мы собираемся (так же, как в Jooningame), используйте цикл для петли, чтобы повторять все GameObjects В коллекции, чтобы проверить, подключен ли пользователь к игре и уничтожить объект игры (для PLAYER 1) или удалить игрока из объекта игры (для игрока 2). Чтобы увеличить читаемость кода, давайте также потребуем момент, чтобы локально определить идентификатор игрового объекта вместе с именами игрока одного и двух. Пока что мы построили, должны напоминать это:

  socket.on('leaveGame', function() {
    if (gameCollection.totalGameCount == 0){
       socket.emit('notInGame');
    }

    else {
        for(var i = 0; i < gameCollection.totalGameCount; i++){
             var gameId = gameCollection.gameList[i]['gameObject']['id']
             var plyr1Tmp = gameCollection.gameList[i]['gameObject']['playerOne'];
             var plyr2Tmp = gameCollection.gameList[i]['gameObject']['playerTwo'];
   …

Подобное право!?!

Проверьте, если имя пользователя соответствует правильному игроку

Хорошо, теперь, когда мы определили наш идентификатор игры и игроков, мы можем запустить, если утверждения проверяют, будет ли наш сокет. Иногирное значение соответствует плееру 1 или 2. Если сокет. Имеется действительно игрок, сначала уменьшите общее количество игровых объектов, так Что подсчет соответствует количеству игровых объектов, содержащихся в GameCollection, вроде:

--gameCollection.totalGameCount;

Чтобы на самом деле удалить сам объект из нашего массива GameCollection, мы полагаемся на наш надежный локально на месте Я Переменная, которая была определена в начале нашего цикла. Заявление SPLICE JavaScript удалит элемент из массива. Используемый синтаксис по существу: myarray.splice (Elementindexnumber, #ofelementStoreMove) Отказ Что Я Переменная – это индекс. Мы только удаляем одну переменную, что означает, что наш пример выглядит так:

gameCollection.gameList.splice(i, 1);

С помощью игрового объекта ушло, как здравоохранение, мы должны console.log () все GameCollection.gamelist множество:

console.log(gameCollection.gameList);

Эмиттеры

Наконец, давайте поставим в наших излучающих излучателях, чтобы объявить обратно на передний конец. Во-первых, сток для игрока, которую можно увидеть, что он покинул игру, а затем второй, чтобы объявить всем, что игра с идентификатором «X» была уничтожена (сообщить игроку 2, что его игра была уничтожена) Отказ Эмиттер IO должен включать в себя Socket.Username, а также игра в его исходящих элементах объекта, так что данные транслируются всем пользователям. В качестве ссылки полный проигрыватель, если блок теперь должен выглядеть так:

if (plyr1Tmp == socket.username){
    --gameCollection.totalGameCount; 
    
    console.log("Destroy the Game!");
    
    gameCollection.gameList.splice(i, 1);

    console.log(gameCollection.gameList);

    socket.emit('leftGame', { gameId: gameId });
    io.emit('gameDestroyed', {gameId: gameId, gameOwner: socket.username });
} 

Игрок два логической обработки немного проще. Добавьте еще больше, если утверждение, которое проверяет, если Socket.username равно PLYR2TMP. Не соблазнитесь:

else if (plyr2Tmp == socket.username) {
   plyr2Tmp = null;
…

… Потому что вы изменяете временную переменную, но не фактические свойства объекта:

Многопользовательское приложение

После того, как вы установите GameCollection.gamelist [i] ['GameObject'] ['playertwo'] равным null , продолжай и console.log () События, поэтому они появляются на экране боковой консоли сервера для справки и, как и раньше, сокет .emit LeftGame событие нашего игрока два пользователя.

Для хорошей меры, console.log () Этот конкретный игровой объект для подтверждения того, что пользователь снимается от списка игр. Помните, что мы работаем с другим, если, что означает, что есть окончательное условие для обработки, когда пользователь нажимает «Выход игра», но нет игры … Опять же, EMIT отметить Отказ Во всем, наше Socket.on («ElemeGame») Объект должен напоминать:

socket.on('leaveGame', function() {
    if (gameCollection.totalGameCount == 0){
       socket.emit('notInGame');
    }

    else {
        for(var i = 0; i < gameCollection.totalGameCount; i++){
            var gameId = gameCollection.gameList[i]['gameObject']['id']
            var plyr1Tmp = gameCollection.gameList[i]['gameObject']['playerOne'];
            var plyr2Tmp = gameCollection.gameList[i]['gameObject']['playerTwo'];
          
              if (plyr1Tmp == socket.username){
                    --gameCollection.totalGameCount; 
                   console.log("Destroy the Game!");
            gameCollection.gameList.splice(i, 1);
            console.log(gameCollection.gameList);
            socket.emit('leftGame', { gameId: gameId });
                    io.emit('gameDestroyed', {gameId: gameId, gameOwner: socket.username });
               } 
               else if (plyr2Tmp == socket.username) {
                    gameCollection.gameList[i]['gameObject']['playerTwo'] = null;
                console.log(socket.username + " has left " + gameId)
            socket.emit('leftGame', { gameId: gameId });
            console.log(gameCollection.gameList[i]['gameObject'])
        } 

              else {
                    socket.emit('notInGame');
             }
         }
    }
});

Еще один вопрос для вас: Поскольку наш код настроен настроен, что происходит, когда у нас есть четыре пользователя подключаются к серверу, делают игры, присоединиться к ним, вступает в пятый игрок, и немедленно нажимает «Выход игры» в первую очередь?

Из-за нашей нынешней логики, держащей окончательную отметить Усилитель сокета внутри цикла для цикла мы видим не в игровое сообщение, испускаемое на каждом проходе цикла (выглядит грязно). Чтобы учесть нашу итерацию для петлей, мы должны определить локально определенную переменную (так же, как мы сделали в уже iNeyingame ) и установите значение по умолчанию для False: 'var;' Отказ

Удалить отметить Излучатель сокета из остального блока в цикле и заменить: отметить; Отказ

Опять же для справки:

 socket.on('leaveGame', function() {

    var notInGame = false;
    if (gameCollection.totalGameCount == 0){
       socket.emit('notInGame');
     
    }

    else {
        for(var i = 0; i < gameCollection.totalGameCount; i++){

          var gameId = gameCollection.gameList[i]['gameObject']['id']
          var plyr1Tmp = gameCollection.gameList[i]['gameObject']['playerOne'];
          var plyr2Tmp = gameCollection.gameList[i]['gameObject']['playerTwo'];
          
          if (plyr1Tmp == socket.username){
            --gameCollection.totalGameCount; 
            console.log("Destroy the Game!");
            gameCollection.gameList.splice(i, 1);
            console.log(gameCollection.gameList);
            socket.emit('leftGame', { gameId: gameId });
            io.emit('gameDestroyed', {gameId: gameId, gameOwner: socket.username });
           } 
          else if (plyr2Tmp == socket.username) {
            gameCollection.gameList[i]['gameObject']['playerTwo'] = null;
            console.log(socket.username + " has left " + gameId)
            socket.emit('leftGame', { gameId: gameId });
            console.log(gameCollection.gameList[i]['gameObject'])

           } 

          else {
            
            notInGame = true;
         }


      }

      if (notInGame == true){
        socket.emit('notInGame');
      }
    }

  });

Хотя мы изменили код только для Emit отметить Однажды на пользователя, у нас есть непреднамеренные следствия запуска отметить Однажды А GameObject Подключен к плееру 1, разрушен, потому что мы все еще итерация через цикл для цикла, которые вызывают ЕЩЕ; по-прежнему. Хотя этот глюк работает в наше преимущество в данный момент, я бы предпочел, чтобы мы обратимся к этому непреднамеренному поведению, чтобы наш код точно выполнял, как мы намерены и не так:

Имя пользователя в этом конкретном окне Bigl

Многопользовательское приложение

Я пытался использовать высказывание в разрыве в пределах IF и если они остальные блокировки в цикле. Однако этот подход не работал из-за того, что мы не знаем, какой индекс соответствует данному игроку. Это может быть четвертая игра в списке или даже второй, кто знает?

С тому, как наш код функционирует сейчас, это означает, что мы вызываем отметить Для того, чтобы быть правдой до того, как наш фонд для цикла достигает GameObject подходящего игрока, вызывая «вы в настоящее время в игре», когда он не должен. Оказывается, мы можем просто перевернуть назначение этой переменной отметить Чтобы по умолчанию равна верному, затем назначьте ложное значение, когда подходящее значение найден в цикле. Так как мы уже переместили, если утверждение, которое проверяет отметить За пределами цикла, он функционирует только один раз после завершения петли, итерации по всем игровым объектам! Таким образом, наш кодовый фрагмент теперь выглядит так:

 var notInGame = true;
  for(var i = 0; i < gameCollection.totalGameCount; i++){

    var gameId = gameCollection.gameList[i]['gameObject']['id']
    var plyr1Tmp = gameCollection.gameList[i]['gameObject']['playerOne'];
    var plyr2Tmp = gameCollection.gameList[i]['gameObject']['playerTwo'];
    
    if (plyr1Tmp == socket.username){
      --gameCollection.totalGameCount; 
      console.log("Destroy Game "+ gameId + "!");
      gameCollection.gameList.splice(i, 1);
      console.log(gameCollection.gameList);
      socket.emit('leftGame', { gameId: gameId });
      io.emit('gameDestroyed', {gameId: gameId, gameOwner: socket.username });
      notInGame = false;
     } 
    else if (plyr2Tmp == socket.username) {
      gameCollection.gameList[i]['gameObject']['playerTwo'] = null;
      console.log(socket.username + " has left " + gameId);
      socket.emit('leftGame', { gameId: gameId });
      console.log(gameCollection.gameList[i]['gameObject']);
      notInGame = false;

     } 

  }

  if (notInGame == true){
  socket.emit('notInGame');
  }

Полировка сервера

Мы почти в доме домой. Чтобы дать нашему серверу Matchmaking, что окончательное похудение, давайте модифицируем наш дизайн слегка так, что когда игрок покидает наш игрингервер (закрывает браузер или обновляет страницу), их объект игры автоматически уничтожен (если проигрыватель) или их имя удаляется ( Если игрок два). Для достижения, просто переместите всю логику внешнего выписки еще в Socket.on («ElemeGame») в свою собственную автономную функцию. Я положил шахту возле вершины index.js ниже функции BuildGame (на или около линии 46). Вот что выглядит новая автономная функция:

function killGame(socket) {

  var notInGame = true;
  for(var i = 0; i < gameCollection.totalGameCount; i++){

    var gameId = gameCollection.gameList[i]['gameObject']['id']
    var plyr1Tmp = gameCollection.gameList[i]['gameObject']['playerOne'];
    var plyr2Tmp = gameCollection.gameList[i]['gameObject']['playerTwo'];
    
    if (plyr1Tmp == socket.username){
      --gameCollection.totalGameCount; 
      console.log("Destroy Game "+ gameId + "!");
      gameCollection.gameList.splice(i, 1);
      console.log(gameCollection.gameList);
      socket.emit('leftGame', { gameId: gameId });
      io.emit('gameDestroyed', {gameId: gameId, gameOwner: socket.username });
      notInGame = false;
     } 
    else if (plyr2Tmp == socket.username) {
      gameCollection.gameList[i]['gameObject']['playerTwo'] = null;
      console.log(socket.username + " has left " + gameId);
      socket.emit('leftGame', { gameId: gameId });
      console.log(gameCollection.gameList[i]['gameObject']);
      notInGame = false;

     } 

  }

  if (notInGame == true){
  socket.emit('notInGame');
  }


}


In tandem with moving the core logic we now have a cleaner looking socket.on('leaveGame'):

  socket.on('leaveGame', function() {


    if (gameCollection.totalGameCount == 0){
       socket.emit('notInGame');
     
    }

    else {
      killGame(socket);
    }

  });

Обертывание

И там у вас есть! Мы расширили запас облегченного Socket.io чата приложение в элементарный сервер сопоставления. Это, в и сам по себе, далеко от минимального жизнеспособного продукта (MVP), хотя вы можете использовать фонд, выложенный здесь, чтобы создать многопользовательское приложение в Node.js. Некоторые дополнительные функции и функциональные возможности, которые я хотел бы добавить включать, но не ограничиваются:

  1. Matchmaking на основе рейтинга навыков, который требует:
    • Настойчивость данных (подключение и сохранение пользователей и пользовательских данных в структуру для получения UserIDS, COMPS WIN/LOLS, максимально достигается и атрибуты рейтинга навыков, сохраненные и получены).
  2. Возможность форсирования уникальных имен на пользователя (как оно стенд, текущий код позволяет дублировать имена пользователей. Один из подходов, возможно, имеет общее имя отображения, но скрытый внутренний уникальный идентификатор для пользователей, так что нет проблемой дубликатов идентификаторов пользователей , Поскольку логика построена сейчас, мы делаем GameObject обновления на основе имени пользователей, что является проблемой, если для игры два пользователя по имени «Джо» – система уничтожит оба игры и/или предотвращает их Возможность играть в свои уникальные игры.
  3. Возможно, возможность выбрать конкретную игру для присоединения к путям кнопок.
  4. Список URL-адресов открытых игр – где нажав, что URL по существу присоединяется к этой конкретной игре.
  5. Создание актуальной конечной точки в номере такая, что пользователи перемещаются на отдельную страницу частной игры – где проводится фактический код игрового кода.

С этими особенностями, я бы тогда преследовали фактическое создание самой игры, которая будет включать в себя игровой двигатель, логику для оценки, контролирует настроек для как игроков, так и любые античеносные контрмеры (возможно, обнаружение скорости ввода пользователя или принуждение клиента Чтобы отправить обновленное снимок текущих переменных состояния игр – настроить фильтр эвристики, который исследует прошедшее количество игровых кадров. Таким образом, если пользователь обманывает и подделывает свои переменные условия игры или нелегальные перемен на сервер, У меня был бы сервер отправить фиксированное сигнал, чтобы заставить мошенник выйти из игры и проиграть (и, возможно, если я чувствую себя особенно злым, создал бы специализированную этикетку, обозначающую такого пользователя как «обманщик»).

Если вы нашли ошибку или ошибку в Github Repo Из этого файла проекта или, возможно, если вы думаете, что эта реализация слишком любитель для вашего вкуса, то, все средства, высказывайте. Я бы предпочел, чтобы ты мне иногда научил меня или два! И наоборот, если вы нашли это слишком трудным для Traverse и/или нуждаются в дополнительных разъяснений, используйте систему комментирования Comment Commarts Electronic Carrier Commer Commiteen ниже, чтобы вызвать меня! Спасибо за ваше время с нами сегодня до следующего раза.

Авторское био.

Frankenmint – это технофил с склонностью к биткойн, разбирается в разработке веб-сайтов и программного обеспечения.