Shakkimikropalvelun kirjoittaminen Node.js: n ja Senecan osan 1 avulla

(Tämä on kolmiosaisen sarjan osa 1 [osa 2, osa 3])

Olen alkanut kietoa pääni mikropalvelujen ympärille. Tähän asti pidin sitä skaalautuvuusmallina ja unohdin sen takana olevat toiminnalliset ohjelmointiperiaatteet.

Shakin säännöt voidaan hajottaa helposti mikropalveluiksi. Ne eivät ole satunnaisia ​​eivätkä epäselviä, mikä sopii täydellisesti pienten, valtiottomien palveluiden kirjoittamiseen, jotka käsittelevät eri kappaleiden liikkeitä.

Tässä viestissä käyn läpi useita luomiani palveluita, jotka määrittävät, mitkä ovat lailliset liikkeet yksinäisille paloille tyhjällä shakkilaudalla. Käytämme Seneca frameworkia, Node.js: n mikropalvelutyökalupakettia, koska se on intuitiivinen ja hyvin dokumentoitu.

Senecan perustaminen

Seneca on Node.js-moduuli, joka asennetaan npm: llä:

npm install seneca

Luotamme myös globaalisti asennettuihin mokka / chai-moduuleihin testeissä, jotka havainnollistavat toimivuutta.

Löydä kaikki lailliset siirrot

Shakkilaudan muistissa olevaa esitystä ei oikeastaan ​​tarvitse ylläpitää, vain palaset ja niiden sijainti 8x8-koordinaattiruudukossa. Algebrallista merkintää käytetään yleisesti kuvaamaan koordinaatit shakkilaudalla, jossa tiedostot on merkitty kirjaimilla ja rivit numeroilla:

Valkoisen pelaajan oikeassa alakulmassa on h1; mustalle se on a8. Torilla b2, joka siirtyy neliöön f2, merkitään Rb2-f2.

Raaka liikkuu

Olen määrittelemällä raaka liikkuu kuin liikkuu pala tekisi jos vaikeuttavat muita kappaleita tai laudan reunaan . Tämä viimeinen bitti saattaa tuntua oudolta, mutta se antaa minun rakentaa 15x15-liikemaski, joka katkaistaan ​​sitten sopivaksi 8x8-levylle. Procrustes-niminen kaveri esitti samanlaisen idean jo kauan sitten.

Kuninkaat, kuningattaret, piispat ja Rookit liikkuvat diagonaaleja ja / tai tiedostoja pitkin, joten käytän yhtä palvelua näiden neljän kappaleen liikkeisiin. Sotilailla on ainutlaatuiset liikkumisominaisuudet, joten heille käytetään erityispalvelua. Sama pätee ritarit, koska he voivat hypätä kappaleen yli eivätkä liikkua tiedostojen tai riveissä.

Torni voi esimerkiksi siirtää 7 ruutua mitä tahansa riviä tai tiedostoa pitkin 15x15-levyllä, jossa torni on keskitetty. Samanlaisia ​​sääntöjä sovelletaan piispaan ja kuningattareen. Kuningas on rajoitettu yhden neliön alueelle mihin tahansa suuntaan (poikkeus on linna, jota käsittelen tulevassa viestissä).

Käytän ChessPieceluokkaa pitämään tietoa jokaisen shakkikappaleen tyypistä ja sijainnista. Sillä ei ole toistaiseksi liian merkittävää roolia, mutta myöhemmin, kun laajennan palvelujen kattamien sääntöjen soveltamisalaa.

Ensimmäinen palvelu: Torni, piispa, kuningatar ja kuningas liikkuvat

Senecassa palveluihin vedotaan kautta roleja cmd. Se rolemuistuttaa luokkaa ja cmdnimeää tietyn palvelun. Kuten näemme myöhemmin, palvelu voidaan määrittää edelleen lisäparametreilla.

Palvelut lisätään seneca.add()ja niitä kutsutaan kautta seneca.act(). Tarkastellaan ensin palvelua (Movement.js: ltä):

 this.add({ role: "movement", cmd: "rawMoves", }, (msg, reply) => { var err = null; var rawMoves = []; var pos = msg.piece.position; switch (msg.piece.piece) { case 'R': rawMoves = rankAndFile(pos); break; case 'B': rawMoves = diagonal(pos); break; case 'Q': rawMoves = rankAndFile(pos) .concat(diagonal(pos)); break; case 'K': rawMoves = rankAndFile(pos, 1) .concat(diagonal(pos, 1)) break; default: err = "unhandled " + msg.piece; break; }; reply(err, rawMoves); });

Katsotaan nyt, kuinka testi kutsuu palvelua (moveTest.js):

 var Ba1 = new ChessPiece('Ba1'); seneca.act({ role: "movement", cmd: "rawMoves", piece: Ba1 }, (err, msg) => {...});

Huomaa, että lisäksi roleja cmdsiellä onpiecePerustelu. Tämä sekä - roleja - cmdominaisuudet ovatmsgPalvelun vastaanottama argumentti. Ennen kuin voit käyttää palvelua, sinun on kuitenkin kerrottava Senecalle mitä palveluja käytetään:

var movement = require(‘../services/Movement’) const seneca = require('seneca')({ log: 'silent' }) .use(movement);

Piispan neliön a1 raakaliikkeet ovat msgvastaanotettuja takaisin palvelusta:

[{tiedosto: '' ', sijoitus:' 0 '},

{tiedosto: 'b', sijoitus: '2'},

{tiedosto: '' ', sijoitus:' 2 '},

{tiedosto: 'b', sijoitus: '0'},

{tiedosto: '_', sijoitus: '/'},

{tiedosto: 'c', sijoitus: '3'},

{tiedosto: '_', sijoitus: '3'},

{tiedosto: 'c', sijoitus: '/'},

{tiedosto: '^', sijoitus: '.' },

{tiedosto: 'd', sijoitus: '4'},

{tiedosto: '^', sijoitus: '4'},

{tiedosto: 'd', sijoitus: '.' },

{tiedosto: ']', sijoitus: '-'},

{tiedosto: 'e', ​​sijoitus: '5'},

{tiedosto: ']', sijoitus: '5'},

{tiedosto: 'e', ​​sijoitus: '-'},

{tiedosto: '\\', sijoitus: ','},

{tiedosto: 'f', sijoitus: '6'},

{tiedosto: '\\', sijoitus: '6'},

{tiedosto: 'f', sijoitus: ','},

{tiedosto: '[', sijoitus: '+'},

{tiedosto: 'g', sijoitus: '7'},

{tiedosto: '[', sijoitus: '7'},

{tiedosto: 'g', sijoitus: '+'},

{tiedosto: 'Z', sijoitus: '*'},

{tiedosto: 'h', sijoitus: '8'},

{tiedosto: 'Z', sijoitus: '8'},

{tiedosto: 'h', sijoitus: '*'}]

Huomaa, että luettelossa on joitain outoja neliöitä! Nämä ovat paikat, jotka “putoavat” 8x8-levyltä ja poistuvat myöhemmin toisesta palvelusta.

Mitä juuri tapahtui?

Palvelu määritettiin role=”movement”ja cmd=”rawMoves”. Kun act()sitä myöhemmin kutsutaan, toimintopyynnön parametrit sovitetaan palvelua vastaan, joka käsittelee näitä parametreja (tätä kutsutaan palvelun malliksi ). Kuten aiemmin mainittiin ja kuten seuraavassa esimerkissä osoitetaan,roleja cmdeivät välttämättä ole ainoat parametrit, jotka määräävät palvelun.

Seuraavat palvelut: Sotilaiset ja ritarit

Sotilaiset liikkuvat yhden neliön eteenpäin, elleivät ne ole alkuperäisellä neliöllä, jolloin he voivat siirtää yhtä tai kahta neliötä eteenpäin. On muitakin liikkeitä, joita sotilas voi tehdä, kun se ei ole ainoa pala tyhjällä laudalla, mutta se on tulevaa harkintaa varten. Sotilaiset alkavat aina toisella listalla eivätkä voi koskaan liikkua taaksepäin.

Knights move in an L-shape pattern. In our imaginary 15x15 board with the knight centered, there will always be eight possible moves.

I’ll write two services (one for pawns, the other for knights) and place both in one module (SpecialMovements.js):

module.exports = function specialMovement(options) { //... this.add({ role: "movement", cmd: "rawMoves", isPawn: true }, (msg, reply) => { if (msg.piece.piece !== 'P') { return ("piece was not a pawn") } var pos = msg.piece.position; const rawMoves = pawnMoves(pos); reply(null, rawMoves); }); this.add({ role: "movement", cmd: "rawMoves", isKnight: true }, (msg, reply) => { if (msg.piece.piece !== 'N') { return ("piece was not a knight") } var rawMoves = []; var pos = msg.piece.position; rawMoves = knightMoves(pos); reply(null, rawMoves); }); }

See the isPawnand isKnightparameters in the services? The first object passed to Seneca add() is called the service pattern. What happens is that Seneca will invoke the service with the most specific pattern match. In order to invoke the right service, I need to addisPawn:true or isKnight:true to the act request:

var movement = require('../services/Movement') var specialMovement = require('../services/SpecialMovement') const seneca = require('seneca')({ log: 'silent' }) .use(specialMovement) ... var p = new ChessPiece('Pe2'); seneca.act({ role: "movement", cmd: "rawMoves", piece: p, ... isPawn: true }, (err, msg) => {...} ... var p = new ChessPiece('Nd4'); seneca.act({ role: "movement", cmd: "rawMoves", piece: p, isKnight: true }, (err, msg) => {

Legal Moves

Our rudimentary legal move service will just filter out all the square positions that are not on files a-h or ranks 1–8. The legal move service will be called directly with a ChessPiece instance as part of the service payload. The legal move service will then invoke the raw move service to get the movement mask. The mask will be truncated to the edges of the board, and the result will be the square positions that can legally be played.

 this.add({ role: "movement", cmd: "legalSquares", }, (msg, reply) => { const isPawn = msg.piece.piece === 'P'; const isKnight = msg.piece.piece === 'N'; this.act({ role: "movement", cmd: "rawMoves", piece: msg.piece, isPawn: isPawn, isKnight: isKnight }, (err, msg) => { const squared = []; msg.forEach((move) => { if (move.file >= 'a' && move.file = 1 && move.rank <= 8) { squared.push(move) } } }) reply(null, squared); }); })

The legalSquaresservice first invokes the rawMovesservice. This gets us the 15x15 movement mask for whatever piece is passed via the msg parameter. It is important, though, that the right service is invoked by setting the isKnightor isPawnpattern field to true for either of those two pieces… if both are false, then the “regular” rawMovesservice for K,Q,B,R will be invoked.

Once the raw moves are retrieved, then the legalSquaresservice removes the invalid positions and returns what is left. So if I invoke the service with the piece at Na1, I get:

[ { file: ‘c’, rank: ‘2’ }, { file: ‘b’, rank: ‘3’ } ]

If instead I pass in Rd4, legalSquares returns:

[ { file: ‘c’, rank: ‘4’ },

{ file: ‘d’, rank: ‘5’ },

{ file: ‘e’, rank: ‘4’ },

{ file: ‘d’, rank: ‘3’ },

{ file: ‘b’, rank: ‘4’ },

{ file: ‘d’, rank: ‘6’ },

{ file: ‘f’, rank: ‘4’ },

{ file: ‘d’, rank: ‘2’ },

{ file: ‘a’, rank: ‘4’ },

{ file: ‘d’, rank: ‘7’ },

{ file: ‘g’, rank: ‘4’ },

{ file: ‘d’, rank: ‘1’ },

{ file: ‘d’, rank: ‘8’ },

{ file: ‘h’, rank: ‘4’ } ]

which is a little harder to decipher, but contains all files along the 4th rank and all ranks along the d-file (trust me!).

Se on nyt! Tulevassa postauksessa käsittelen palveluja, jotka käsittelevät liikkumista estäviä ystävällisiä kappaleita ja käsittelen sitten vihamielisten kappaleiden mahdollista kaappaamista. Muut palvelut käsittelevät sääntöjä, jotka koskevat linnaa, kuljettajaa , sekkiä, matoa ja umpikujaa.

Kaikki lähdekoodit löytyvät täältä.

Jatka tämän sarjan osaan 2.