Dagaz: Episódios (Parte 1)

Abalamos seus filtros mentais, e o resultado foi uma resposta. O método funcionou, sempre será eficaz. Tudo o que precisa ser feito é se livrar do ônus extra do preconceito ...

Raymond Jones " Nível de ruído "

Dagaz não apareceu do zero. Sempre gostei de jogos de tabuleiro e quebra-cabeças e faço programação o máximo que me lembro, mas o pensamento de um certo mecanismo "universal" simplesmente não poderia ter me passado pela cabeça. Eu era cético em relação a essa ideia. Até eu ver Zillions . Infelizmente, o produto, na época, não estava mais em desenvolvimento, o código-fonte não estava disponível e, de fato, o programa funcionava apenas no Windows. Depois de algum tempo, decidi pegar um projeto aberto.

Como eu já disse, não tinha códigos-fonte, mas toquei um pouco no Zillions e peguei sua ideia principal - a reutilização máxima do código do aplicativo, que permite usar as mesmas construções em diferentes, o que pareceria completamente diferente um do outro casos. Era tudo sobre o caso de uso certo. E eu fiz um plano .

Damas


Essa importante, mas extremamente subestimada família de jogos lançou a pedra fundamental do projeto. Todos os jogos de "damas" são semelhantes entre si e diferem apenas em detalhes. Em termos de design de jogos, todos eles estão unidos por três idéias principais:

  • Por cheque
  • Prioridade
  • Movimento composto

O primeiro parágrafo não levanta questões especiais, mas se o desenvolvimento estiver focado nos jogos de xadrez, pode ser uma surpresa. Nem em todos os jogos, a captura ocorre no mesmo campo em que a peça completa a jogada. Com movimentos prioritários, as coisas são um pouco mais interessantes. Nas damas, essa regra permite que você construa um jogo combinatório complexo, atraindo o inimigo para armadilhas que operam com o princípio de "dar menos - pegar mais".


Obviamente, o mecanismo de movimentos prioritários foi implementado no Zillions (e migrado de lá para Dagaz). Sem ele, quase todos os jogos de damas (certamente incluídos no "conjunto de cavalheiros" de jogos de tabuleiro que são obrigatórios para implementação) simplesmente não funcionavam corretamente. É tudo sobre os detalhes. Vamos ver como esse mecanismo foi implementado:

Em zilhões
(move-priorities jump-type normal-type) ... (define checker-shift ( $1 (verify empty?) ;        add ;   )) (define checker-jump ( $1 (verify enemy?) ;         capture ;   ( capture  -   ) $1 (verify empty?) ;           add ;   )) ... (piece (name Man) (image White "images/stapeldammen/white.bmp" Red "images/stapeldammen/red.bmp") (moves (move-type jump-type) (checker-jump nw) (checker-jump ne) (checker-jump sw) (checker-jump se) (move-type normal-type) (checker-shift nw) (checker-shift ne) ) ) 

Esta é uma descrição quase completa do jogo mais simples de damas. O conceito de modos de movimentação ( tipo de movimentação ) é introduzido no ZRF , e a construção de prioridades de movimentação nos permite dizer que, se houver movimentos de maior prioridade (takes), menos prioridade (movimentos silenciosos) não deve ser considerada. Os níveis de prioridade podem ser determinados e mais de dois, nesse sentido, o design é bastante universal, mas, trabalhando em jogos no Dagaz, me deparei com algumas limitações desse mecanismo.


Neste jogo , inventado por Solomon Golomb, além de damas, também existem peças de xadrez. A dificuldade reside no fato de que tomar, embora permaneça uma prioridade para as peças de xadrez, não é o mesmo para as peças de xadrez (caso contrário, seria muito fácil capturá-las e comê-las). Uma priorização ingênua usando a palavra - chave mover-prioridades não funcionará neste jogo.

De fato, se as peças de xadrez não estiverem incluídas nos movimentos prioritários, se houver a possibilidade de pegar uma peça de xadrez e uma peça de xadrez, não poderemos jogar uma peça de xadrez, uma vez que uma captura de cheques é uma prioridade. Se os movimentos de xadrez forem considerados igualmente prioritários, seremos obrigados a pegar peças de xadrez sempre que surgir a oportunidade. Ambos contradizem as regras do jogo.

Em Zilhões, esse problema praticamente não está resolvido. E esse foi o principal motivo pelo qual pensei em introduzir um mecanismo de extensão JavaScript no Dagaz. A idéia, por si só, é bastante simples: como algumas mecânicas de jogo são bastante difíceis de expressar no ZRF, por que não introduzir a fase de pós-processamento dos movimentos? O módulo de extensão, nesse caso, verifica toda a lista de movimentos gerados na sua totalidade e pode tomar decisões sobre a rejeição de determinados movimentos. Aqui está o que parece para Shashmat :

Código simples e compacto
 var CheckInvariants = Dagaz.Model.CheckInvariants; Dagaz.Model.CheckInvariants = function(board) { var design = Dagaz.Model.design; var types = []; types.push(design.getPieceType("Bishop")); types.push(design.getPieceType("Camel")); var isPriority = false; _.each(board.moves, function(move) { if (isCapturing(board, move)) { if (_.indexOf(types, getType(board, move)) < 0) isPriority = true; } }); if (isPriority) { _.each(board.moves, function(move) { if (!isCapturing(board, move)) { move.failed = true; } }); } CheckInvariants(board); } 

No futuro, a idéia de extensões se desenvolveu e floresceu com uma cor magnífica. Eu tenho um mecanismo conveniente e poderoso para codificar muitos jogos cuja implementação em ZRF puro seria extremamente problemática, mas isso significa que a priorização no estilo ZRF está desatualizada? Claro que não! Primeiro, escrever uma linha em ZRF é mais fácil do que cinquenta em JavaScript, mas, mais importante, as prioridades "rígidas" no estilo ZRF funcionam de maneira que movimentos de baixa prioridade nem sejam gerados! Isso é importante em termos de desempenho. Gerar movimentos no Dagaz é uma operação muito cara.

Outro jogo com prioridades desafiadoras

Dablot é um jogo um pouco semelhante ao italiano Drafts , mas mais antigo. Além das figuras comuns, existem “príncipes” e “reis” nela, e as figuras mais jovens não têm o direito de derrotar os mais velhos. Mas essa não é a dificuldade. Para reis (e em algumas variedades do jogo também para príncipes), a captura é opcional! Aqui surge o mesmo problema que o Shashmat . Se declararmos o rei como uma prioridade, violaremos as regras do jogo, caso contrário não seremos capazes de derrotá-lo com a possibilidade de uma batalha alternativa com uma figura simples. Somente o mecanismo de extensão Dagaz resolve esse problema.

A propósito, com "Italian Drafts" tudo não é tão simples. Em muitas variedades de rascunhos, existe uma regra que declara que o jogador é obrigado a pegar o número máximo de peças. Ou seja, ele não pode apenas interromper a cadeia de capturas, mas deve escolher o caminho em que ele pegará mais pedaços! Pelas razões que discutirei abaixo, esta regra não pôde ser implementada no Zillions de forma universal e os desenvolvedores foram forçados a codificá-la. Nos rascunhos italianos, a "regra da maioria" parece ainda mais complicada: "você precisa vencer o número máximo possível de rascunhos do oponente e, com opções de batalha iguais, precisa vencer o número máximo de damas".

Movimentos compostos são o segundo componente importante dos jogos de damas. Tão importante que o teste para a captura correta nos " Rascunhos Turcos " eu corro periodicamente até agora. Algumas vezes, quando quebrei o modelo com as próximas mudanças, isso realmente ajudou.

Movimentos - compostos e parciais
Vamos ver como as movimentações compostas são implementadas no ZRF
 (define checker-shift ( $1 (verify empty?) (if (in-zone? promotion) (add King) else add ) )) (define checker-jump ( $1 (verify enemy?) capture $1 (verify empty?) (if (in-zone? promotion) (add King) else (add-partial jump-type) ) )) (define king-shift ( $1 (verify empty?) add )) (define king-jump ( $1 (verify enemy?) capture $1 (verify empty?) (add-partial jump-type) )) 

É assim que é simples. O comando add-parcial diz que a movimentação pode ser continuada (com a mesma peça, isso é importante) se ainda houver movimentos com o modo especificado. Em outras palavras: "a figura deve continuar a ser usada, enquanto houver uma oportunidade". Tudo parece estar bem, mas há uma ressalva. Zillions vê cada um deles como um movimento "parcial" separado. Vamos ver o que isso pode levar.


Neste jogo , o número de "etapas" executadas por uma peça é determinado pelo ícone em que está. Agora as brancas se mexem e Damyo está andando (uma peça marcada com uma conta vermelha). Em Zillions, depois de concluir dois movimentos parciais, ela pode facilmente ir para o canto superior esquerdo, do qual não pode mais fazer o último movimento restante (você não pode voltar). Tomar a peça do oponente no segundo movimento parcial também é proibido. No Zillions, não há como proibir a sequência de movimentos que levam a um beco sem saída.

Na Dagaz, é diferente! Uma sequência de movimentos parciais é sempre montada em um movimento composto completo. Uma jogada que não pode ser concluída simplesmente não vai acontecer! Essa é uma abordagem que consome muitos recursos e, como resultado, gerar uma lista de movimentação no Dagaz é uma operação muito cara. Mas suas vantagens são significativas. Por exemplo, o bot recebe todo o movimento composto na sua totalidade e não deve olhar para a frente, executando os movimentos parciais restantes.

Ainda mais importante, essa abordagem oferece a oportunidade de considerar toda a lista de movimentos condicionalmente aceitáveis, executando verificações mais complexas e proibindo alguns movimentos, dependendo da presença de outros. Por exemplo, a "regra da maioria", que mencionei acima, no Dagaz é implementada de maneira bastante simples . Além disso, para os "damas italianos" também. Os desenvolvedores do Zillions "resolveram" o problema que conheciam para os jogos de damas codificando a opção " capturas máximas ", mas há um grande número de jogos com outras verificações complexas que eles, na época, não tinham idéia!

No processo de trabalhar em novos jogos, o conceito de uma jogada composta também foi desenvolvido. Fanorona e Pasang sugeriram uma mecânica de jogo interessante, na qual um grupo de peças removidas do tabuleiro deve ser selecionado pelo jogador que executa o movimento:


Além disso, Fanorona é um daqueles jogos raros em que um jogador tem o direito de interromper a cadeia de capturas. A primeira captura é obrigatória, a subsequente, dentro do mesmo movimento - a critério do jogador. No Dagaz, essa opção ( passagem parcial ) é implementada movendo a figura no lugar. O Missclick pode estar aqui, e isso, aparentemente, não é muito conveniente, mas com a introdução do gerenciador de sessões , movimentos errados agora podem ser revertidos.

Um desenvolvimento adicional do tópico foram os movimentos de "tiro". Eu os fiz pela primeira vez em Hanga Roa e Ko Shogi , mas como aconteceu depois, eu fiz errado! A implementação incorreta não funcionou sob o controle de bots (e como ainda não tenho bots para esses dois jogos, não é de surpreender que eu não tenha notado nada). Muito mais tarde, quando fiz o Amazons , consegui localizar o problema e resolvê-lo. Essa idéia atingiu seu auge em um jogo inventado por um de nossos compatriotas em 1957.


Há outro problema relacionado à implementação de movimentos compostos no Dagaz. O fato é que a lista de jogadas permitidas, de um estado específico do jogo, é formada imediatamente - a coisa toda. Em Zillions, com seus movimentos parciais, isso não é muito crítico, mas em Dagaz, se a peça tiver a oportunidade de "circular no lugar", a fase da geração do movimento nunca será concluída (é óbvio que é impossível corrigir esse problema com extensões, porque é fácil lidar com eles não alcança). Aqui está um dos jogos para os quais isso é importante:


Aqui, as peças não são removidas do tabuleiro e a mesma peça pode ser saltada várias vezes seguidas. A solução óbvia é proibir a visita ao mesmo campo duas vezes por turno, mas eu tive que entrar completamente no kernel para implementar essa verificação. Era um pouco como a implementação da opção " captura diferida ", mas desde que eu fiz Rascunhos Russos muito antes, havia muito menos problemas com ela.

Infelizmente, existem jogos em que mesmo essas verificações não salvam
As regras dos Stapeldammen (este é um tipo de “ Pilares ”) afirma explicitamente que a mesma peça pode ser vencida várias vezes por turno. A peça que executa o movimento retorna à mesma posição várias vezes e continua a batalha, enquanto no inimigo existem figuras nas colunas .. Os movimentos compostos de Dagaz não conseguem lidar com esse problema. A lógica da batalha de Pole Drafts é muito complicada para o kernel e não alcança as extensões, porque a geração do loop é repetida. Claro, existe uma saída:


Não há jogadas parciais no Dagaz, mas podemos imitá-las pulando a próxima jogada do oponente (a mesma abordagem é usada nos decalques ). E apenas essa lógica é facilmente implementada pela extensão . Simplesmente proibimos todos os movimentos sob certas condições, e a opção passa-volta = forçada gera automaticamente um movimento vazio. Aqui está outro jogo com emulação semelhante.


Dividir artificialmente os movimentos compostos em parciais não é muito bom para os bots de IA, mas, às vezes, simplesmente não há outra saída.

Em geral, o conceito de composto move vidas e se desenvolve. Mais recentemente, tive que fazer outra nova opção ( parcial parcial ) para um jogo egípcio antigo.


Além de completar automaticamente o movimento das figuras ao longo das setas, também possui outras soluções técnicas interessantes. Mas sobre isso em outro momento.

Source: https://habr.com/ru/post/pt459456/


All Articles