Malemikroteenuse kirjutamine Node.js ja Seneca 1. osa abil

(See on kolmeosalise seeria 1. osa [2. osa, 3. osa])

Olen hakanud oma pead mikroteenuste ümber mähkima. Siiani pidasin seda mastaapsuse mustriks ja jätsin kahe silma vahele selle taga olevad funktsionaalsed programmeerimispõhimõtted.

Malereeglid saab hõlpsasti jaotada mikroteenusteks. Need ei ole juhuslikud ega mitmetähenduslikud, mis sobib suurepäraselt väikeste kodakondsuseta teenuste kirjutamiseks, mis käsitlevad erinevate tükkide liikumist.

Selles postituses tutvun mitmete loodud teenustega, mis määravad, millised on tühja malelaua üksikute palade seaduslikud käigud. Kasutame Node.js-i jaoks mikroteenuste tööriistakomplekti Seneca framework, kuna see on intuitiivne ja hästi dokumenteeritud.

Seneca seadistamine

Seneca on Node.js moodul, mis on installitud npm abil:

npm install seneca

Samuti toetume funktsionaalsust illustreerivate testide jaoks globaalselt installitud mocha / chai moodulitele.

Leidke kõik seaduslikud käigud

Malelaua mälusisest esitamist pole tegelikult vaja säilitada, vaid tükid ja nende asukoht 8x8 koordinaatvõrgus. Algebralist tähistust kasutatakse tavaliselt malelaua koordinaatide kirjeldamiseks, kus failid tähistatakse tähtedega ja auastmed numbritega:

Valge mängija jaoks on kõige parem alumine nurk h1; musta jaoks on see a8. Ruudul f2 liikuv book b2 tähistatakse kui Rb2-f2.

Toored liigutused

Ma määratlen tooreid käike nii, nagu käigud, mida tükk teeks, kui teised tükid või laua serv takistaksid . See viimane natuke võib tunduda veider, kuid see võimaldab mul konstrueerida 15x15 liikumismaski, mis seejärel kärbitakse 8x8 tahvlile sobivaks. Kaaslane Procrustes pakkus sarnase idee välja juba ammu tagasi.

Kuningad, kuningannad, piiskopid ja Rookid liiguvad mööda diagonaale ja / või faile, nii et nende nelja tüki liikumiseks kasutan ühte teenust. Etturitel on ainulaadsed liikumisomadused, seetõttu kasutatakse nende jaoks spetsiaalset teenust. Sama kehtib ka rüütlite kohta, sest nad saavad hüpata üle tükkide ega liigu failide või ridade järgi.

Näiteks võib vanker 15x15 laual, kus vanker on tsentreeritud, liikuda 7 ruutu mööda suvalist järku või faili. Sarnased reeglid kehtivad piiskopi ja kuninganna kohta. Kuningas piirdub suvalises suunas ühe ruutu vahemikuga (erandiks on loss, millega ma edaspidi tegelen).

Kasutan ChessPieceklassi, et hoida teavet iga maletüki tüübi ja asukoha kohta. Praegu ei mängi see liiga suurt rolli, kuid hiljem, kui teenustega hõlmatud reeglite ulatust laiendan.

Esimene jumalateenistus: Rook, piiskop, kuninganna ja kuningas liiguvad

Senecas kutsutakse teenuseid läbi roleja cmd. See rolesarnaneb kategooriaga ja cmdnimetab konkreetset teenust. Nagu näeme hiljem, saab teenust täiendavate parameetrite abil täpsustada.

Teenused lisatakse seneca.add()ja neid kutsutakse läbi seneca.act(). Vaatame kõigepealt teenust (saidilt Movement.js):

 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); });

Nüüd vaatame, kuidas test teenust kutsub (moveTest.js):

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

Pange tähele, et lisaks roleja cmdon olemaspieceargument. See, koos roleja cmdon omadusedmsgtalitusele saabunud argument. Enne teenuse käivitamist peate Senecale siiski ütlema, milliseid teenuseid kasutada:

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

Piiskopi toored käigud ruudul a1 asuvad msgVastuvõetud tagasi teenuse:

[{fail: '' ', auaste:' 0 '},

{fail: 'b', auaste: '2'},

{file: '' ', auaste:' 2 '},

{fail: 'b', auaste: '0'},

{fail: '_', auaste: '/'},

{fail: 'c', auaste: '3'},

{fail: '_', auaste: '3'},

{fail: 'c', auaste: '/'},

{fail: '^', auaste: '.' },

{fail: 'd', auaste: '4'},

{fail: '^', auaste: '4'},

{fail: 'd', auaste: '.' },

{file: ']', auaste: '-'},

{fail: 'e', ​​auaste: '5'},

{file: ']', auaste: '5'},

{fail: 'e', ​​auaste: '-'},

{fail: '\\', auaste: ','},

{fail: 'f', auaste: '6'},

{fail: '\\', auaste: '6'},

{fail: 'f', auaste: ','},

{fail: '[', auaste: '+'},

{fail: 'g', auaste: '7'},

{fail: '[', auaste: '7'},

{fail: 'g', auaste: '+'},

{fail: 'Z', auaste: '*'},

{fail: 'h', auaste: '8'},

{fail: 'Z', auaste: '8'},

{fail: 'h', auaste: '*'}]

Pange tähele, et loetletud on mõned imelikud ruudud! Need on kohad, mis 8x8 tahvlilt "kukuvad" ja mille teine ​​teenus kõrvaldab hiljem.

Mis just juhtus?

Teenus määratleti role=”movement”ja abil cmd=”rawMoves”. Kui act()see hiljem käivitatakse, sobitatakse toimingu päringu parameetrid teenusega, mis neid parameetreid käsitab (seda nimetatakse teenuse mustriks ). Nagu eelnevalt mainitud ja nagu näidatakse järgmises näites,roleja cmdpole tingimata ainsad parameetrid, mis määravad teenuse, millele viidatakse.

Järgmised teenused: Etturid ja rüütlid

Etturid liiguvad ühe ruudu edasi, välja arvatud juhul, kui nad asuvad oma algsel ruudul, sel juhul saavad nad liikuda ühe või kahe ruuduga edasi. On ka teisi käike, mida ettur saab teha, kui see pole tühja tahvli üksik tükk, kuid see on edaspidiseks kaalumiseks. Etturid alustavad alati teisel kohal ega saa kunagi tagurpidi liikuda.

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!).

See on selleks korraks! Järgmises postituses käsitlen teenuseid, mis käsitlevad liikumist takistavaid sõbralikke tükke ja tegelevad seejärel vaenulike tükkide võimaliku hõivamisega. Edasised teenistused tegelevad lossimise, reisijate , tšeki, mati ja ummikseisu reeglitega .

Kogu lähtekoodi leiate siit.

Jätkake selle sarja 2. osaga.