Skip to content

juliodaal/AIProject

Repository files navigation

Projeto 1

Logo de Markdown

Manual Técnico

  • Universidade: [Instituto Politécnico de Setúbal]
  • Projeto: [Projeto 1 - Jogo do Cavalo: Jogador Único]
  • Aluno(s): [Julio Sousa (201902012)] [Marco Ventura (202000792)]
  • Data: 18/12/2023

1. Arquitectura do Sistema

Nesta secção, descrevemos a arquitetura geral do sistema, incluindo:

  • Identificação dos módulos
  1. Módulo de procura (Ficheiro procura.LISP)
  2. Módulo de interação com o utiizador (Ficheiro projeto.LISP)
  3. Módulo de mecânicas do Jogo (Ficheiro puzzle.LISP)
  • Objetivos individuais de cada módulo
  1. Módulo de procura: Contém a implementação dos algoritmos de pesquisa sejam BFS, DFS e A*
  2. Módulo de interação com o utiizador: Carrega os outros ficheiros de código, escreve e lê ficheiros, e trata da interação com o utilizador.
  3. Módulo de mecânicas do Jogo: Relacionado com a implementação do jogo, contém funções relativamente a regras e jogabilidade.
  • Conexões entre os módulos

A conexão entre os módulos faz-se através da função load no ficheiro projeto.LISP, no qual carregam-se os dos módulos Procura e Mecânicas.

  • Fluxo de informações dentro de cada módulo e entre módulos

O primeiro módulo a ser executado é o Módulo de interação com o utilizador, sendo que através deste modulo são obtidos os parâmetros iniciais para chamar a função preparação do módulo de procura e a mesma vai executar o algoritmo de procura pretendido, interagindo com o módulo de Mecânicas.

2. Entidades e sua Implementação

Aqui, abordamos as entidades do sistema, tanto do ponto de vista do domínio da aplicação como do ponto de vista da implementação.

Tabuleiro: Composto por uma lista de lista com a seguinte estrutura:

  (
    (2 NIL 4 NIL 6 NIL 8 NIL 10 NIL)
    (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)
    (NIL 3 NIL 5 NIL 7 NIL 9 NIL 11)
    (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)
    (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)
    (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)
    (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)
    (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)
    (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)
    (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)
  )

Observamos um tabuleiro de dimensões 10x10 onde as casas podem adquirir os valores:

  • T: Posição atual do cavalo.
  • NIL: Casa marcada como visitada e inacessível pelo cavalo.
  • "Valor Numérico": Casa acessível pelo cavalo com o valor disponível para acumular.

Para gerar o tabuleiro temos a função tabuleiro-aleatorio

;; tabuleiro-aleatorio
(defun tabuleiro-aleatorio (lista &optional (n 10))
    "Recebe a lista e opcionalmente o n, Retorna o tabuleiro baralhado"
    (cond
        ((null lista) nil)
        (t (cons (subseq lista 0 n) (tabuleiro-aleatorio (subseq lista n) n)))
    )
)

Na interação com o tabuleiro temos seguintes:

  • posicao-casa: Recebe o tabuleiro e o valor da casa, as coordenadas da casa, NIL caso não exista
;; posicao-casa 
(defun posicao-casa (tabuleiro casa)
    "Recebe o tabuleiro e o valor da casa, as coordenadas da casa, NIL caso não exista"
    (let* (
            (lista (mapcar #'(lambda (linha) (position casa linha :test #'eql)) tabuleiro))
            (j (find-if #'(lambda (x) (not (null x))) lista))
            )
        (cond
        ((null j) NIL)
        (t (list (position-if #'(lambda (x) (not (null x))) lista) j))
        )
    )
)
  • substituir: Substitui o VALOR nas coordenadas (INDICE1, INDICE2) do TABULEIRO.
(defun substituir (indice1 indice2 tabuleiro &optional (valor nil))
    "Substitui o VALOR nas coordenadas (INDICE1, INDICE2) do TABULEIRO."
    (let* ((linha-substituida (substituir-posicao indice2 (linha indice1 tabuleiro) valor)))
      (substituir-posicao indice1 tabuleiro linha-substituida))
)

-celula: Função que recebe dois índices e o tabuleiro e retorna o valor presente nessa célula do tabuleiro.

(defun celula (linha coluna tabuleiro)
  "Função que recebe dois índices e o tabuleiro e retorna o valor presente nessa célula do tabuleiro."
  (if (and (integerp linha) (integerp coluna) (< linha  (length tabuleiro)) (< coluna (length (first tabuleiro))))
      (nth coluna (nth linha tabuleiro))
      nil
  )
)

Cavalo: Representado pelo valor "T", indica a posição do cavalo no tabuleiro:

  (
    (2 NIL 4 NIL T NIL 8 NIL 10 NIL)
    (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)
    (NIL 3 NIL 5 NIL 7 NIL 9 NIL 11)
    (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)
    (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)
    (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)
    (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)
    (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)
    (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)
    (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)
  )

O cavalo possui um conjunto de operadores que representam os movimentos possíveis num determinado estado. O máximo de movimentos possíveis serão 8, desde que essas casas não tenham sido ainda visitadas ou removidas pela regra dos simétricos ou duplos. (Os custos de aplicação dos operadores são considerados constantes e unitários)

Exemplo de uma função operador:

;; operador-1 
(defun operador-1 (estado)
  "Recebe o estado, e define o operador, movimento a seguir"
  (definir-operador '+ 2 '- 1 (car estado) (second estado))
)

É importante observar que para os movimentos do cavalo tambem são adicionadas um conjunto de regras. Que são a seguintes:

  • Se a casa escolhida tiver um número com dois dígitos diferentes, por exemplo 57, então, em consequência, o número simétrico 75 é apagado do tabuleiro, tornando esta casa inacessível durante o resto do jogo. Ou seja, nenhum cavalo pode terminar outra jogada nessa casa.
  • Se um cavalo for colocado numa casa com um número "duplo", por exemplo 66, então qualquer outro número duplo pode ser removido e o jogador deve escolher qual em função da sua estratégia (por default remover a de maior valor). Depois de um jogador deixar a casa para se movimentar para outra, a casa onde estava fica também inacessível para o jogo, ficando o numero da casa apagado.

Com o objetivo de resolver estas dos problematicas foram implementadas a seguintes funções:

;; simetrico-solucao
(defun simetrico-solucao (coordenada-objetivo tabuleiro)
    "Recebe o tabuleiro, valor da coordenada objetivvo e o valor acumulado, retorna o novo estado"
    (let*   (
                (valor-celula (celula (car coordenada-objetivo) (second coordenada-objetivo) tabuleiro))
                (valor-simetrico (valor-simetrico valor-celula))
                (posicao-peca (posicao-casa tabuleiro valor-simetrico))
            )
        (cond
            ((not (null posicao-peca)) (substituir (car posicao-peca) (second posicao-peca) tabuleiro)) 
            (t tabuleiro)
        )
    )
)
;; duplo-solucao
(defun duplo-solucao (tabuleiro)
    "Recebe o tabuleiro e o valor acumulado, retorna o novo estado"
    (let*   (
                (duplos-atuais (valores-duplos-atuais tabuleiro))
            )
        (cond
            ((not (null duplos-atuais))
              (let* (
                      (escolha (random (length duplos-atuais)))
                      (posicao-peca (posicao-casa tabuleiro (nth escolha duplos-atuais)))
                    )
                    (substituir (car posicao-peca) (second posicao-peca) tabuleiro)
              )
            )
            (t tabuleiro)
        )
    )
)

3. Algoritmos e sua Implementação

Nesta secção, dividimos o sistema em algoritmos e explicamos cada um deles.

Para todos os algoritmos de procura é realizada uma preparação prévia com o objetivo de que o utlizador selecione a primeira casa é a partir de ali começa a pesquisa da solução ao problema. Dita preparação tem o seguinte comportamento:

;; preparacao
(defun preparacao (algoritmo tabuleiro valor-objetivo)
    (imprimir-tabuleiro tabuleiro)
    (let*   (
                (coordenada-objetivo (escolher-coordenadas (obter-coordenadas-iniciais tabuleiro)))
                (valor-da-coordenada-objetivo (celula (car coordenada-objetivo) (second coordenada-objetivo) tabuleiro))
                (novo-tabuleiro (substituir (car coordenada-objetivo) (second coordenada-objetivo) tabuleiro T))
                (resultado  
                    (let*   (
                                (inicio (get-internal-real-time))
                                (resultado-algoritmo (cond
                                        ((equal algoritmo 'bfs)
                                            (funcall algoritmo (criar-no (list novo-tabuleiro valor-da-coordenada-objetivo) 1 0 0 (criar-no (list tabuleiro 0))) 'no-solucaop 'sucessores (operadores) valor-objetivo)
                                        )
                                        ((equal algoritmo 'dfs) 
                                            (let* ((profundidade (escolher-profundidade)))
                                                (funcall algoritmo (criar-no (list novo-tabuleiro valor-da-coordenada-objetivo) 1 0 0 (criar-no (list tabuleiro 0))) 'no-solucaop 'sucessores (operadores) valor-objetivo :d profundidade)
                                            )
                                        )
                                        ((equal algoritmo 'aestrela) 
                                            (let* ((heuristica-escolhida (mostrar-menu (opcoes-heuristicas))))
                                                (let*   (
                                                            (h  (cond 
                                                                    ((= heuristica-escolhida 0) (calculo-heuristica valor-da-coordenada-objetivo 1 novo-tabuleiro))
                                                                    (t (heuristica-cavalo (criar-no (list novo-tabuleiro valor-da-coordenada-objetivo) 1 0 0 (criar-no (list tabuleiro 0)))))
                                                                )
                                                            )
                                                        ) 
                                                    (funcall algoritmo (criar-no (list novo-tabuleiro valor-da-coordenada-objetivo) 1 h (f 1 h) (criar-no (list tabuleiro 0))) 'no-solucaop 'sucessores (operadores) valor-objetivo :heuristica heuristica-escolhida)
                                                )
                                            )
                                        )
                                    )
                                )
                            ) 
                        (let* ((fin (get-internal-real-time))) 
                            (list resultado-algoritmo (- fin inicio))
                        )
                    )
                )
            ) 
            (cond
                ((null (car resultado)) (format t "O problema nao tem solucao."))
                (t (imprimir-resultado algoritmo resultado))
            )
    )
)

Estrutura do no e construtor

Foram implementadas diversas funções para a criação, alteração e retorna das deversas carateristicas de um no, como são seguintes:

;; criar-no
(defun criar-no (estado &optional (g 0) (h 0) (f 0) (pai nil))
    "Recebe o tabuleiro e opcionalmente o acumulador de pontos, heuristica e o no pai, retorna um novo no"
    (list estado g h f pai)
)
;;; Metodos seletores
;; no-estado
(defun no-estado (no)
    "Recebe o no, retorna o estado"
    (car no)
)
;; no-pontos-acumulados
(defun no-pontos-acumulados (no)
    "Recebe o no, retorna os pontos acumulados"
    (second (car no))
)
;; no-tabuleiro
(defun no-tabuleiro (no)
    "Recebe o no e retorna o tabuleiro"
    (car (car no))
)
;; no-profundidade
(defun no-profundidade (no)
    "Recebe o no, retorna a profundidade"
    (second no)
)
;; no-heuristica
(defun no-heuristica (no)
    "Recebe o no, retorna a heuristica"
    (third no)
)
;; no-heuristica
(defun no-interesse (no)
    "Recebe o no, retorna o interesse"
    (fourth no)
)
;; no-pai
(defun no-pai (no)
    "Recebe o no, retorna no pai"
    (fifth no)
)

Validação da solução do algoritmo

Para validar se um no é o nó solução foi implementada a seguinte função:

;; no-solucaop
(defun no-solucaop (no valor-objetivo)
    "Recebe o no e o valor objetivo, retorna T se os pontos acumulados >= aos pontos objetivo, NIL caso contrario"
    (>= (no-pontos-acumulados no) valor-objetivo)
)

Operadores

Os operadores foram abordados anteriormente neste documento e eles são encarregados de ditar as ações do cavalo neste jogo.

No total temos 8 operadores para os 8 posiveis movimentos do cavalo, as funções para representar cada operador são as seguintes:

;; operadores
(defun operadores ()
    "Retorna predicados referente aos operadores"
    (list 'operador-1 'operador-2 'operador-3 'operador-4 'operador-5 'operador-6 'operador-7 'operador-8)
)
;; operador-1
(defun operador-1 (estado)
    "Recebe o estado, e define o operado, movimento a seguir"
    (definir-operador '+ 2 '- 1 (car estado) (second estado))
)
;; operador-2
(defun operador-2 (estado)
    "Recebe o estado, e define o operado, movimento a seguir"
    (definir-operador '+ 2 '+ 1 (car estado) (second estado))
)
;; operador-3
(defun operador-3 (estado)
    "Recebe o estado, e define o operado, movimento a seguir"
    (definir-operador '+ 1 '+ 2 (car estado) (second estado))
)
;; operador-4
(defun operador-4 (estado)
    "Recebe o estado, e define o operado, movimento a seguir"
    (definir-operador '- 1 '+ 2 (car estado) (second estado))
)
;; operador-5
(defun operador-5 (estado)
    "Recebe o estado, e define o operado, movimento a seguir"
    (definir-operador '- 2 '+ 1 (car estado) (second estado))
)
;; operador-6
(defun operador-6 (estado)
    "Recebe o estado, e define o operado, movimento a seguir"
    (definir-operador '- 2 '- 1 (car estado) (second estado))
)
;; operador-7
(defun operador-7 (estado)
    "Recebe o estado, e define o operado, movimento a seguir"
    (definir-operador '- 1 '- 2 (car estado) (second estado))
)
;; operador-8
(defun operador-8 (estado)
    "Recebe o estado, e define o operado, movimento a seguir"
    (definir-operador '+ 1 '- 2 (car estado) (second estado))
)
;; definir-operador
(defun definir-operador (pre-op-i valor-i pre-op-j valor-j tabuleiro valor-acumulado)
    "Define qual é o operador a seguir"
    (let* (
            (posicao (posicao-cavalo tabuleiro))
            )
        (cond
        ((null posicao) NIL)
        (t  (let* (
                    (coordenada-objetivo (list (funcall pre-op-i (first posicao) valor-i) (funcall pre-op-j (second posicao) valor-j)))
                    )
                    (cond
                    ((e-validop (car coordenada-objetivo) (second coordenada-objetivo) tabuleiro)
                        (let* ((valor-objetivo (celula (car coordenada-objetivo) (second coordenada-objetivo) tabuleiro)))
                        (list (mover posicao coordenada-objetivo (escolher-solucao coordenada-objetivo tabuleiro)) (+ valor-acumulado valor-objetivo))
                        ; (list (mover posicao coordenada-objetivo tabuleiro) (+ valor-acumulado valor-objetivo)) 
                        )
                    )
                    (t NIL)
                    )
            )
        )
        )
    )
)

Sucessores

Os sucessores definem os nós resultantes de cada nó expandido no algoritmo. E a criação do sucessores tem a seguinte estrutura:

;; novo-sucessore
(defun novo-sucessor (no nome-operador valor-objetivo heuristica)
    "Recebe o no e o predicado do operador, retorna o no sucessor, NIL caso no existir o no sucessor"
    (let* ((resultado (funcall nome-operador (no-estado no))))
        (cond
            ((null resultado) NIL)
            (t 
                (let*   (
                            (h  (cond
                                    ((= heuristica 0) (calculo-heuristica valor-objetivo (second resultado) (car resultado)))
                                    (t (heuristica-cavalo no))
                                )
                            )
                        )
                        (list (criar-no resultado (1+ (no-profundidade no)) h (f (1+ (no-profundidade no)) h) no))
                )
            )
        )
    )
)
;; sucessores
(defun sucessores (no operadores &optional (valor-objetivo 0) (heuristica 0))
    "Recebe o no e os operadores, retorna os nos sucessores ao no, NIL caso não existir sucessores"
    (apply #'append (mapcar #'(lambda (nome-operador)
        (novo-sucessor no nome-operador valor-objetivo heuristica)
    ) operadores))
)

Heurística

Neste projeto foram abordadas 2 heurísticas:

  1. A heurística baseada em privilegiar visitar as casas com o maior número de pontos.
  2. A heuristica baseada na adaptação a distança manhattan mas com a diferença que a casa objetivo é a casa de maior valor no tabuleiro

Para implementação e calculo destas heurísticas foram implementadas um conjuntos de funções:

(defun f (g h)
    "Recebe a profundidade e a heuristica, retorna a soma de g e h"
    (+ g h)
)
;; calculo-heuristica
(defun calculo-heuristica (valor-objetivo valor-acumulado tabuleiro)
    "Calcula a heuristica h(x) = o(x)/m(x) onde o(x) é o número de pontos que faltam para atingir o valor definido como objetivo e m(x) a média por casa dos pontos que constam no tabuleiro"
    (/ (- valor-objetivo valor-acumulado) (m tabuleiro))
)
;; m
(defun m (tabuleiro)
    "Recebe o tabuleiro, Retorna a média por casa dos pontos que constam no tabuleiro"
    (let* ((lista-sem-nil (apply #'append (mapcar #'(lambda (lista) (remove-if #'(lambda (n) (or (null n) (equal (write-to-string n) "T"))) lista)) tabuleiro))))
        (cond
            ((null lista-sem-nil) 1)
            (t (/ (apply #'+ lista-sem-nil) (length lista-sem-nil)))
        )
    )
)
;; heuristica-cavalo
(defun heuristica-cavalo (no)
  "Recebe o no, coordenada objetivo e o valor objetivo, Retorna a heuristica pretendida"
  (let* ((valor-objetivo (let* ((lista-sem-nil (apply #'append (mapcar #'(lambda (lista) (remove-if #'(lambda (n) (or (null n) (equal (write-to-string n) "T"))) lista)) (no-tabuleiro no))))) 
            (cond 
                ((null lista-sem-nil) 0)
                (t (apply #'max lista-sem-nil))
            )
         ))
         (coordenada-objetivo (posicao-casa (no-tabuleiro no) valor-objetivo))
         (coordenada-cavalo (let* ((p (posicao-cavalo (no-tabuleiro no))))
                                (cond ((null p) '(0 0)) (t p) )
                            )
         )
         (dm (distanca-manhattan coordenada-cavalo coordenada-objetivo))
         (pontos-acumulados (no-pontos-acumulados no)))
    (+ (* 0.5 dm) pontos-acumulados)))
;; distanca-manhattan
(defun distanca-manhattan (p1 p2)
  "Calcula a distanca Manhattan entre dois posicoes no tabuleiro"
  (let ((x1 (first p1))
        (y1 (second p1))
        (x2 (first p2))
        (y2 (second p2)))
    (+ (abs (- x1 x2)) (abs (- y1 y2)))))

Algoritmos de pesquisa

Foram implementados 3 algoritmos de pesquisa:

  • BFS (Breadth First Search)
  • DFS (Depth First Search)
  • A*
  1. BFS (Breadth First Search)

BFS é um algoritmo de pesquisa em grafos que explora todos os vértices de um nível antes de passar para o próximo nível. Começa pelo nó inicial e visita todos os seus vizinhos antes de se mover para os vizinhos dos vizinhos.

Para o efeito foi criada a seguinte função recursiva:

;; bfs
(defun bfs (no-inicial f-objetivo f-sucessores operadores valor-objetivo &key (abertos nil) (fechados nil) (d nil) (heuristica 0))
   "Através do algoritmo bfs, retorna o no solução, NIL caso contrario"
   (labels (
               (recursiva (abertos-atual fechados-atual nos-gerados)
                   (cond
                       ((null abertos-atual) NIL)
                       (t  (let*   (
                                       (no-atual (car abertos-atual))
                                       (sucessores (funcall f-sucessores no-atual operadores valor-objetivo))
                                       (novos-abertos (inserir-bfs (cdr abertos-atual) sucessores))
                                       (novos-fechados (cons no-atual fechados-atual))
                                   )
                               (cond
                                   ((funcall f-objetivo no-atual valor-objetivo) (list no-atual novos-abertos novos-fechados (+ nos-gerados (length sucessores))))
                                   (t (recursiva novos-abertos novos-fechados (+ nos-gerados (length sucessores))))
                               )
                           )
                       )
                   )
               )
           )
   
           (recursiva  (cons no-inicial abertos) fechados 0)
   )
)
  1. DFS (Depth First Search)

DFS é um algoritmo de pesquisa em grafos que explora o máximo possível ao longo de um ramo antes de retroceder. Começa pelo nó inicial e mergulha profundamente até atingir o fim de um caminho antes de retroceder.

Para o efeito foi criada a seguinte função recursiva:

;; dfs
(defun dfs (no-inicial f-objetivo f-sucessores operadores valor-objetivo &key (abertos nil) (fechados nil) (d nil) (heuristica 0) (heuristica 0))
   "Através do algoritmo dfs, retorna o nó solução, NIL caso contrário."
   (labels (
               (recursiva (abertos-atual fechados-atual nos-gerados)
                   (cond
                       ((null abertos-atual) NIL)
                       (t (let* (
                                   (no-atual (car abertos-atual))
                                   (sucessores (funcall f-sucessores no-atual operadores valor-objetivo))
                                   (novos-abertos  (cond
                                                       ((and (not (null d)) (> (no-profundidade no-atual) d)) (recursiva (cdr abertos-atual)))
                                                       (t (inserir-dfs (cdr abertos-atual) sucessores))    
                                                   )
                                   )
                                   (novos-fechados (cons no-atual fechados-atual))
                               )
                               (cond
                                   ((funcall f-objetivo no-atual valor-objetivo) (list no-atual novos-abertos novos-fechados (+ nos-gerados (length sucessores))))
                                   (t (recursiva novos-abertos novos-fechados (+ nos-gerados (length sucessores))))
                               )
                           )
                       )
                   )
               )
           )
       (recursiva (cons no-inicial abertos) fechados 0)
   )
)
  1. A*

A* é um algoritmo de pesquisa heurística que combina a eficiência do Dijkstra com uma heurística para estimar o custo restante até o objetivo. Avalia os nós com base no custo do caminho percorrido e na estimativa do custo restante até o objetivo.

Para o efeito foi criada a seguinte função recursiva:

;; aestrela
(defun aestrela (no-inicial f-objetivo f-sucessores operadores valor-objetivo &key (abertos nil) (fechados nil) (d nil) (heuristica 0))
   "Através do algoritmo A*, retorna o nó solução, NIL caso contrário."
   (labels (
               (recursiva (abertos-atual fechados-atual nos-gerados)
                   (cond
                       ((null abertos-atual) NIL)
                       (t  (let*   (
                                       (no-atual (no-menor-custo abertos-atual))
                                       (sucessores (funcall f-sucessores no-atual operadores valor-objetivo))
                                       (novos-abertos (inserir-aestrela (remover-no no-atual abertos-atual) fechados-atual sucessores))
                                       (novos-fechados (cons no-atual fechados-atual))
                                   )
                               (cond
                                   ((funcall f-objetivo no-atual valor-objetivo) (list no-atual novos-abertos novos-fechados (+ nos-gerados (length sucessores))))
                                   (t (recursiva novos-abertos novos-fechados (+ nos-gerados (length sucessores))))
                               )
                           )
                       )
                   )
               )
           )
       (recursiva (cons no-inicial abertos) fechados 0)
   )
)

4. Análise Comparativa

BFS

Problema Valor acumulado Opção Nós gerados Nós expandidos Penetrância Profundidade FRM Tempo Observações
A A0 Não tem solução
A 72 B0 2 3 3/2 3 1 micro-segundos
A C0 Não tem solução
B 65 A0 9 10 10/9 10 2 micro-segundos
B 60 C0 9 10 8/9 8 3 micro-segundos
B E0 Não tem solução
B G0 Não tem solução
B I0 Não tem solução
C 272 A0 16 13 3/8 6 4 micro-segundos
C B0 Não tem solução
C C0 Não tem solução
C D0 Não tem solução
C F0 Não tem solução
D 665 A0 40 40 13/40 13 14 micro-segundos
D 665 B0 32 31 3/8 12 10 micro-segundos
D 665 C0 29 30 12/29 12 10 micro-segundos
D D0 Não tem solução
D E0 Não tem solução
D 661 F0 36 36 1/3 12 12 micro-segundos
D 638 G0 29 28 12/29 12 11 micro-segundos
D H0 Não tem solução
D I0 Não tem solução
D 657 J0 35 34 12/35 12 11 micro-segundos
E B0 Stack overflow
E F0 Stack overflow (deep)
E J0 Stack overflow (deep)
F Todas as opções Stack overflow (deep)

DFS

Problema Valor acumulado Opção Nós gerados Nós expandidos Penetrância Profundidade FRM Tempo Observações
A A0 Não tem solução
A 72 B0 2 3 3/2 3 1046 micro-segundos
A C0 Não tem solução
B 65 A0 9 10 10/9 10 1428 micro-segundos
B 60 C0 9 10 8/9 8 1592 micro-segundos
B E0 Não tem solução
B G0 Não tem solução
B I0 Não tem solução
C 272 A0 11 8 6/11 6 1606 micro-segundos
C B0 Não tem solução
C C0 Não tem solução
C D0 Não tem solução
C F0 Não tem solução
D A0 Não tem solução
D 665 B0 16 13 3/4 12 1218 micro-segundos
D 665 C0 27 26 4/9 12 1406 micro-segundos
D D0 Não tem solução
D E0 Não tem solução
D 665 F0 18 14 2/3 12 976 micro-segundos
D 665 G0 20 17 13/20 13 1001 micro-segundos
D H0 Não tem solução
D 643 I0 31 30 10/31 10 1606 micro-segundos
D 657 J0 19 17 12/19 12 1689 micro-segundos
E B0 Stack Overflow (Deep)
E F0 Stack Overflow (Deep)
E J0 Stack Overflow (Deep)
F Todas as opções Stack Overflow (Deep)

A

Problema Valor acumulado Opção Nós gerados Nós expandidos Penetrância Profundidade FRM Tempo Observações
A Não tem solução
A 72 B0 2 3 3/2 3 4143 micro-segundos
A Não tem solução
B 65 A0 9 10 10/9 10 1686 micro-segundos
B 60 C0 8 8 1 8 1339 micro-segundos
B E0 Não tem solução
B G0 Não tem solução
B I0 Não tem solução
C 282 A0 14 11 3/7 6 1559 micro-segundos
C B0 Não tem solução
C C0 Não tem solução
C D0 Não tem solução
C F0 Não tem solução
D 665 A0 39 37 1/3 13 1484 micro-segundos
D 665 B0 32 29 3/8 12 1307 micro-segundos
D 665 C0 21 21 4/7 12 1212 micro-segundos
D D0 Não tem solução
D E0 Não tem solução
D 661 F0 35 30 12/35 12 1240 micro-segundos
D 661 G0 23 21 12/23 12 1243 micro-segundos
D H0 Não tem solução
D I0 Não tem solução
D 657 J0 32 27 3/8 12 2878 micro-segundos
E Todas as opções Stack Overflow (Deep)
F Todas as opções Stack Overflow (Deep)

5. Descrição das Opções Tomadas

Aqui descrevemos as opções de implementação que foram escolhidas em vez de outras alternativas. Certas opções podem não ser óbvias, e é importante documentá-las.

  • Por obrigação desta cadeira os algoritmos de pesquisa foram feitos de forma recursiva, sendo que de forma iterativa proporcionam melhores resultados, foram deixadas as soluções iterativas comentadas para caso de teste.

  • Devido à pouca memória disponibilizada pelo Lisp, evitou-se o uso excessivo de funções recursivas, em substituição optou-se por usar funções de ordem superior.

6. Limitações Técnicas e Ideias para Desenvolvimento Futuro

Nesta seção, discutimos:

  • Devido à pouca memória disponibilizada pelo Lisp alguns problemas apresentam "Stack Overflow" sobre tudo quando é abordada uma solução recursiva.
  • Para evitar este problema poderíamos melhorar as funções usando soluções iterativas ou funções de ordem superior.
  • A implementação das soluções Bónus não foi implementada, funções Simplified Memory‒Bounded A* (SMA*), Iterative Deepening A* (IDA*) e Recursive Best First Search (RBFS)

7. Conclusão

O projeto do jogo do cavalo, explorando os algoritmos de pesquisa em largura (BFS), pesquisa em profundidade (DFS) e A*, revelou-se uma jornada envolvente no campo da inteligência artificial e resolução de problemas complexos. Ao longo do manual técnico, foram abordadas as implementações detalhadas desses algoritmos e suas aplicações no contexto específico do tabuleiro de xadrez com movimentos de cavalo.

O algoritmo BFS, com sua abordagem de pesquisa nivelada, mostrou-se eficaz para encontrar soluções ótimas quando a prioridade é alcançar o objetivo com o menor número de movimentos possíveis. A sua característica de expandir os nós em camadas proporciona uma visão clara das soluções mais curtas.

A pesquisa em profundidade (DFS), por outro lado, revelou-se útil para explorar a fundo os caminhos do tabuleiro, proporcionando uma compreensão abrangente das possíveis soluções. No entanto, é crucial lidar com seus desafios, como ciclos e caminhos mais longos.

O algoritmo A* apresentou um equilíbrio eficiente entre otimização e exploração, incorporando uma heurística inteligente para guiar a pesquisa. Isso mostrou-se particularmente valioso em cenários complexos, permitindo encontrar soluções rápidas e eficientes.

Ao implementar estes algoritmos, ganhámos insights valiosos sobre a importância da escolha do algoritmo adequado para diferentes contextos. Cada abordagem tem seus pontos fortes e limitações, e a escolha dependerá das características específicas do problema em questão.

Em resumo, este manual técnico proporciona uma visão aprofundada do desenvolvimento e implementação de algoritmos de pesquisa para o jogo do cavalo.

Projeto 2

Logo de Markdown

Manual Técnico

  • Universidade: [Instituto Politécnico de Setúbal]
  • Projeto: [Projeto 2 - Jogo do Cavalo]
  • Aluno(s): [Julio Sousa (201902012)]
  • Data: 26/01/2024

1. Arquitectura do Sistema

O sistema do Jogo do Cavalo consiste em diversos módulos interligados para permitir a interação entre jogadores humanos e computadores, bem como a implementação dos algoritmos de inteligência artificial necessários para a jogabilidade. A arquitetura geral do sistema compreende:

  • Módulo de Interface com o Utilizador (Ficheiro interact.LISP)
  • Módulo de Lógica do Jogo (Ficheiro jogo.LISP)
  • Módulo de Algoritmos de Inteligência Artificial (Ficheiro algoritmo.LISP)

2. Entidades e sua Implementação

O sistema inclui as seguintes entidades principais:

Tabuleiro: Composto por uma lista de lista representa o tabuleiro de jogo, onde as peças se movem:

  (
    (NIL 25  63  -1  42  1   4  84  70  10 ) 
    (13  0   8   49  94  NIL 85 40  99  NIL) 
    (24  NIL NIL 61  53  50  98 89  33  64 )
    (18  54  31  65  83  48  47 28  7   21 ) 
    (NIL 72  NIL 81  80  88  87 45  51  22 ) 
    (27  52  56  19  35  -2  30 34  29  5  )
    (14  36  39  NIL 82  68  12 73  16  57 ) 
    (17  41  11  69  NIL 59  43 26  92  58 ) 
    (NIL 86  NIL 37  93  38  75 78  71  3  )
    (NIL 76  95  46  91  74  96 NIL NIL 67 )
)

Observamos um tabuleiro de dimensões 10x10 onde as casas podem adquirir os valores:

  • -1: Posição atual do jogador 1.
  • -2: Posição atual do jogador 2.
  • NIL: Casa marcada como visitada e inacessível pelos jogadores.
  • "Valor Numérico": Casa acessível pelos jogadores com o valor disponível para acumular.

Para gerar o tabuleiro temos a função tabuleiro-aleatorio

;; tabuleiro-aleatorio
(defun tabuleiro-aleatorio (lista &optional (n 10))
    "Recebe a lista e opcionalmente o n, Retorna o tabuleiro baralhado"
    (cond
        ((null lista) nil)
        (t (cons (subseq lista 0 n) (tabuleiro-aleatorio (subseq lista n) n)))
    )
)

Na interação com o tabuleiro temos seguintes:

  • posicao-casa: Recebe o tabuleiro e o valor da casa, as coordenadas da casa, NIL caso não exista
;; posicao-casa 
(defun posicao-casa (tabuleiro casa)
    "Recebe o tabuleiro e o valor da casa, as coordenadas da casa, NIL caso não exista"
    (let* (
            (lista (mapcar #'(lambda (linha) (position casa linha :test #'eql)) tabuleiro))
            (j (find-if #'(lambda (x) (not (null x))) lista))
            )
        (cond
        ((null j) NIL)
        (t (list (position-if #'(lambda (x) (not (null x))) lista) j))
        )
    )
)
  • substituir: Substitui o VALOR nas coordenadas (INDICE1, INDICE2) do TABULEIRO.
(defun substituir (indice1 indice2 tabuleiro &optional (valor nil))
    "Substitui o VALOR nas coordenadas (INDICE1, INDICE2) do TABULEIRO."
    (let* ((linha-substituida (substituir-posicao indice2 (linha indice1 tabuleiro) valor)))
      (substituir-posicao indice1 tabuleiro linha-substituida))
)
  • celula: Função que recebe dois índices e o tabuleiro e retorna o valor presente nessa célula do tabuleiro.
(defun celula (linha coluna tabuleiro)
  "Função que recebe dois índices e o tabuleiro e retorna o valor presente nessa célula do tabuleiro."
  (if (and (integerp linha) (integerp coluna) (< linha  (length tabuleiro)) (< coluna (length (first tabuleiro))))
      (nth coluna (nth linha tabuleiro))
      nil
  )
)

Jogador: Representa os jogadores, tanto humanos quanto computadores, e mantém o estado do jogo para cada jogador.

O cavalo possui um conjunto de operadores que representam os movimentos possíveis num determinado estado. O máximo de movimentos possíveis serão 8, desde que essas casas não tenham sido ainda visitadas ou removidas pela regra dos simétricos, duplos ou o outro jogador.

Exemplo de uma função operador:

;; operador-1 
(defun operador-1 (posicao)
    "Recebe a posicao retorna a coordenada"
    (list (+ (car posicao) 2) (- (second posicao) 1))
)

É importante observar que para os movimentos do cavalo tambem são adicionadas um conjunto de regras. Que são a seguintes:

  • Se a casa escolhida tiver um número com dois dígitos diferentes, por exemplo 57, então, em consequência, o número simétrico 75 é apagado do tabuleiro, tornando esta casa inacessível durante o resto do jogo. Ou seja, nenhum cavalo pode terminar outra jogada nessa casa.
  • Se um cavalo for colocado numa casa com um número "duplo", por exemplo 66, então qualquer outro número duplo pode ser removido e o jogador deve escolher qual em função da sua estratégia (por default remover a de maior valor). Depois de um jogador deixar a casa para se movimentar para outra, a casa onde estava fica também inacessível para o jogo, ficando o numero da casa apagado.

Com o objetivo de resolver estas dos problematicas foram implementadas a seguintes funções:

;; simetrico-solucao
(defun simetrico-solucao (coordenada-objetivo tabuleiro)
    "Recebe o tabuleiro, valor da coordenada objetivvo e o valor acumulado, retorna o novo estado"
    (let*   (
                (valor-celula (celula (car coordenada-objetivo) (second coordenada-objetivo) tabuleiro))
                (valor-simetrico (valor-simetrico valor-celula))
                (posicao-peca (posicao-casa tabuleiro valor-simetrico))
            )
        (cond
            ((not (null posicao-peca)) (substituir (car posicao-peca) (second posicao-peca) tabuleiro)) 
            (t tabuleiro)
        )
    )
)
;; duplo-solucao
(defun duplo-solucao (coordenada-objetivo tipo-jogo jogador tabuleiro)
    "Recebe o tabuleiro e o valor acumulado, retorna o novo estado"
    (let*   (
                (duplos-atuais (valores-duplos-atuais (celula (car coordenada-objetivo) (second coordenada-objetivo) tabuleiro) tabuleiro))
            )
        (cond
            ((null duplos-atuais) tabuleiro)
            (t
              (let* (
                        (escolha    (cond
                                        ((and (eql tipo-jogo 0) (eql jogador -1))
                                            (progn
                                                (format t "Escolher valor duplo a apagar:~%")
                                                (let*   (
                                                            (coor (escolher-coordenadas (mapcar #'(lambda (casa) (posicao-casa tabuleiro casa)) duplos-atuais)))
                                                        )
                                                    (celula (car coor) (second coor) tabuleiro)
                                                )
                                            )
                                        )
                                        (t
                                            (random (length duplos-atuais))
                                        )
                                    )
                        )
                        (posicao-peca (posicao-casa tabuleiro (nth escolha duplos-atuais)))
                    )
                    (substituir (car posicao-peca) (second posicao-peca) tabuleiro)
              )
            )
        )
    )
)

3. Algoritmos e sua Implementação

O sistema utiliza o algoritmo Alfa-Beta para a tomada de decisões dos jogadores computadorizados. O Algoritmo Alfa-Beta é uma variação do algoritmo Minimax, usado em jogos de decisão de múltiplas rodadas, como o Jogo do Cavalo. Ele permite uma análise eficiente das possíveis jogadas, reduzindo a quantidade de nós avaliados na árvore de busca.

Este algoritmo é fundamental para o desempenho e a competitividade dos jogadores computadorizados, permitindo que eles explorem profundamente as possíveis jogadas em um tempo razoável.

Alfabeta

;; alfabeta
(defun alfabeta (no p-max alfa beta e-max tempo-limite tempo-inicial &optional (tempo-atual 0) (num-nos-eval 0) (cortes-max 0) (cortes-min 0))
    "Recebe o no, profundidade maxima, o alfa e o beta, retorno o no com a melhor avaliação"
    (cond
        ((or (eql p-max 0) (>= (- tempo-atual tempo-inicial) (- tempo-limite 100000))) (list no num-nos-eval cortes-max cortes-min))
        (t 
            (let*   (
                        (max-eval -999999)
                        (min-eval 999999)
                        (nos-sucessores (sucessores no))
                        (no-eval (list (car nos-sucessores) num-nos-eval cortes-max cortes-min))
                    )
                (cond
                    ((null (car no-eval)) (list no (+ (length nos-sucessores) num-nos-eval) cortes-max cortes-min))
                    (e-max
                        (evaluar-sucessores-max nos-sucessores p-max max-eval no-eval alfa beta tempo-limite tempo-inicial(+ (length nos-sucessores) num-nos-eval) cortes-max cortes-min)
                    )
                    (t
                        (evaluar-sucessores-min nos-sucessores p-max min-eval no-eval alfa beta tempo-limite tempo-inicial (+ (length nos-sucessores) num-nos-eval) cortes-max cortes-min)
                    )
                )
            )
        )
    )
)

Avaliadores

;; avaliar-sucessores-max
(defun avaliar-sucessores-max (nos p-max max-eval no-max-eval alfa beta tempo-limite tempo-inicial &optional  (num-nos-eval 0) (cortes-max 0) (cortes-min 0))
    "Recebe os nos, profundidade maxima, o minimo valor, o no do minimo valor, o alfa e o beta, retorna o no avalidado"
    (cond
        ((null nos) (list (car no-max-eval) num-nos-eval cortes-max cortes-min))
        (t
            (let*   (
                        (eval (alfabeta (car nos) (1- p-max) alfa beta NIL tempo-limite tempo-inicial (get-internal-real-time) num-nos-eval cortes-max cortes-min))
                        (novo-max-eval (max max-eval (no-valor (car eval))))
                        (novo-no (cond ((> (no-valor (car eval)) max-eval) eval) (t no-max-eval)))
                        (novo-alfa (max alfa (no-valor (car eval))))
                    )
                (cond
                    ((<= beta alfa) (list (car novo-no) num-nos-eval (1+ cortes-max) (1+ cortes-min)))
                    (t (avaliar-sucessores-max (cdr nos) p-max novo-max-eval novo-no novo-alfa beta tempo-limite tempo-inicial num-nos-eval cortes-max cortes-min))
                )
            )
        )
    )
)
;; evaluar-sucessores-min
(defun evaluar-sucessores-min (nos p-max min-eval no-min-eval alfa beta tempo-limite tempo-inicial &optional (num-nos-eval 0) (cortes-max 0) (cortes-min 0))
    "Recebe os nos, profundidade minima, o minimo valor, o no do minimo valor, o alfa e o beta, retorna o no avalidado"
    (cond
        ((null nos) (list (car no-min-eval) num-nos-eval cortes-max cortes-min))
        (t
            (let*   (
                        (eval (alfabeta (car nos) (1- p-max) alfa beta T tempo-limite tempo-inicial (get-internal-real-time) num-nos-eval cortes-max cortes-min))
                        (novo-min-eval (max min-eval (no-valor (car eval))))
                        (novo-no (cond ((< (no-valor (car eval)) min-eval) eval) (t no-min-eval)))
                        (novo-beta (max beta (no-valor (car eval))))
                    )
                (cond
                    ((<= beta alfa) (list (car novo-no) num-nos-eval (1+ cortes-max) (1+ cortes-min)))
                    (t (evaluar-sucessores-min (cdr nos) p-max novo-min-eval novo-no alfa tempo-limite tempo-inicial novo-beta num-nos-eval cortes-max cortes-min))
                )
            )
        )
    )
)

Estados

Foram implementadas diversas funções para a criação, alteração e retorna das deversas carateristicas de um no, como são seguintes:

;; criar-no
(defun criar-no (pontos-j1 pontos-j2 tabuleiro &optional (g 0) (pai NIL) &aux (v (+ (* 3 g) (* 5 (- pontos-j1 pontos-j2)) (* 2 (obter-numero-casa-tabuleiro tabuleiro)))))
    "Recebe o tabuleiro e opcionalmente o acumulador de pontos, heuristica e o no pai, retorna um novo no"
    (list (list pontos-j1 pontos-j2 tabuleiro) g v pai)
)
;;; Metodos seletores
;; no-estado
(defun no-estado (no)
    "Recebe o no, retorna o estado"
    (car no)
)
;; no-pontos-acumulados-j1
(defun no-pontos-acumulados-j1 (no)
    "Recebe o no, retorna os pontos acumulados do jogador 1"
    (first (car no))
)
;; no-pontos-acumulados-j2
(defun no-pontos-acumulados-j2 (no)
    "Recebe o no, retorna os pontos acumulados do jogador 1"
    (second (car no))
)
;; no-tabuleiro
(defun no-tabuleiro (no)
    "Recebe o no e retorna o tabuleiro"
    (third (car no))
)
;; no-profundidade
(defun no-profundidade (no)
    "Recebe o no, retorna a profundidade"
    (second no)
)
;; no-valor
(defun no-valor (no)
    "Recebe o no, retorna o valor"
    (third no)
)
;; no-pai
(defun no-pai (no)
    "Recebe o no, retorna no pai"
    (fourth no)
)

Operadores

Os operadores foram abordados anteriormente neste documento e eles são encarregados de ditar as ações do cavalo neste jogo.

No total temos 8 operadores para os 8 posiveis movimentos do cavalo, as funções para representar cada operador são as seguintes:

;; operadores
(defun operadores ()
    "Retorna predicados referente aos operadores"
    (list 'operador-1 'operador-2 'operador-3 'operador-4 'operador-5 'operador-6 'operador-7 'operador-8)
)
;; operador-1
(defun operador-1 (posicao)
    "Recebe a posicao retorna a coordenada"
        (list (+ (car posicao) 2) (- (second posicao) 1))
)
;; operador-2
(defun operador-2 (posicao)
    "Recebe a posicao retorna a coordenada"
        (list (+ (car posicao) 2) (+ (second posicao) 1))
)
;; operador-3
(defun operador-3 (posicao)
    "Recebe a posicao retorna a coordenada"
        (list (+ (car posicao) 1) (+ (second posicao) 2))
)
;; operador-4
(defun operador-4 (posicao)
    "Recebe a posicao retorna a coordenada"
        (list (- (car posicao) 1) (+ (second posicao) 2))
)
;; operador-5
(defun operador-5 (posicao)
    "Recebe a posicao retorna a coordenada"
        (list (- (car posicao) 2) (+ (second posicao) 1))
)
;; operador-6
(defun operador-6 (posicao)
    "Recebe a posicao retorna a coordenada"
        (list (- (car posicao) 2) (- (second posicao) 1))
)
;; operador-7
(defun operador-7 (posicao)
    "Recebe a posicao retorna a coordenada"
        (list (- (car posicao) 1) (- (second posicao) 2))
)
;; operador-8
(defun operador-8 (posicao)
    "Recebe a posicao retorna a coordenada"
        (list (+ (car posicao) 1) (- (second posicao) 2))
)

Sucessores

Os sucessores definem os nós resultantes de cada nó explorado no algoritmo. E a criação do sucessores tem a seguinte estrutura:

;; sucessores
(defun sucessores (no)
    "Recebe o no e retorna os nos sucessores ao no, NIL caso não existir sucessores"
    (let*   (
                (tabuleiro (no-tabuleiro no))
                (pontos-j1 (no-pontos-acumulados-j1 no))
                (pontos-j2 (no-pontos-acumulados-j1 no))
                (coordenada-j1 (posicao-casa tabuleiro -1))
                (coordenada-j2 (posicao-casa tabuleiro -2))
                (coordenadas-j1 (obter-coordenadas-de-jogo -1 tabuleiro))
            )
        (cond
            ((null coordenadas-j1) 
                (let*   ((coordenadas-j2 (obter-coordenadas-de-jogo -2 tabuleiro)))
                    (cond
                        ((null coordenadas-j2) (list NIL))
                        (t (mapcar #'(lambda (coor2) 
                                (let*   (
                                            (estado (simular-jogada 1 -2 coordenada-j2 coor2 pontos-j2 tabuleiro))
                                        )
                                    (novo-sucessor no pontos-j1 (car estado) (third estado))
                                )
                            ) (obter-coordenadas-de-jogo -2 tabuleiro))
                        )
                    )
                )
            )
            (t
                (let*   ((estados-1 (mapcar #'(lambda (coor1)  (simular-jogada 1 -1 coordenada-j1 coor1 pontos-j1 tabuleiro)) coordenadas-j1)))
                    (apply #'append (mapcar #'(lambda (estado-1-atual)
                        (let*   ((coordenadas-j2 (obter-coordenadas-de-jogo -2 (third estado-1-atual))))
                            (cond
                                ((null coordenadas-j2)
                                    (list (novo-sucessor no (car estado-1-atual) pontos-j2 (third estado-1-atual)))
                                )
                                (t
                                    (let*   ((estados-2 (mapcar #'(lambda (coor2)  (simular-jogada 1 -2 coordenada-j2 coor2 pontos-j2 (third estado-1-atual))) coordenadas-j2)))
                                        (mapcar #'(lambda (estado-2-atual)
                                            (novo-sucessor no (car estado-1-atual) (car estado-2-atual) (third estado-2-atual))
                                        ) estados-2)
                                    )
                                )
                            )
                        )
                    ) estados-1))
                )
            )
        )
    )
)
;; novo-sucessor
(defun novo-sucessor (no pontos-j1 pontos-j2 tabuleiro)
    "Recebe o no, os pontos-j1, os pontos-j2 e o tabuleiro, retorna o no sucessor, NIL caso no existir o no sucessor"
    (criar-no pontos-j1 pontos-j2 tabuleiro (1+ (no-profundidade no)) no)
)

Heurística

A heurística se obtem através da soma Ponderada que atribui pesos a cada um dos fatores (profundidade, diferença de pontos, valor das casas restantes) e soma seus valores multiplicados pelos pesos correspondentes. Por exemplo:

Heurística = (Peso_Profundidade * Profundidade) + (Peso_Diferença_Pontos * Diferença de Pontos) + (Peso_Valor_Casas * Valor das Casas Restantes) 
  • implementação
    (v (+ (* 3 g) (* 5 (- pontos-j1 pontos-j2)) (* 2 (obter-numero-casa-tabuleiro tabuleiro))))

4. Análise Resultados

Humano vs Computador

Tabuleiro

(
    (51 3  5  59 80 71 69 42 21 78) 
    (29 56 6  9  75 92 8  33 30 16) 
    (36 24 88 74 99 90 37 62 40 67)
    (34 28 85 73 19 43 79 54 45 31) 
    (52 83 60 20 25 32 39 10 14 53) 
    (27 64 89 86 7  72 57 66 23 2 )
    (98 97 0  41 46 12 11 87 77 38) 
    (76 68 18 13 93 58 17 44 4  50) 
    (91 15 48 49 61 84 82 94 26 47)
    (55 81 63 35 1  65 70 95 22 96)
)

Tempo

  • 5000 milisegundo
Nos gerados Cortes max Cortes min Tempo
30790 780 2724 4.2667s
16293 651 1394 1.9442s
26475 882 2425 4.9506s
34765 1802 3696 4.0687s
23340 734 2529 4.2627s
13170 539 1493 1.6791s
7845 383 1134 1.4732s
6277 286 838 0.8317s
2124 145 298 0.3681s
2065 116 232 0.2988s
1213 67 149 0.1809s
146 11 18 0.0339s
5 0 0 0.0012s

Computador vs Computador

Tabuleiro

(
    (51 3  5  59 80 71 69 42 21 78) 
    (29 56 6  9  75 92 8  33 30 16) 
    (36 24 88 74 99 90 37 62 40 67)
    (34 28 85 73 19 43 79 54 45 31) 
    (52 83 60 20 25 32 39 10 14 53) 
    (27 64 89 86 7  72 57 66 23 2 )
    (98 97 0  41 46 12 11 87 77 38) 
    (76 68 18 13 93 58 17 44 4  50) 
    (91 15 48 49 61 84 82 94 26 47)
    (55 81 63 35 1  65 70 95 22 96)
)

Tempo

  • 5000 milisegundo
Nos gerados Cortes max Cortes min Tempo
34049 1390 3517 4.9504s
42121 1549 4009 4.9501s
10549 409 1019 1.4340s
18782 755 1853 3.3047s
23264 956 2151 4.9502s
17393 867 1466 2.7020s
30759 1236 2851 3.7869s
2929 89 375 0.4784s
12 0 0 0.0007s
14 0 0 0.0011s
10 0 0 0.0006s
10 0 0 0.0009s
4 0 0 0.0003s
10 0 0 0.0006s
2 0 0 0.0002s
4 0 0 0.0003s
6 0 0 0.0004s
8 0 0 0.0005s
6 0 0 0.0004s
4 0 0 0.0003s
4 0 0 0.0005s

5. Limitações Técnicas e Ideias para Desenvolvimento Futuro

Algumas limitações técnicas do sistema incluem:

  • Restrições de tempo de processamento para determinadas configurações de jogo.
  • Limitações na capacidade de avaliação de jogadas para jogadores computadorizados.

Para futuras versões do jogo, algumas ideias de desenvolvimento incluem:

  • Implementação de algoritmos mais sofisticados de inteligência artificial.
  • Adição de uma interface gráfica de usuário para melhorar a experiência do jogador.

6. Conclusão

Ao longo deste Manual Técnico, exploramos detalhadamente a implementação do sistema de Inteligência Artificial para o jogo do Cavalo. Utilizamos uma abordagem baseada no algoritmo Alfa-Beta, combinado com heurísticas que consideram a profundidade da busca, a diferença de pontos entre os jogadores e o valor das casas restantes no tabuleiro.

Além disso, apresentamos dois de modos de jogo, incluindo a opção de jogador contra computador e computador contra computador, oferecendo aos usuários diferentes experiências.

No entanto, durante o desenvolvimento do projeto, enfrentamos desafios significativos, como a implementação do algoritmo Alfa-Beta em cenários complexos, a seleção adequada de heurísticas para balancear a eficiência computacional e a precisão da busca.

Apesar dos desafios, o processo de desenvolvimento deste jogo proporcionou uma oportunidade valiosa para a aplicação dos conceitos teóricos aprendidos em sala de aula em um contexto prático e desafiador.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published