Connetti 4 con Socket.io

Il gioco di Connect 4 riporta i ricordi del passato. Questo gioco classico ha sicuramente lasciato un'impressione su tutti coloro che lo hanno giocato. In questo articolo, creeremo una versione multiplayer di Connect 4 usando Node.js e Socket.io.

Installare le dipendenze

Questo tutorial presuppone che siano installati Node.js e npm. Per gestire le dipendenze frontend, utilizzeremo Bower per recuperare i pacchetti e Grunt per gestire le attività. Apri un terminale e installa Bower and Grunt globalmente eseguendo:

$ sudo npm install -g bower grunt-cli

Nota: Grunt richiede versioni Node.js> = 0.8.0. Al momento di scrivere questo articolo, i repository di Ubuntu avevano una versione precedente di Node. Assicurati di utilizzare il PPA di Chris Lea se sei su Ubuntu. Per altre distribuzioni / sistemi operativi, fare riferimento ai documenti di installazione di Node.js per ottenere la versione più recente.

Con Bower e Grunt-cli installati, creiamo una directory per il progetto e recuperiamo Twitter Bootstrap e Alertify.js (per gestire le notifiche di avviso) usando Bower.

$ mkdir connect4 $ cd connect4 $ bower install bootstrap alertify.js 

Ora, impostiamo una directory per gestire le nostre risorse personalizzate. Lo nomineremo risorse e memorizza i nostri file personalizzati Less e JavaScript al suo interno.

$ mkdir -p assets / javascript, stylesheets $ touch assets / javascript /frontend.js assets / fogli di stile /styles.less assets / fogli di stile / variables.less 

Per servire le risorse compilate, creeremo una directory chiamata statico con sotto-directory nominate javascript e fogli di stile.

$ mkdir -p static / javascript, stylesheets 

Aperto assets / fogli di stile / styles.less e importa variables.less e i file Less richiesti dal bootstrap.

// Core variables and mixins @import "... / ... /bower_components/bootstrap/less/variables.less"; @import "... / ... /bower_components/bootstrap/less/mixins.less"; // Reimposta @import "... / ... /bower_components/bootstrap/less/normalize.less"; @import "... / ... /bower_components/bootstrap/less/print.less"; // Core CSS @import "... / ... /bower_components/bootstrap/less/scaffolding.less"; @import "... / ... /bower_components/bootstrap/less/type.less"; @import "... / ... /bower_components/bootstrap/less/code.less"; @import "... / ... /bower_components/bootstrap/less/grid.less"; @import "... / ... /bower_components/bootstrap/less/tables.less"; @import "... / ... /bower_components/bootstrap/less/forms.less"; @import "... / ... /bower_components/bootstrap/less/buttons.less"; // Components @import "... / ... /bower_components/bootstrap/less/component-animations.less"; @import "... / ... /bower_components/bootstrap/less/glyphicons.less"; @import "... / ... /bower_components/bootstrap/less/dropdowns.less"; @import "... / ... /bower_components/bootstrap/less/navbar.less"; @import "... / ... /bower_components/bootstrap/less/jumbotron.less"; @import "... / ... /bower_components/bootstrap/less/alerts.less"; @import "... / ... /bower_components/bootstrap/less/panels.less"; @import "... / ... /bower_components/bootstrap/less/wells.less"; // Utility classes @import "... / ... /bower_components/bootstrap/less/utilities.less"; @import "... / ... /bower_components/bootstrap/less/responsive-utilities.less"; // Variabili personalizzate @import "variables.less"; // Alertify @import (less) "... / ... /bower_components/alertify.js/themes/alertify.core.css"; @import (less) "... / ... /bower_components/alertify.js/themes/alertify.default.css"; // Stili personalizzati 

Fatto ciò, impostiamo il Gruntfile.js compilare i file Less in CSS e combinare tutti i file JavaScript in un unico file. La struttura di base del Gruntfile.js file con alcune attività, sarebbe simile a questo:

// Gruntfile module.exports = function (grunt) // Inizializzazione dell'oggetto di configurazione grunt.initConfig (// Configurazione dell'attività less: // ..., concat: // ..., watch: // ... ); // Carica plugin // Definisci compiti; 

Attività Asset

Definiremo tre compiti per gestire le risorse. Il primo sarà compilare tutti i file Less in CSS. Il secondo sarà quello di concatenare tutti i file JavaScript in uno e, infine, l'ultimo compito sarà quello di guardare i file per le modifiche. L'attività di controllo sarebbe l'attività predefinita e può essere eseguita digitando grugnito nella root del progetto, una volta terminata la configurazione del file grunt.

Impostiamo un'attività per compilare tutti i file Less in file CSS in statico / stylesheets elenco.

di meno: sviluppo: opzioni: compress: true,, file: "./static/stylesheets/styles.css": "./assets/stylesheets/styles.less",, 

Andando avanti, configureremo un'altra attività per concatenare tutti i file JS in uno solo.

concat: options: separator: ';',, js: src: ['./bower_components/jquery/jquery.js', './bower_components/bootstrap/dist/js/bootstrap.js', '. /bower_components/alertify.js/lib/alertify.js ',' ./assets/javascript/frontend.js'], destinazione:' ./static/javascript/frontend.js',,, 

Infine, impostiamo l'attività di controllo per vedere i nostri file per le modifiche ed eseguire le attività richieste al salvataggio.

watch: js: files: ['./bower_components/jquery/jquery.js', './bower_components/bootstrap/dist/js/bootstrap.js', './bower_components/alertify.js/lib/alertify. js ',' ./assets/javascript/frontend.js'], attività: [' concat: js '], less: file: [' ./assets/stylesheets/*.less '], attività: [' less '], 

Fatto ciò, caricaremo i plugin npm richiesti e registreremo l'attività di default.

// Carica i plugin grunt.loadNpmTasks ('grunt-contrib-concat'); grunt.loadNpmTasks ( 'grunt-contrib-less'); grunt.loadNpmTasks ( 'grunt-contrib-orologio'); // Definire i compiti grunt.registerTask ('default', ['watch']); 

Passiamo alla gestione delle dipendenze del backend usando npm. Per questo progetto, utilizzeremo il framework Express con il motore di template di Jade e Socket.io. Installare localmente le dipendenze eseguendo il seguente comando:

$ npm installa express jade socket.io async grunt grunt-contrib-concat grunt-contrib-less grunt-contrib-watch 

La struttura della directory dovrebbe essere simile a questa:

Ora che abbiamo stabilito le nostre dipendenze, è il momento di passare al frontend del nostro gioco.

Il frontend

Continuiamo creando un file chiamato server.js e serve i contenuti usando Express.

var express = require ('express'); var async = require ('async'); var app = express () var io = require ('socket.io'). listen (app.listen (8000)); app.use ('/ static', express.static (__ dirname + '/ static')); app.get ('/', function (req, res) res.render ('index.jade');); app.get ('/ landingPage', function (req, res) res.render ('landing.jade');); console.log ('Listening on port 8000'); 

I modelli

Stiamo usando il Jade Templating Engine per gestire i template. Per impostazione predefinita, Express cerca viste all'interno di visualizzazioni directory. Facciamo il visualizzazioni directory e creare file Jade per il layout, l'indice e la pagina dei ringraziamenti.

$ mkdir -p visualizza $ touch views / layout.jade, index.jade, landing.jade 

Successivamente, modifichiamo il layout del nostro progetto, la pagina indice e la pagina di destinazione (landing.jade).

doctype html html (lang = "en") titolo titolo Collega 4 link (rel = 'stylesheet', href = "static / stylesheets / styles.css") body #wrap nav.navbar.navbar-default (role = 'navigation' ) .container-fluid .navbar-header a.navbar-brand (href = '#') Collega il contenuto a 4 blocchi #footer .container p.text-muted | Sviluppato da | Gaurav Narula per Nettuts blocca script javascript (src = '/ socket.io/socket.io.js') script (src = 'static / javascript /frontend.js') 
estende il contenuto del blocco di layout .container .row .col-xs-3 .p1-score p 0 # board.col-xs-6 table.center-table label .form-group (per = "shareUrl"). col-sm- 3.control-label.share-label URL di condivisione: .col-sm-9 input (type = 'text' ReadOnly) .form-control .col-xs-3 .p2-score p 0 
estende il contenuto del blocco di layout .jumbotron .container 

Grazie!

Grazie per aver giocato! Speriamo ti sia piaciuto il gioco!

blocco javascript

Si noti che stiamo servendo socket.io.js, anche se non è definito da nessuna parte nel statico directory. Questo perché il socket.io il modulo gestisce automaticamente la porzione disocket.io.js file client.

Lo stile

Ora che abbiamo configurato l'HTML, passiamo alla definizione degli stili. Inizieremo con la sovrascrittura di alcune variabili di bootstrap con i valori di nostra scelta all'interno assets / fogli di stile / variables.less.

@ body-bg: # F1F1F1; @ text-color: # 717171; @ headingings-color: # 333; @ brand-primary: # 468847; @ brand-success: # 3A87AD; @ avviso di marca: # FFC333; @ pericolo di marca: # FB6B5B; @ navbar-default-bg: # 25313E; @ navbar-default-color: #ADBECE; @ navbar-default-link-color: @ navbar-default-color; @ navbar-default-link-hover-color: # 333; 

Quindi aggiungeremo alcuni stili personalizzati a styles.less.

// Stili personalizzati / * Sticky Footer * / html, body height: 100%;  / * Wrapper per il contenuto della pagina per spingere verso il basso footer * / #wrap min-height: 100%; altezza: auto; margine: 0 auto -60px; riempimento: 0 0 60 px;  #footer height: 60px; background-color: # 65BD77; > .container padding-left: 15px; padding-right: 15px;  .container .text-muted margin: 20px 0; colore: #fff;  // Tabella griglia border-collapse: separate; border-spacing: 10px 10px;  table tr margin: 10px;  table tr td width: 50px; altezza: 50 px; border: 1px solid # 3A87AD;  .center-table margin: 0 auto! important; fluttuante: nessuno! importante;  .p1-score, .p2-score padding: 185px 0; larghezza: 50 px; altezza: 50 px; font-size: 25px; altezza della linea: 50px; colore: #fff; allineamento del testo: centro;  .p1-score float: right; p background: # FFC333; .current border: 5px solid darken (# FFC333, 10%);  .p2-score p background: # FB6B5B; .current border: 5px solid darken (# FB6B5B, 10%);  .share-label line-height: 34px; allineamento del testo: giusto;  

Il JavaScript

Fatto questo, aggiungiamo un po 'di codice JavaScript assets / javascript /frontend.js per creare la griglia e aggiungere dati fila e dati colonna attributi con valori corretti dinamicamente.

$ (document) .ready (function () for (var i = 0; i < 6; i++) $('#board table').append("); for(var j = 0; j < 7; j++)  $('#board tr').last().append("); $('#board td').last().addClass('box').attr('data-row', i).attr('data-column', j);   ); 

Questo copre l'impostazione del frontend. Compila le risorse e avvia il server.

$ grunt less concat: js $ node server.js 

Se hai seguito, la pagina dell'indice dovrebbe essere simile a questa:

Mancia: Corri il grugnito comando sulla radice del progetto su un terminale separato. Questo chiamerebbe il compito predefinito che capita di essere visto. Questo concat tutto i file JS o compila tutti i file Meno su ogni salvataggio.

Il backend

L'obiettivo di Connect 4 è collegare quattro "blocchi" consecutivi in ​​orizzontale, verticale o diagonale. Socket.io ci consente di creare camere che i clienti possono aderire. Pensa a loro come canali IRC. 

Le sale da gioco

Useremo questa funzione nel gioco, in modo che solo due giocatori possano trovarsi in una stanza e la stanza venga distrutta quando uno di loro si ferma. Creeremo un oggetto che terrà traccia di tutte le stanze e, a sua volta, di tutti gli stati di gioco. Iniziamo con la creazione di una funzione in server.js per creare nomi di stanze casuali.

function generateRoom (length) var haystack = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; var room = "; for (var i = 0; i < length; i++)  room += haystack.charAt(Math.floor(Math.random() * 62));  return room; ; 

La funzione si aspetta la lunghezza del nome casuale della stanza che vogliamo generare. Il nome della stanza è generato dalla concatenazione di caratteri casuali dal pagliaio stringa. Modifichiamo il nostro percorso per la pagina indice per includere l'URL di condivisione e creare una nuova rotta per servire il contenuto se si accede a una particolare stanza.

app.get ('/', function (req, res) share = generateRoom (6); res.render ('index.jade', shareURL: req.protocol + ': //' + req.get (' host ') + req.path + condividi, condividi: condividi);); app.get ('/: room ([A-Za-z0-9] 6)', funzione (req, res) share = req.params.room; res.render ('index.jade',  shareURL: req.protocol + ': //' + req.get ('host') + '/' + condividi, condividi: condividi);); 

Nel codice sopra, generiamo l'ID di condivisione usando il generateRoom () funzione che abbiamo definito in precedenza e passiamo l'ID della condivisione e l'URL come parametri al modello. Il secondo percorso prevede un parametro denominato room che è limitato da un'espressione regolare. Il regex consente una stringa contenente solo caratteri alfanumerici, di lunghezza sei. Di nuovo, passiamo al shareURL e id come parametri per il modello. Aggiungiamo alcuni attributi all'elemento di input del nostro indice in modo che possiamo accedervi frontend.js dopo.

input (type = 'text', data-room = share, name = "shareUrl", valore = shareURL ReadOnly) .form-control

Quindi, modifichiamo frontend.js per connetterti al server Socket.io, unisciti alla room e assegna alcune proprietà al player corrente.

var socket = io.connect ('localhost'); funzione Player (room, pid) this.room = room; this.pid = pid;  var room = $ ('input'). data ('room'); var player = new Player (room, ","); socket.on ('connect', function () socket.emit ('join', room: room);); socket.on ('assign', function (data) player.color = data.color; player.pid = data.pid; if (player.pid == 1) $ ('. p1-score p'). addClass ('current'); else $ ('. p2-score p'). addClass ('current');); 

Si noti che abbiamo creato un oggetto chiamato giocatore per fare riferimento al giocatore sul lato client. Alla connessione, l'evento join viene chiamato sul backend che inturn emette l'emit di assegnamento sul frontend per assegnare alcune proprietà al player. Ora possiamo procedere alla definizione del codice nel back-end per gestire il aderire evento.

// un oggetto per contenere tutti i gamestates. La chiave denota room var var games = ; io.sockets.on ('connection', function (socket) socket.on ('join', function (data) if (data.room in games) if (typeof games [data.room] .player2! = "undefined") socket.emit ('leave'); return; socket.join (data.room); socket.set ('room', data.room); socket.set ('color', '# FB6B5B '); socket.set (' pid ', -1); giochi [data.room] .player2 = socket // Imposta gli opponenti socket.set (' avversario ', giochi [data.room] .player1); giochi [dati .room] .player1.set ('avversario', giochi [data.room] .player2); // Imposta turn socket.set ('turn', false); socket.get ('avversario', funzione (err, avversario ) avversario.set ('turn', true);); socket.emit ('assign', pid: 2); else socket.join (data.room); socket.set ('room') , data.room); socket.set ('color', '# FFC333'); socket.set ('pid', 1); socket.set ('turn', false); games [data.room] =  giocatore1: presa, scheda: [[0,0,0,0,0,0,0], [0,0,0,0,0,0,0], [0,0,0,0,0, 0,0], [0,0,0,0,0,0,0], [0,0,0,0,0,0,0], [0,0,0,0,0,0, 0]],; socket.emit ('assign', pid: 1););); 

Nota: I gestori di eventi Socket.io sul backend dovrebbero essere aggiunti all'interno di io.sockets.on ('connection', function (socket)  blocco di codice. Allo stesso modo, i gestori di eventi e il codice JavaScript di frontend dovrebbero essere all'interno di $ (document) .ready (function () blocco di codice.

Nel codice sopra, abbiamo definito il gestore di eventi per il aderire evento che viene emesso dal frontend. Controlla se la stanza data esiste già e se il giocatore due non è già stato assegnato e in tal caso, assegna il client corrente come giocatore due. In caso contrario, assegna il client corrente come giocatore uno e inizializza la scheda. Emettiamo il partire evento sul frontend per i clienti che tentano di partecipare a una partita in corso. Abbiamo anche impostato alcune proprietà sul socket utilizzando socket.set (). Questi includono la room id, il colore, il pid e la variabile turn. Le proprietà impostate in questo modo possono essere recuperate dal callback di socket.get (). Successivamente, aggiungiamo il partire gestore di eventi sul frontend.

socket.on ('leave', function () window.location = '/ landingPage';); 

Il partire il gestore di eventi semplicemente reindirizza il client alla pagina di destinazione. Ora procediamo ad emettere un evento che avvisa entrambi i giocatori se il gioco è pronto per iniziare. Aggiungiamo un po 'di codice al Se condizione del nostro evento di join sul lato server.

if (data.room in games) // ... aggiungi al codice che esiste // Notifica i giochi [data.room] .player1.emit ('notify', connected: 1, turn: true); socket.emit ('notify', connected: 1, turn: false);  

Dobbiamo definire a notificare evento nel frontend che si occupa della notifica. Alert.js fornisce un modo pulito per gestire tutte le notifiche. Aggiungiamo il gestore eventi di notifica in frontend.js.

socket.on ('notify', function (data) if (data.connected == 1) if (data.turn) alertify.success ('Players Connected! Your turn'), altrimenti alertify.success ('Players Connected ! Giro dell'avversario ');); 

È tempo di provare i nostri progressi finora. Avvia il server localmente e accedi a localhost e l'URL di condivisione in due finestre separate. Se hai seguito, dovresti essere accolto con un avviso nell'angolo in basso a destra, come mostrato nell'immagine qui sotto:

Aggiunta di interattività

Ora procediamo ad aggiungere il codice che emette un evento quando si fa clic sui blocchi. Per questa parte, dobbiamo accertare se il click è stato fatto dal giocatore corretto. Questo è dove il turno la proprietà che abbiamo impostato sulla presa entrerebbe in gioco. Aggiungere il seguente codice a frontend.js.

$ ('. box'). click (function () // trova la casella per rilasciare il disco su var click = row: $ (this) .data ('row'), column: $ (this) .data ('column'); socket.emit ('click', click);); 

Il codice sopra imposta un gestore di eventi su tutte le celle della tabella. Una cosa da notare è che la griglia in Connect 4 è simile all'aggiunta di mattoni in un muro, cioè, non si può riempire una particolare coppia (riga, colonna) se la coppia (riga-1, colonna) non è piena. Pertanto, dobbiamo prima ottenere la coppia (riga, colonna) della cella su cui è stato fatto clic e quindi elaborare un modo per determinare la cella effettiva da riempire. Questo viene fatto nel back-end, nel gestore eventi per clic.

socket.on ('click', function (data) async.parallel ([socket.get.bind (this, 'turn'), socket.get.bind (questo, 'opponente'), socket.get.bind ( this, 'room'), socket.get.bind (this, 'pid')], function (err, results) if (results [0]) socket.set ('turn', false); risultati [1 ] .set ('turn', true); var i = 5; while (i> = 0) if (games [results [2]]. board [i] [data.column] == 0) break;  i--; if (i> = 0 && data.column> = 0) games [results [2]]. board [i] [data.column] = results [3]; socket.get ('color ', function (err, color) socket.emit (' drop ', row: i, column: data.column, color: color); results [1] .emit (' drop ', row: i, colonna: data.column, color: color);); else console.log ('Opponent \' s turn '););); 

Il gestore di eventi sopra riportato utilizza il modulo asincrono per recuperare contemporaneamente le proprietà del socket. Ciò evita i callback di annidamento negli usi successivi di socket.get (). Il risultati variabile è una matrice con elementi nello stesso ordine di socket.get () chiamate. risultati [0], quindi si riferisce a turno e così via. 

Una volta che le proprietà sono state recuperate, scambiamo le virate e calcoliamo la coppia (riga, colonna) da riempire. Facciamo questo nel ciclo while iniziando dalla riga in basso (riga 5) e muovendoci verso l'alto fino a che il valore della tavola su (riga, colonna) è zero (il che implica che non è stato giocato). Quindi assegniamo il pid (uno o uno negativo) all'elemento sul tabellone ed emettiamo il far cadere evento su entrambi i giocatori. Aggiungiamo il far cadere gestore di eventi attivato frontend.js e introdurre un'animazione che ci regala un effetto di caduta.

socket.on ('drop', function (data) var row = 0; stopVal = setInterval (function () if (row == data.row) clearInterval (stopVal); fillBox (riga, data.colonna, dati. color); row ++;, 25);); function fillBox (riga, colonna, colore) $ ('[data-row = "' + (row-1) + '"] [data-column = "' + column + '"]'). css ('background' , "); $ ('[data-row ="' + row + '"] [data-column ="' + column + '"]'). css ('background', color); 

Implementiamo l'animazione di rilascio utilizzando JavaScript setInterval () metodo. Partendo dalla riga più in alto (riga zero), continuiamo a chiamare fillBox () a intervalli di 25 secondi fino al valore di riga è uguale al valore di data.row. Il fillBox funzione cancella lo sfondo dell'elemento precedente, nella stessa colonna e assegna uno sfondo all'elemento corrente. Successivamente, arriviamo al punto cruciale del gioco, implementando le condizioni vincenti e di estrazione. Copriremo questo nel backend.

// Funzione funzione di supporto getPair (riga, colonna, passaggio) l = []; per (var i = 0; i < 4; i++)  l.push([row, column]); row += step[0]; column += step[1];  return l;  // a list to hold win cases var check = []; 

Iniziamo definendo una funzione di supporto che restituisce quattro coppie (riga, colonna) orizzontalmente, verticalmente o diagonalmente. La funzione si aspetta la riga e la colonna corrente e una matrice che determina l'incremento nei valori di riga e colonna. Ad esempio, una chiamata a getPair (1,1, [1,1]) sarebbe tornato [[1,1], [2,2], [3,3], [4,4]] che sembra essere la diagonale giusta. In questo modo possiamo ottenere le rispettive coppie selezionando valori adatti per il passo array. Abbiamo anche dichiarato un elenco per contenere tutte le funzioni che controllano le vincite. Cominciamo esaminando la funzione che controlla le vittorie orizzontalmente e verticalmente.

check.push (function check_horizontal (room, row, startColumn, callback) for (var i = 1; i < 5; i++)  var count = 0; var column = startColumn + 1 - i; var columnEnd = startColumn + 4 - i; if(columnEnd > 6 || colonna < 0)  continue;  var pairs = getPair(row, column, [0,1]); for(var j = column; j < columnEnd + 1; j++)  count += games[room]['board'][row][j];  if(count == 4) callback(1, pairs); else if(count == -4) callback(2, pairs);  ); check.push(function check_vertical(room, startRow, column, callback)  for(var i = 1; i < 5; i++)  var count = 0; var row = startRow + 1 - i; var rowEnd = startRow + 4 - i; if(rowEnd > 5 || riga < 0)  continue;  var pairs = getPair(row, column, [1,0]); for(var j = row; j < rowEnd + 1; j++)  count += games[room]['board'][j][column];  if(count == 4) callback(1, pairs); else if(count == -4) callback(2, pairs);  ); 

Passiamo attraverso la funzione di cui sopra passo dopo passo. La funzione prevede quattro parametri per stanza, riga, colonna e il callback di successo. Per verificare una vincita in senso orizzontale, la cella su cui è stato fatto clic potrebbe contribuire a una condizione vincente in un massimo di quattro modi. Ad esempio, la cella in (5, 3) può risultare in una vincita in una delle seguenti quattro combinazioni: [[5,3], [5,4], [5,5], [5,6]], [[5,2], [5,3], [5,4], [5,5] ], [[5,1], [5,2], [5,3], [5,4]], [[5,0], [5,1], [5,2], [5, 3], [5,4]]. Il numero di combinazioni potrebbe essere inferiore per le condizioni di confine. L'algoritmo di cui sopra si occupa del problema in mano calcolando la colonna più a sinistra (variabile colonna) e destra più colonna (variabile columnEnd) in ognuna delle quattro possibili combinazioni.

Se la colonna più a destra è maggiore di sei, è fuori dalla griglia e quel passaggio può essere saltato. Lo stesso verrà fatto se la colonna più a sinistra è inferiore a zero. Tuttavia, se i casi limite cadono nella griglia, calcoliamo le coppie (riga, colonna) usando il getPair () funzione di aiuto definita in precedenza e quindi procedere per aggiungere i valori degli elementi alla lavagna. Ricordiamo che abbiamo assegnato un valore più uno sulla scacchiera per il giocatore uno e uno negativo per il giocatore due. Pertanto, quattro celle consecutive di un giocatore dovrebbero risultare in un conteggio di quattro o quattro negativi rispettivamente. Il callback è chiamato in caso di vittoria e viene passato due parametri, uno per il giocatore (uno o due) e l'altro per le coppie vincenti. La funzione che si occupa del controllo verticale è abbastanza simile a quella orizzontale, tranne per il fatto che controlla i casi limite in righe anziché in colonne.

Diagonali sinistra e destra

Passiamo alla definizione dei controlli per le diagonali sinistra e destra.

check.push (function check_leftDiagonal (room, startRow, startColumn, callback) for (var i = 1; i < 5; i++)  var count = 0; var row = startRow + 1 - i; var rowEnd = startRow + 4 - i; var column = startColumn + 1 - i; var columnEnd = startColumn + 4 - i; if(column < 0 || columnEnd > 6 || rowEnd> 5 || riga < 0)  continue;  var pairs = getPair(row, column, [1,1]); for(var j = 0; j < pairs.length; j++)  count += games[room]['board'][pairs[j][0]][pairs[j][1]];  if(count == 4) callback(1, pairs); else if(count == -4) callback(2, pairs);  ); check.push(function check_rightDiagonal(room, startRow, startColumn, callback)  for(var i = 1; i < 5; i++)  var count = 0; var row = startRow + 1 - i; var rowEnd = startRow + 4 - i; var column = startColumn -1 + i; var columnEnd = startColumn - 4 + i; if(column < 0 || columnEnd > 6 || rowEnd> 5 || riga < 0)  continue;  var pairs = getPair(row, column, [1,-1]); for(var j = 0; j < pairs.length; j++)  count += games[room]['board'][pairs[j][0]][pairs[j][1]];  if(count == 4) callback(1, pairs); else if(count == -4) callback(2, pairs);  ); 

I controlli per le diagonali sono abbastanza simili a quelli per il controllo orizzontale e verticale. L'unica differenza è che nel caso delle diagonali, controlliamo i casi limite per entrambe le righe e le colonne. Infine, definiamo una funzione per verificare la presenza di disegni.

// Funzione da verificare per la funzione draw check_draw (room, callback) for (var val in games [room] ['board'] [0]) if (val == 0) return;  richiama();  

Il controllo delle estrazioni è piuttosto banale. Un pareggio è ovvio se tutte le celle nella riga superiore sono state riempite e nessuno ha vinto. Quindi, escludiamo un'estrazione se nessuna delle celle nella riga superiore non è stata giocata e richiamiamo altrimenti la richiamata.

Con le condizioni di vincita e di estrazione risolte, ora dobbiamo utilizzare queste funzioni nell'evento click ed emettere a reset evento sul frontend per denotare i clienti alla fine del gioco. Modifichiamo l'evento click per gestire queste condizioni.

if (i> = 0 && data.column> = 0) / * Il codice precedente è saltato * / var win = false; check.forEach (function (method) method (results [2], i, data.column, function (player, pairs) win = true; if (player == 1) games [results [2]]. player1 .emit ('reset', text: 'You Won!', 'inc': [1,0], highlight: pairs); games [results [2]]. player2.emit ('reset', text : "You Lost!", "Inc": [1,0], evidenzia: coppie); else games [results [2]]. Player1.emit ('reset', text: 'You Lost!' , 'inc': [0,1], evidenzia: coppie); giochi [risultati [2]]. player2.emit ('reset', text: 'You won!', 'inc': [0,1 ], evidenzia: coppie); partite [risultati [2]]. board = [[0,0,0,0,0,0,0], [0,0,0,0,0,0,0 ], [0,0,0,0,0,0,0], [0,0,0,0,0,0,0], [0,0,0,0,0,0,0], [0,0,0,0,0,0,0]];);); se (vinci) ritorno;  check_draw (risultati [2], function () games [results [2]]. board = [[0,0,0,0,0,0,0], [0,0,0,0,0, 0,0], [0,0,0,0,0,0,0], [0,0,0,0,0,0,0], [0,0,0,0,0,0, 0], [0,0,0,0,0,0,0]]; io.sockets.in (risultati [2]). Emit ('reset', 'text': 'Game Drawn', 'inc ': [0,0]););  

Nel codice sopra, controlliamo una vittoria orizzontalmente, verticalmente e diagonalmente. In caso di vittoria, emettiamo il reset evento sul frontend con un messaggio appropriato per entrambi i giocatori. Il evidenziare proprietà contiene le coppie vincenti e il inc proprietà indica il punteggio di incremento per entrambi i giocatori. Per esempio, [1,0] denoterebbe il punteggio crescente del giocatore di uno e il punteggio di due giocatori per zero. 

Procediamo gestendo il reset evento sul frontend.

socket.on ('reset', function (data) if (data.highlight) setTimeout (function () data.highlight.forEach (function (pair) $ ('[data-row = "' + pair [ 0] + '"] [data-column ="' + pair [1] + '"]'). Css ('background-color', '# 65BD77'););, 500); setTimeout ( function () $ ('td'). css ('background-color', ") alertify.confirm (data.text, function (e) if (e) socket.emit ('continue'); else window.location = '/ landingPage';);, 1200) // Imposta punteggi p1 = parseInt ($ ('. p1-score p'). html ()) + data ['inc'] [0 ]; $ ('. p1-score p'). html (p1); p2 = parseInt ($ ('. p2-score p'). html ()) + data ['inc'] [1]; $ ( '.p2-score p'). html (p2);); 

Nel reset gestore di eventi, evidenziamo le coppie vincenti dopo 500ms. Il motivo del ritardo è di consentire il completamento dell'animazione di rilascio. Successivamente, ripristiniamo la scheda in background e apriamo una finestra di dialogo di conferma di notifica con il testo inviato dal back-end. Nel caso in cui l'utente decida di continuare, emetteremo il Continua evento sul lato server o reindirizzare il client alla pagina di destinazione. Procediamo quindi ad aumentare i punteggi dei giocatori incrementando il punteggio corrente in base ai valori ricevuti dal server.

Quindi, definiamo il Continua gestore di eventi nel back-end.

socket.on ('continue', function () socket.get ('turn', function (err, turn) socket.emit ('notify', connected: 1, turn: turn);); ); 

Il Continua gestore di eventi è abbastanza semplice. Emettiamo nuovamente l'evento di notifica e il gioco riprende nel frontend.

Quindi, decidiamo su cosa succede quando uno dei giocatori viene disconnesso. In questo scenario, dovremmo reindirizzare l'altro giocatore alla pagina di destinazione e rimuovere la stanza dal gamestate. Aggiungiamo questa funzionalità nel back-end.

socket.on ('disconnect', function () console.log ('Disconnected'); socket.get ('