Koble 4 med Socket.io

Spillet av Connect 4 bringer tilbake herre minner. Dette klassiske spillet har sikkert gitt et inntrykk av alle som har spilt det. I denne artikkelen skal vi lage en multiplayer-versjon av Connect 4 ved hjelp av Node.js og Socket.io.

Installere avhengighetene

Denne opplæringen antar at du har Node.js og npm installert. For å håndtere frontendavhengighetene bruker vi Bower til å hente pakken og Grunt for å administrere oppgaver. Åpne en terminal og installer Bower and Grunt globalt ved å utføre:

$ sudo npm installere -g bower grunt-cli

Merk: Grunt krever Node.js versjoner> = 0.8.0. På tidspunktet for denne artikkelen hadde Ubuntus repositorier en eldre versjon av Node. Sørg for at du bruker Chris Leas PPA hvis du er på Ubuntu. For andre distribusjoner / operativsystemer, se Node.js installasjonsdokumenter for å få den nyeste versjonen.

Med Bower og Grunt-cli installert, la oss lage en katalog for prosjektet og hente Twitter Bootstrap og Alertify.js (for å administrere varslingsvarsler) ved hjelp av Bower.

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

La oss nå sette opp en katalog for å administrere våre egendefinerte eiendeler. Vi nevner det eiendeler og lagre våre tilpassede Mindre og JavaScript-filer inni den.

$ mkdir -p eiendeler / javascript, stylesheets $ touch assets / javascript /frontend.js eiendeler / stilark /styles.less eiendeler / stilark / variables.less 

For å betjene de kompilerte eiendelene, oppretter vi en katalog som heter statisk med underkataloger kalt javascript og stilark.

$ mkdir -p statisk / javascript, stilark 

Åpen eiendeler / stilark / styles.less og importere variables.less og de nødvendige Mindre filene fra bootstrap.

// Kjernevariabler og mixins @import "... / ... /bower_components/bootstrap/less/variables.less"; @import "... / ... /bower_components/bootstrap/less/mixins.less"; // Tilbakestill @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"; // Komponenter @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 klasser @ import "... / ... /bower_components/bootstrap/less/utilities.less"; @import "... / ... /bower_components/bootstrap/less/responsive-utilities.less"; // Egendefinerte variabler @import "variables.less"; // Alertify @import (mindre) "... / ... /bower_components/alertify.js/themes/alertify.core.css"; @import (mindre) "... / ... /bower_components/alertify.js/themes/alertify.default.css"; // Tilpassede stiler 

Med det gjort, la oss sette opp Gruntfile.js å kompilere de mindre filene i CSS og kombinere alle JavaScript-filene til en enkelt fil. Den grunnleggende strukturen til Gruntfile.js fil med noen oppgaver, ser noe ut som dette:

// Gruntfile module.exports = funksjon (grunt) // Initialisering av konfigurasjonsobjektet grunt.initConfig (// Oppgave konfigurasjon mindre: // ..., konkat: // ..., se: // ... ); // Load plugins // Definer oppgaver; 

Asset Tasks

Vi definerer tre oppgaver for å administrere eiendelene. Den første vil være å kompilere alle de mindre filene til CSS. Den andre vil være å sammenkoble alle JavaScript-filer til en og til slutt vil den siste oppgaven være å se filer for endringer. Klokkeoppgaven ville være standardoppgaven og kan drives av å skrive grynte i prosjektrotten, når vi er ferdige med å konfigurere gruntfilen.

La oss sette opp en oppgave å kompilere alle Mindre filer til CSS-filer i statisk / stil katalog.

mindre: development: options: compress: true,, filer: "./static/stylesheets/styles.css": "./assets/stylesheets/styles.less",, 

Fortsett å sette opp en annen oppgave for å konkatisere alle JS-filene til en.

concat: opsjoner: ';',, 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'], dest:' ./static/javascript/frontend.js',,, 

Til slutt, la oss stille klokkeoppgaven til å se filene våre for endringer og utføre de nødvendige oppgavene på lagre.

se: 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'], oppgaver: [' concat: js '], mindre: filer: [' ./assets/stylesheets/*.less '], oppgaver: [' mindre '], 

Med det gjort, laster vi inn de nødvendige npm-pluginene og registrerer standardoppgaven.

// Last plugins grunt.loadNpmTasks ('grunt-contrib-concat'); grunt.loadNpmTasks ( 'grov-contrib-mindre'); grunt.loadNpmTasks ( 'grynt-contrib-watch'); // Definer oppgaver grunt.registerTask ('standard', ['watch']); 

La oss fortsette å administrere backend-avhengigheter ved hjelp av npm. For dette prosjektet bruker vi Express-rammeverket med Jade Templating Engine og Socket.io. Installer avhengighetene lokalt ved å utføre følgende kommando:

$ npm installere ekspress jade socket.io async grunt grunt-contrib-concat grunt-contrib-less grunt-contrib-watch 

Katalogstrukturen bør nå lignes på dette:

Nå som vi har vår avhengighet satt opp, er det på tide å fortsette å gjøre frontenden av spillet vårt.

Frontend

La oss fortsette ved å opprette en fil som heter server.js og server innhold ved hjelp av Express.

var express = kreve ('express'); var async = krever ('async'); var app = express () var io = krever ('socket.io'). listen (app.listen (8000)); app.use ('/ static', express.static (__ dirname + '/ static')); app.get ('/', funksjon (req, res) res.render ('index.jade');); app.get ('/ landingPage', funksjon (req, res) res.render ('landing.jade');); console.log ('Lytter på port 8000'); 

Maler

Vi bruker Jade Templating Engine til å administrere maler. Som standard ser Express etter visninger inne i visninger katalogen. La oss lage visninger katalog og opprett Jade-filer for layout, indeks og takksiden.

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

La oss deretter redigere utformingen av prosjektet vårt, indekssiden og destinasjonssiden (landing.jade).

doktype html html (lang = "en") hodetittel Koble 4 link (rel = 'stylesheet', href = "static / stylesheets / styles.css") body #wrap nav.navbar.navbar- ) .container-fluid .navbar-header a.navbar-brand (href = '#') Koble 4 blokkinnhold #footer .container p.text-muted | Utviklet av | Gaurav Narula for Nettuts blokk javascript script (src = '/ socket.io/socket.io.js') skript (src = 'static / javascript /frontend.js') 
utvider layoutblokkinnhold .container .row .col-xs-3 .p1-score på 0 # board.col-xs-6 table.center-table .form-gruppelabel (for = "shareUrl"). col-sm- 3.control-label.share-label Del URL: .col-sm-9 input (type = 'tekst' ReadOnly) .form-kontroll .col-xs-3 .p2-score p 0 
utvider layoutblokkinnhold .jumbotron .container 

Takk skal du ha!

Takk for at du spilte! Vi håper du likte spillet!

blokkér javascript

Legg merke til at vi serverer socket.io.js, selv om det ikke er definert hvor som helst i statisk katalogen. Dette skyldes at socket.io modulen administrerer automatisk serveringen avsocket.io.js klientfil.

The Styling

Nå som vi har HTML-oppsettet, la oss gå videre til å definere stilene. Vi begynner med å overskrive noen bootstrap-variabler med verdiene av vårt valg inni eiendeler / stilark / variables.less.

@ body-bg: # F1F1F1; @ tekstfarge: # 717171; @ overskrift-farge: # 333; @ varemerker: # 468847; @ merkevaresuksess: # 3A87AD; @ varemerkevarsel: # FFC333; @ merkevarefare: # FB6B5B; @ navbar-default-bg: # 25313E; @ navbar-standard-farge: #ADBECE; @ navbar-standard-link-farge: @ navbar-standard-farge; @ navbar-default-link-hover-farge: # 333; 

Deretter legger vi til noen egendefinerte stiler til styles.less.

// Tilpassede stiler / * Sticky Footer * / html, kropp høyde: 100%;  / * Wrapper for sideinnhold å presse ned footer * / #wrap min-høyde: 100%; høyde: auto; margin: 0 auto -60px; polstring: 0 0 60px;  #footer høyde: 60px; bakgrunnsfarge: # 65BD77; > .container polstring-venstre: 15px; polstring-høyre: 15px;  .container .text-muted margin: 20px 0; farge: #fff;  // Grid table border-collapse: separate; border spacing: 10px 10px;  tabell tr ​​margin: 10px;  tabell tr ​​td bredde: 50px; høyde: 50px; grense: 1px solid # 3A87AD;  .center-table margin: 0 auto! important; flyte: ingen! viktig;  .p1-poengsum, .p2-score polstring: 185px 0; bredde: 50px; høyde: 50px; skriftstørrelse: 25px; linjehøyde: 50px; farge: #fff; tekst-align: center;  .p1-score float: right; p bakgrunn: # FFC333; .strøm grense: 5px solid mørkere (# FFC333, 10%);  .p2-poengsum p bakgrunn: # FB6B5B; .strøm grense: 5px solid mørkere (# FB6B5B, 10%);  .share-label linjehøyde: 34px; tekstjustering: høyre;  

JavaScript

Med det gjort, la oss legge til noen JavaScript-kode i eiendeler / javascript /frontend.js å opprette nettet og legge til data-rad og data-kolonne attributter med riktige verdier dynamisk.

$ (dokument) .ready (funksjon () 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);   ); 

Det dekker frontend-oppsettet. La oss kompilere eiendommene og starte serveren.

$ grunt mindre concat: js $ node server.js 

Hvis du har fulgt med, bør indekssiden se slik ut:

Tips: Kjør grynte kommandoen på prosjektrotten på en separat terminal. Det ville kalle standardoppgaven som skjer med å se på. Dette vil konkatisere alle JS-filer eller kompilere alle Mindre filer på hver lagre.

The Backend

Målet i Connect 4 er å koble fire påfølgende "blokker" enten horisontalt, vertikalt eller diagonalt. Socket.io tillater oss å lage rom at klienter kan bli med. Tenk på dem som IRC-kanaler. 

Spillrommene

Vi bruker denne funksjonen i spillet, slik at bare to spillere kan være i et rom og rommet blir ødelagt når en av dem slutter. Vi lager et objekt som vil holde orden på alle rom og i sin tur alle spill-stater. La oss begynne med å skape en funksjon i server.js å lage tilfeldige romnavn.

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

Funksjonen forventer lengden på tilfeldig romnavnet vi ønsker å generere. Romnavnet er generert ved å sammenkoble tilfeldige tegn fra høystakk streng. La oss endre vår rute for indekssiden for å inkludere delingsadressen og opprette en ny rute for å betjene innholdet hvis et bestemt rom er åpnet.

app.get ('/', funksjon (req, res) share = generateRoom (6); res.render ('index.jade', shareURL: req.protocol + ': //' + req.get vert ") + req.path + dele, dele: dele);); app.get ('/: rom ([A-Za-z0-9] 6)', funksjon (req, res) share = req.params.room; res.render ('index.jade' shareURL: req.protocol + ': //' + req.get ('host') + '/' + del, del: del);); 

I ovennevnte kode genererer vi delings-IDen ved hjelp av generateRoom () funksjon definert tidligere og passere i delings-ID og URL som parametere til malen. Den andre ruten forventer en parameter som heter rom som er begrenset av et regulært uttrykk. Regex tillater en streng som bare inneholder alfanumeriske tegn, med lengde seks. Igjen passerer vi på shareURL og id som parametere til malen. La oss legge til noen attributter til inngangselementet i indeksen vår, slik at vi får tilgang til dem i frontend.js seinere.

input (type = 'tekst', datarom = del, navn = "shareUrl", verdi = shareURL ReadOnly) .form-kontroll

Deretter la vi redigere frontend.js For å koble til Socket.io-serveren, bli med i rommet og tilordne noen egenskaper til gjeldende spiller.

var socket = io.connect ('localhost'); funksjon Spiller (rom, pid) this.room = room; this.pid = pid;  var room = $ ('input'). data ('rom'); var spiller = ny spiller (rom, ","); socket.on ('connect', funksjon () socket.emit ('join', rom: rom);); socket.on ('assign', funksjon (data) player.color = data.color; player.pid = data.pid; if (player.pid == 1) $ ('.p1-score p'). addClass ('current'); else $ ('. p2-score p'). addClass ('nåværende');); 

Legg merke til at vi opprettet et objekt som heter spiller å referere til spilleren på klientsiden. Ved tilkobling kalles sluttbegivenheten på baksiden som inturn utsender tildelingen avgir på frontenden for å tilordne noen egenskaper til spilleren. Vi kan nå fortsette å definere koden i backend for å håndtere bli med begivenhet.

// et objekt for å holde alle gamestates. Nøkkel angir rom id var spill = ; io.sockets.on ('connection', funksjon (socket) socket.on ('join', funksjon (data) hvis (data.room i spill) if (typeof spill [data.room] .player2! = "undefined") socket.emit ('leave'); return; socket.join (data.room); socket.set ('rom', data.rom); socket.set ('color', '# FB6B5B '); socket.set (' pid ', -1); spill [data.room] .player2 = socket // Sett motstandere socket.set (' opponent ', spill [data.room] .player1); spill [data .rom] .player1.set ('motstander', spill [data.room] .player2); // Sett sving socket.set ('turn', false); socket.get ('opponent', funksjon (feil, motstander ) opponent.set ('turn', sant);); socket.emit ('assign', pid: 2); annet socket.join (data.room); socket.set , data.room); socket.set ('color', '# FFC333'); socket.set ('pid', 1); socket.set ('turn', false); spill [data.room] =  player1: socket, 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]],; socket.emit ('assign', pid: 1););); 

Merk: Socket.io event handlers på backend bør legges inn i io.sockets.on ('forbindelse', funksjon (socket)  kode blokk. På samme måte bør hendelseshåndtereren og frontend-JavaScript-koden være inne i $ (dokument) .ready (funksjon () kode blokk.

I koden ovenfor definerte vi hendelsesbehandleren for bli med hendelse som sendes ut av frontenden. Det kontrollerer om det gjeldende rommet allerede eksisterer, og om spilleren to ikke allerede er tildelt og i så fall tilordner gjeldende klient som spiller to. Ellers tilordner den gjeldende klienten som spiller en og initialiserer styret. Vi sender ut permisjon Hendelse på frontend for klienter som forsøker å bli med i et pågående spill. Vi har også satt noen egenskaper på kontakten ved hjelp av socket.set (). Disse inkluderer rom id, farge, pid og sving variabelen. Egenskapene som er angitt på denne måten, kan hentes fra tilbakekallingen til socket.get (). Deretter legger vi til permisjon hendelseshåndterer på frontenden.

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

De permisjon Hendelsesbehandler omdirigerer klienten enkelt til destinasjonssiden. Vi fortsetter nå med å avgive et arrangement som varsler begge spillerne om spillet er klar til å begynne. La oss legge til noen kode til hvis betingelse for å bli med på vår server-side.

Hvis (data.room i spill) // ... legger til koden som eksisterer // Melde spill [data.room] .player1.emit ('notify', connected: 1, turn: true); socket.emit ('notify', connected: 1, turn: false);  

Vi må definere en gi beskjed hendelse i frontend som omhandler varselet. Alert.js gir en fin måte å håndtere alle meldingene på. La oss legge til varslingshendelen i frontend.js.

socket.on ('notify', funksjon (data) if (data.connected == 1) if (data.turn) alertify.success ('Spillere koblet! Din tur'); ellers alertify.success Motstanderens tur ');); 

Tid til å prøve vår fremgang så langt. Start serveren lokalt og få tilgang til localhost og delingsadressen i to separate vinduer. Hvis du har fulgt med, bør du bli møtt med et varsel i nederste høyre hjørne, som vist på bildet nedenfor:

Legger til interaktivitet

La oss nå fortsette å legge til kode som avgir en hendelse når blokkene klikkes. For denne delen må vi avgjøre om klikket ble laget av riktig spiller. Det er her sving eiendom vi satt på stikkontakten ville komme til spill. Legg til følgende kode til frontend.js.

$ ('.boks'). klikk (funksjon () // finn boksen for å slippe platen til var klikk = rad: $ (dette) .data ('rad'), kolonne: $ (dette) .data ('kolonne'); socket.emit ('klikk', klikk);); 

Koden ovenfor angir en hendelseshåndterer på alle tabellceller. En ting å merke seg er at rutenettet i Connect 4 ligner på å legge murstein i en vegg, det vil si at man ikke kan fylle et bestemt (rad, kolonne) par hvis (rad-1, kolonne) -paret ikke er fylt. Derfor må vi først få det (rad, kolonne) paret i cellen som ble klikket, og deretter utarbeide en måte å bestemme den faktiske cellen som skal fylles ut. Dette gjøres i backend, i tilfelle handler for klikk.

socket.on ('klikk', funksjon (data) async.parallel ([socket.get.bind (this, 'turn'), socket.get.bind (denne motstanderen), socket.get.bind dette, 'rom'), socket.get.bind (dette, 'pid')], funksjon (feil, resultater) if (results [0]) socket.set ('turn', false); resultater [1 ] .set ('turn', true); var i = 5; mens (i> = 0) if (spill [resultater [2]]. bord [i] [data.column] == 0) break;  i--; hvis (i> = 0 && data.column> = 0) spill [resultater [2]]. bord [i] [data.column] = resultater [3]; socket.get ', funksjon (feil, farge) socket.emit (' drop ', rad: i, kolonne: data.column, color: color); resultater [1] .emit (' drop ' kolonne: data.column, color: color);; ellers console.log ('Opponentens tur'););); 

Ovennevnte hendelsehandler bruker async-modulen til å hente sokkelegenskapene samtidig. Dette unngår nesting tilbakeringinger i etterfølgende bruk av socket.get (). De resultater variabel er en matrise med elementer i samme rekkefølge som socket.get () samtaler. Resultatene [0], refererer derfor til sving og så videre. 

Når egenskapene er hentet, bytter vi svingene og finner ut (rad, kolonne) paret for å fylle. Vi gjør dette i løpet av runden ved å starte fra den nederste raden (rad fem) og bevege seg oppover til verdien av brettet i (rad, kolonne) er null (noe som betyr at den ikke har blitt spilt på). Vi tilordner deretter pid (enten en eller negativ) til elementet på brettet og avgir miste arrangement på begge spillerne. La oss legge til miste hendelseshåndterer på frontend.js og introdusere en animasjon som gir oss en fallende effekt.

socket.on ('drop', funksjon (data) var row = 0; stopVal = setInterval (funksjon () hvis (rad == data.row) clearInterval (stopVal); fillBox (rad, data.column, data. farge); rad ++;, 25);); Funksjon fillBox (rad, kolonne, farge) $ ('[data-rad = "' + (rad-1) + '"] [data-kolonne = "' + kolonne + '" , '); $ (' [data-rad = "'+ rad +'"] [datakolonne = "'+ kolonne +'"] '). css (' bakgrunn ', farge); 

Vi implementerer drop animasjonen ved hjelp av JavaScript setInterval () metode. Fra den øverste rad (rad null) fortsetter vi å ringe fillBox () i intervaller på 25 sekunder til verdien av rad tilsvarer verdien av data.row. De fillBox funksjonen fjerner bakgrunnen til det forrige elementet, i samme kolonne og tilordner en bakgrunn til det nåværende elementet. Deretter kommer vi til crux av spillet, implementering av vinnende og tegneforhold. Vi vil dekke dette i backend.

// Hjelperfunksjon funksjon getPair (rad, kolonne, trinn) l = []; for (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 = []; 

Vi begynner med å definere en hjelperfunksjon som returnerer fire (rad, kolonne) par enten horisontalt, vertikalt eller diagonalt. Funksjonen forventer nåværende rad og kolonne og en matrise som bestemmer økningen i rad og kolonneverdier. For eksempel, et anrop til getPair (1,1, [1,1]) ville returnere [[1,1], [2,2], [3,3], [4,4]] som skjer med riktig diagonal. På denne måten kan vi få respektive par ved å velge passende verdier for skritt array. Vi har også erklært en liste for å beholde alle funksjonene som ser etter vinner. La oss begynne med å gå gjennom funksjonen som sjekker for vinner horisontalt og vertikalt.

check.push (funksjon check_horizontal (rom, rad, start kolonne, tilbakeringing) for (var i = 1; i < 5; i++)  var count = 0; var column = startColumn + 1 - i; var columnEnd = startColumn + 4 - i; if(columnEnd > 6 || kolonne < 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 || rad < 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);  ); 

La oss gå gjennom funksjonen ovenfor trinn for trinn. Funksjonen forventer fire parametre for rom, rad, kolonne og suksess tilbakeringing. For å sjekke om en seier vandret, kan cellen som ble klikket på, bidra til en vinnende tilstand på maksimalt fire måter. For eksempel kan cellen ved (5, 3) resultere i en seier i en av følgende fire kombinasjoner: [[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]]. Antall kombinasjoner kan være mindre for grensebetingelsene. Algoritmen ovenfor omhandler problemet ved hånden ved å beregne venstre mest kolonne (variabel kolonne) og høyre mest kolonne (variabel columnEnd) i hver av de fire mulige kombinasjonene.

Hvis den høyeste kolonnen er større enn seks, er den av rutenettet og passet kan hoppes over. Det samme vil bli gjort hvis venstre mest kolonne er mindre enn null. Men hvis kantsaker faller i nettet, beregner vi parene (rad, kolonne) ved hjelp av getPair () hjelperfunksjonen vi definerte tidligere, og fortsett deretter for å legge til verdiene av elementene på brettet. Husk at vi tildelte en verdi pluss en på brettet for spiller en og negativ for spilleren to. Derfor bør fire påfølgende celler av en spiller resultere i en telling på henholdsvis fire eller negative fire. Tilbakeringingen kalles i tilfelle en seier og er bestått to parametre, en for spilleren (enten en eller to) og den andre for de vinnende parene. Funksjonen som omhandler den vertikale kontrollen er ganske lik den horisontale, bortsett fra at den kontrollerer kantsakerne i rader i stedet for kolonner.

Venstre og høyre diagonaler

La oss fortsette å definere sjekker for venstre og høyre diagonaler.

check.push (funksjon check_leftDiagonal (rom, startRow, startColumn, tilbakeringing) 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 || rad < 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 || rad < 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);  ); 

Kontrollene for diagonaler er ganske lik dem for horisontal og vertikal kontroll. Den eneste forskjellen er at i tilfelle diagonaler kontrollerer vi kantsaker for både rader og kolonner. Endelig definerer vi en funksjon for å sjekke for trekk.

// Funksjon for å sjekke for tegnefunksjon check_draw (rom, tilbakeringing) for (var val i spill [rom] ['board'] [0]) hvis (val == 0) tilbake;  Ring tilbake();  

Å sjekke for trekker er ganske trivielt. Uavgjort er åpenbart dersom alle cellene i toppraden er fylt og ingen har vunnet. Dermed utelukker vi uavgjort dersom noen av cellene i øverste rad ikke er spilt på og ringe tilbakekallingen ellers.

Med de vinnende og trekningsbetingelsene sortert, må vi nå bruke disse funksjonene i klikkhendelsen og avgi a tilbakestille hendelse på frontenden for å betegne klientene på slutten av spillet. La oss redigere klikkhendelsen for å håndtere disse forholdene.

hvis (i> = 0 && data.column> = 0) / * Tidligere kode hoppet over * / var win = false; check.forEach (funksjon (metode) metode (resultater [2], jeg, data.column, funksjon (spiller, par) win = true; if (player == 1) spill [resultater [2]]. .emit ('reset', text: 'You Won!', 'inc': [1,0], høydepunkt: par); spill [resultater [2]]. player2.emit ('reset' : 'Du har mistet!', 'Inc': [1,0], høydepunkt: par); else spill [resultater [2]]. Player1.emit ('reset', text: 'You Lost!' , 'inc': [0,1], høydepunkt: par); spill [resultater [2]]. player2.emit ('reset', text: 'You Won!', 'inc': [0,1 ], høydepunkt: par); spill [resultater [2]]. bord = [[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]];);); hvis (seier) return;  check_draw (resultater [2], funksjon () spill [resultater [2]]. bord = [[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 (resultater [2]). Emit ('reset', 'text': 'Game Drawn', 'inc ': [0,0];;  

I koden ovenfor ser vi etter en seier horisontalt, vertikalt og diagonalt. I tilfelle en seier sender vi ut tilbakestille Begivenhet på frontenden med en passende melding for begge spillerne. De fremheve Eiendommen inneholder de vinnende parene og inc Egenskapen angir stigningspoeng for begge spillerne. For eksempel, [1,0] ville betegne økende spillerens poengsum med en og spillers to score med null. 

La oss fortsette med å håndtere tilbakestille hendelse på frontenden.

socket.on ('reset', funksjon (data) if (data.highlight) setTimeout (funksjon () data.highlight.forEach (funksjon (par) $ ('[data-rad = 0 + '"] [data-kolonne ="' + par [1] + '"]'). Css ('bakgrunnsfarge', '# 65BD77'););, 500); setTimeout funksjon () $ ('td'). css ('bakgrunnsfarge', ') alertify.confirm (data.text, funksjon (e) hvis (e) socket.emit (' fortsette '); else window.location = '/ landingPage';;; 1200) // Angi poeng 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);); 

I tilbakestille Event Handler, vi markerer de vinnende parene etter 500ms. Årsaken til tidsforsinkelsen er å la slipp animasjonen slutte. Deretter tilbakestilles brettet i bakgrunnen, og dukker opp en varslingsbekreftelsesdialog med teksten som sendes fra baksiden. Hvis brukeren bestemmer seg for å fortsette, sender vi ut Fortsette hendelse på server siden eller omdirigere klienten til destinasjonssiden. Vi fortsetter da for å øke spillerpoengene ved å øke dagens poengsum med de mottatte verdiene fra serveren.

Neste, la oss definere Fortsette hendelse handler i backend.

socket.on ('fortsett', funksjon () socket.get ('turn', funksjon (feil, sving) socket.emit ('notify', connected: 1, turn: turn;); ); 

De Fortsette hendelse handler er ganske grei. Vi sender meldingen om igjen, og spillet gjenopptas i frontenden.

Neste, la oss bestemme hva som skjer når en av spillerne blir frakoblet. I dette scenariet bør vi omdirigere den andre spilleren til destinasjonssiden og fjerne rommet fra spillestedet. La oss legge til denne funksjonen i backend.

socket.on ('disconnect', funksjon () console.log ('Disconnected'); socket.get ('rom', funksjon (feil, rom) io.sockets.in ); hvis (rom i spill) slett spill.rom;;;); 

Ovennevnte hendelsehandler ville kringkaste permisjon arrangement til de andre spillerne og fjern rommet fra spillets objekt, hvis det fortsatt eksisterer.

Konklusjon

Vi har dekket ganske mye grunnlag i denne opplæringen, og starter med å få avhengighetene, skape noen oppgaver, bygge front og back-end og slutte med et ferdig spill. Med det sagt, antar jeg det er på tide for dere å ha noen runder med vennene dine! Jeg vil gjerne svare på dine spørsmål i kommentarene. Ta gjerne på min repo på GitHub og improvisér på koden. Det var alt folkens!