(Aceasta este partea 1 a unei serii din trei părți [Part 2, Part 3])

Am început să-mi învelesc capul în jurul microserviciilor. Până în acest moment l-am considerat ca un model de scalabilitate și am trecut cu vederea principiile funcționale de programare din spatele acestuia.

reguli de șah poate fi descompus cu ușurință în microservicii. Nu sunt nici aleatorii, nici ambigue, ceea ce este perfect pentru a scrie servicii mici, fără stat, care se ocupă de mișcări de diferite piese.

În această postare, voi parcurge mai multe servicii pe care le-am creat care determină care sunt mișcările legale pentru piesele singure pe o tablă de șah goală. Vom folosi Cadrul Seneca, un set de instrumente pentru microservicii pentru Node.js, deoarece este intuitiv și bine documentat.

Înființarea Seneca

Seneca este un modul Node.js care este instalat folosind npm:

npm install seneca

De asemenea, ne vom baza pe instalarea globală mocha / chai module pentru testele care vor ilustra funcționalitatea.

De fapt, nu este necesar să mențineți o reprezentare în memorie a unei table de șah, doar piesele și locația lor pe o grilă de coordonate 8×8. Notatie algebrica este folosit în mod obișnuit pentru a descrie coordonatele de pe o tablă de șah, unde fișierele sunt notate cu litere și rândurile cu numere:

Scrierea unui microserviciu de sah folosind Nodejs si Seneca Partea
Vedere din partea albă a tablii

Pentru jucătorul care este alb, colțul din dreapta jos este h1; pentru negru este a8. Un turn de pe b2, care se deplasează în pătratul f2, ar fi notat ca Rb2-f2.

Mutare brută

Eu definesc mișcări crude ca mișcări pe care le-ar face o piesă dacă nu va fi împiedicată de alte piese sau marginea plăcii. Ultimul bit poate părea ciudat, dar îmi permite să construiesc o mască de mișcare de 15×15, care este apoi tăiată pentru a se potrivi cu placa de 8×8. Un tip numit Procrustes a venit cu o idee similară în urmă cu veacuri.

Regii, reginele, episcopii și turnurile se deplasează de-a lungul diagonalelor și / sau fișierelor, așa că voi folosi un serviciu pentru mișcările celor patru piese. Pionii au caracteristici de mișcare unice, așa că va fi folosit un serviciu special pentru ei. Același lucru este valabil și pentru Cavaleri, deoarece aceștia pot sări peste bucăți și nu se mișcă de-a lungul fișierelor sau rangurilor.

De exemplu, o turnă poate deplasa 7 pătrate de-a lungul oricărui rang sau fișier pe o placă de 15×15 în care este centrată turnul. Reguli similare se aplică episcopului și reginei. Regele este limitat la o rază de un pătrat în orice direcție (excepția este castlingul, de care mă voi ocupa într-un post viitor).

Voi folosi un ChessPiece clasă pentru a deține informații despre tipul și locația fiecărei piese de șah. Nu va juca un rol prea important deocamdată, dar mai târziu, când voi extinde domeniul de aplicare al regulilor acoperite de servicii.

Primul serviciu: se mută Rook, Bishop, Queen și King

În Seneca, serviciile sunt invocate prin role și cmd. role este asemănător unei categorii și cmd numește un serviciu specific. După cum vom vedea mai târziu, un serviciu poate fi specificat în continuare prin parametri suplimentari.

Serviciile sunt adăugate folosind seneca.add(), și invocat prin seneca.act(). Să vedem mai întâi serviciul (de la 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);
    });

Acum să vedem cum invocă testul serviciului (movesTest.js):

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

Rețineți că pe lângă role și cmd, este un piece argument. Aceasta, împreună cu role și cmd, sunt proprietăți ale msg argument primit de serviciu. Înainte de a putea invoca serviciul, totuși, trebuie să-i spuneți lui Seneca ce servicii să utilizați:

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

Mișcările brute pentru un episcop în pătratul a1 se află în msg primit înapoi din serviciu:

[ { file: ‘`’, rank: ‘0’ },
{ file: ‘b’, rank: ‘2’ },
{ file: ‘`’, rank: ‘2’ },
{ file: ‘b’, rank: ‘0’ },
{ file: ‘_’, rank: ‘/’ },
{ file: ‘c’, rank: ‘3’ },
{ file: ‘_’, rank: ‘3’ },
{ file: ‘c’, rank: ‘/’ },
{ file: ‘^’, rank: ‘.’ },
{ file: ‘d’, rank: ‘4’ },
{ file: ‘^’, rank: ‘4’ },
{ file: ‘d’, rank: ‘.’ },
{ file: ‘]’, rang: ‘-‘ },
{file: ‘e’, ​​rang: ‘5’},
{file: ‘]’, rang: ‘5’},
{file: ‘e’, ​​rang: ‘-‘},
{file: ‘\’, rank: ‘,’},
{file: ‘f’, rang: ‘6’},
{file: ‘\’, rang: ‘6’},
{file: ‘f’, rank: ‘,’},
{file: ‘[‘, rank: ‘+’ },
{ file: ‘g’, rank: ‘7’ },
{ file: ‘[‘, rank: ‘7’ },
{ file: ‘g’, rank: ‘+’ },
{ file: ‘Z’, rank: ‘*’ },
{ file: ‘h’, rank: ‘8’ },
{ file: ‘Z’, rank: ‘8’ },
{ file: ‘h’, rank: ‘*’ } ]

Rețineți că există câteva pătrate ciudate listate! Acestea sunt pozițiile care „cad” de pe placa 8×8 și vor fi eliminate ulterior de un alt serviciu.

Ce s-a intamplat?

Un serviciu a fost definit cu role=”movement” și cmd=”rawMoves”. Cand act() este invocat ulterior, parametrii cererii de act sunt corelate cu un serviciu care gestionează acei parametri (acest lucru se numește serviciul model). Așa cum am menționat anterior și așa cum va fi arătat în exemplul următor, role și cmd nu sunt neapărat singurii parametri care determină serviciul invocat.

Următoarele servicii: pioni și cavaleri

Pionii mișcă un pătrat înainte, cu excepția cazului în care se află pe pătratul lor original, caz în care pot muta unul sau două pătrate înainte. Există și alte mișcări pe care le poate face un pion atunci când nu este singura piesă de pe o tablă goală, dar asta este pentru o analiză viitoare. Pionii încep mereu pe al doilea rang și nu se pot mișca niciodată înapoi.

Cavalerii se mișcă în formă de L. În placa noastră imaginară de 15×15 cu cavalerul centrat, vor exista întotdeauna opt mișcări posibile.

Voi scrie două servicii (unul pentru pioni, celălalt pentru cavaleri) și le voi pune pe ambele într-un singur modul (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);
    });
}

A se vedea isPawn și isKnight parametrii din servicii? Primul obiect i-a trecut lui Seneca add() se numește tipar de serviciu. Ceea ce se întâmplă este că Seneca va invoca serviciul cu cel mai specific potrivire tipar. Pentru a invoca serviciul potrivit, trebuie să adaug isPawn:true sau isKnight:true la cererea actului:

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) => {

Serviciul nostru de mutare legală rudimentară va filtra doar toate pozițiile pătrate care nu se află în fișierele ah sau pe rangurile 1-8. Serviciul legal de mutare va fi apelat direct cu un ChessPiece ca parte a sarcinii utile a serviciului. Serviciul legal de mutare va invoca apoi serviciul de mutare brută pentru a obține masca de mișcare. Masca va fi trunchiată la marginile planșei, iar rezultatul va fi pozițiile pătrate care pot fi jucate legal.

    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 <= 'h') {
                    if (move.rank >= 1 && move.rank <= 8) {
                        squared.push(move)
                    }
                }
            })

            reply(null, squared);
        });
    })

legalSquares serviciul invocă mai întâi rawMoves serviciu. Acest lucru ne aduce masca de mișcare 15×15 pentru orice piesă este transmisă prin msg parametru. Este important, totuși, ca serviciul potrivit să fie invocat prin setarea isKnight sau isPawn câmpul modelului este adevărat pentru oricare dintre aceste două piese … dacă ambele sunt false, atunci „regulat” rawMoves serviciul pentru K, Q, B, R va fi invocat.

Odată ce mișcările brute sunt recuperate, atunci legalSquares serviciul elimină pozițiile nevalide și returnează ceea ce a mai rămas. Deci, dacă invoc serviciul cu piesa la Na1, primesc:

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

Dacă în schimb trec în Rd4, legalSquares returnează:
[ { 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’ } ]

care este puțin mai greu de descifrat, dar conține toate fișierele de-a lungul rangului 4 și toate rangurile de-a lungul fișierului d (credeți-mă!).

Atât deocamdată! Într-o postare viitoare voi trece peste serviciile care se ocupă de piese prietenoase care împiedică mișcarea, apoi mă ocup de captarea potențială a pieselor ostile. Servicii suplimentare se vor ocupa de reguli pentru castling, en passant, cec, șah și impas.

Tot codul sursă poate fi găsit aici.

Continuă să Partea 2 a acestei serii.