Vous êtes sur la page 1sur 15

Mestrado Integrado em Engenharia Informática e Computação

Relatório Intercalar do 1º Trabalho Prático da Disciplina de Programação em


Lógica 2008/2009

Luta de Cavalos

Autores:
David Manuel Rodrigues de Magalhães ei06119@fe.up.pt
Diogo Samuel Teixeira da Rocha ei06109@fe.up.pt

Porto, 6 de Outubro de 2008


Resumo
O trabalho consiste na implementação do jogo de tabuleiro Luta de Cavalos
através da linguagem de programação Prolog. O jogo contará no futuro com uma
componente gráfica 3D feita com a tecnologia OpenGL implementado pela linguagem
C++.
Na versão final deverá ser possível jogar um jogador humano contra outro,
humano contra computador e computador contra computador. Deverão também existir
dois níveis de dificuldade.

Introdução
Pretende-se que este trabalho permita aprender o paradigma da programação em
lógica, neste caso através do uso da linguagem Prolog, sendo que o objectivo é fazer um
jogo de tabuleiro usando estratégias de resolução de problemas que até agora ainda não
foram exploradas.
A escolha do jogo em questão (Luta de Cavalos), deve-se ao facto de o jogo ter,
de uma maneira geral, regras bastante simples, mas que, ao mesmo tempo, parece conter
várias vertentes, no que à programação diz respeito, o que é uma grande motivação para
quem pretende aprender a programar em lógica, uma vez que, para além de se trabalhar
com estruturas de dados algo complexas, também se tem de colocar o computador a
“pensar” como se estivesse um humano a jogar. Ao mesmo tempo, o jogo aparenta ser
bastante desafiante para o jogador, sendo que, depois de complementado com uma
interface 3D, já se tratará de um projecto com alguma dimensão no que à complexidade
diz respeito.
As principais características e dificuldades que este jogo apresenta e que mais
cativaram a atenção dos elementos do grupo são o facto de tabuleiro ter se ser
construído aleatoriamente, as peças têm uma movimentação complexa e de, à medida
que o jogo avança, existirem casas que não podem ser visitadas, o que implica que se
têm de fazer verificações adicionais.
De salientar ainda que o jogo era desconhecido dos elementos do grupo, pelo
que isso representa, por si só, mais uma motivação para ver o jogo funcional.

1
Descrição do Problema
Luta de Cavalos é um jogo original criado por Andy Lewicki. É uma mistura
interessante de ideias inspirado pelos jogos de contagem de pontos e de território. As
regras que a seguir são apresentadas foram retiradas do site
http://brainking.com/pt/GameRules?tp=110.

A Luta de Cavalos desenrola-se num tabuleiro de 10x10 casas. Às 100 casas são
atribuídas, de forma aleatória, números de 00 a 99 (00, 01, 02, ..., 97, 98, 99). A figura 1
mostra um exemplo do tabuleiro inicial:

Figura 1: Estado do tabuleiro inicial.


[http://brainking.com/pt/GameRules?tp=110, Outubro 2008]

O objectivo do jogo é acumular mais pontos que o adversário, usando um cavalo


de xadrez. Um jogador tem um cavalo branco e o outro tem um cavalo preto.

Regras do jogo
O jogo começa com a colocação do cavalo branco na primeira linha (A1-J1) do
tabuleiro. 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 é retirado do tabuleiro, tornando
esta casa inacessível durante o resto do jogo. Ou seja, nenhum cavalo pode terminar
outra jogada nessa casa. Se a casa escolhida tiver o número 9, então o número 90 é
retirado do tabuleiro.
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. Depois de um jogador deixar a casa para se movimentar
para outra, a casa onde estava fica também inacessível para o resto do jogo.

2
À primeira jogada (colocar o cavalo branco) segue-se a colocação do cavalo
preto em qualquer casa da última linha (A10-J10) do tabuleiro. O número simétrico
correspondente é também apagado.
Depois de ambos os cavalos serem colocados, todas as jogadas seguintes são
efectuadas através de um salto de cavalo, usando as regras tradicionais do Xadrez para o
cavalo, isto é, anda duas casas numa direcção vertical e uma casa numa direcção
horizontal, ou vice-versa. Um cavalo não pode terminar a jogada numa casa vazia (sem
número) e também não pode fazê-lo para uma casa que esteja ameaçada pelo cavalo
adversário, o que quer dizer que o cavalo branco não pode jogar para uma casa se o
cavalo preto poder jogar para a mesma casa na jogada seguinte. Evidentemente que isto
também se aplica na situação inversa.
Um jogador ganha pontos por cada casa visitada pelo seu cavalo (incluindo a
colocação inicial). Os pontos são contabilizados apenas para as casas visitadas, não
pelos números simétricos

Fim do Jogo
O jogo termina quando um dos jogadores não puder fazer uma jogada. Quando
isso sucede, o jogador com mais pontos é o vencedor. Se ambos os jogadores tiverem o
mesmo número de pontos, o jogo termina empatado.

Níveis de Dificuldade
O jogo tem dois níveis de dificuldade:
• No nível fácil, as jogadas que o computador faz são escolhidas aleatoriamente;
• No nível difícil, o computador já verifica qual a jogada da qual pode tirar mais
proveito, no momento, e executa-a.

Representação do estado do jogo


Para representar o tabuleiro usaram-se listas. Neste caso trata-se de uma lista
com dez listas, sendo que cada uma destas dez listas corresponde a uma linha do
tabuleiro e contem dez elementos.
Inicialmente a lista de listas só contem números de 0 a 99. Após a primeira
jogada, a posição na lista de listas que representa a casa que o jogador escolheu passa a
ficar com um -1, para o cavalo branco, e -2 para o cavalo preto. Quando as casas ficam
vazias, na lista de listas elas são representadas com um -3.
Para além disto, é necessário guardar a informação relativa a cada jogador, para
facilitar a programação. Assim criou-se o seguinte predicado:
jogador(NJogador, TipoJ, P, X, Y) 
em que NJogador representa o número do jogador, TipoJ representa o tipo de jogador
(se é humano ou se é o computador), P indica os pontos do jogador e X e Y representam
a posição do tabuleiro em que o seu cavalo se encontra. Este predicado vai sendo
actualizado, à medida que se vai avançando no jogo.

3
Representação de uma jogada
Neste jogo existem dois tipos de jogadas: a primeira jogada e as outras todas. A
primeira consiste em meter o cavalo no tabuleiro. Ora para esta jogada a única restrição
é que o cavalo deve ser colocado na primeira linha do lado de cada jogador. A peça é
colocada no tabuleiro através de:
jogada_inicial(NJogador, Tabuleiro, NovoTab):‐ … 
em que NJogador é o número do jogador. Quanto a Tabuleiro e NovoTab,
correspondem ao estado do tabuleiro antes e depois da jogada. A partir de NJogador,
consegue-se obter o tipo de jogador. Depois é pedido ao jogador a casa para onde quer
jogar e verifica-se se a jogada é possível. Se for, os pontos do jogador são actualizados,
bem como as suas coordenadas. A peça é, então, colocada, na nova posição no
NovoTab. O algoritmo usado é o de copiar linha a linha o conteúdo de Tabuleiro para
NovoTab até chegar à linha onde se pretende fazer a alteração; uma vez aí, passa-se a
copiar elemento a elemento até se chegar à casa pretendida. Depois de mudar o estado da
casa, copia-se elemento a elemento até acabar a linha em questão e, depois, novamente,
linhas inteiras.
As outras jogadas sem ser a primeira são sempre iguais. Primeiro é solicitado ao
jogador para colocar as coordenadas da casa de destino da jogada; se a jogada for válida
a peça é movida através do seguinte predicado:
move_cavalo(NJogador, X, Y, Tabuleiro, NovoTab):‐ … 
em que NJogador, Tabuleiro e NovoTab têm o mesmo significado que no predicado
da primeira jogada e X e Y são as coordenadas do destino do jogador. Ao contrário da
primeira jogada de cada jogador, todas as outras jogadas implicam que se altere duas
casas no tabuleiro: a casa de partida, que fica vazia, e a casa de destino, que passa a
conter o cavalo. Para não aumentar a complexidade do algoritmo, em princípio estas
duas alterações no tabuleiro vão ser feitas uma de cada vez, recorrendo ao algoritmo
usado na primeira jogada.
Para além disto, vai ser necessário, para as jogadas ficarem completas, tornar
vazia a casa que tem o número “simétrico” em relação à casa de destino de uma jogada.
Para isso, vai ter que se percorrer o tabuleiro elemento a elemento para descobrir onde
se encontra o número pretendido. Esse algoritmo vai ser implemento no predicado:
find_and_replace(Numero, NovoConteudo, Tabuleiro, NovoTab):­… 
em que Numero é o número que se pretende apagar e NovoConteudo é o se vai colocar
no lugar de Numero no NovoTab. Depois de se identificar o elemento é usado
algoritmo já referido para copiar o conteúdo de Tabuleiro para NovoTab.
É evidente que o tabuleiro tem que ser percorrido mais vezes, pois é necessário
verificar se as jogadas são possíveis e só depois se pode, efectivamente mover a peça (ver
anexo 1).

4
Visualização do Tabuleiro
O tabuleiro é relativamente grande, pois são dez linhas por dez colunas. Para
além disso é necessário mostrar a identificação de cada linha (de 1 a 10) e de cada
coluna (de A a J). Na figura 2 está o desenho do tabuleiro inicial de jogo.

Figura 2: Aspecto do tabuleiro inicial.

Nesta fase a maior dificuldade foi colocar os números aleatoriamente na lista de


listas. Para além disso foi necessário, para o tabuleiro ficar bem formatado, verificar se
o número a escrever tinha um ou dois algarismos, pois no caso de ter só um algarismo é
necessário colocar um espaço no lugar do algarismo das dezenas. 
write_zero(X):‐ X > ‐1, X < 10, write(' '). 

Para desenhar uma casa usa-se: 


draw_casa(Valor):‐ 
        write(' '), 
        ((Valor  ==  ‐1,  write('B  '))  |  (Valor  ==  ‐2,  write('P  '))  |  (Valor  ==  ‐3,  write('    '))  | 
((write_zero(Valor) | true), write(Valor))), 
        write('|'). 

Percorrendo uma lista recursivamente consegue-se desenhar uma linha: 


draw_line([H|List], X):‐ 
        draw_casa(H), 
        draw_line(List, X). 

Para terminar de desenhar uma linha tem que se verificar quando a lista já foi toda
percorrida e desenha-se o número da linha: 
draw_line([], X):‐ 
        write('  '), 
        ( write_zero(X) | true ), 
5
        write(X), 
        write(' #'), nl. 
 
Sabendo desenhar linhas, basta percorrer, recursivamente, o tabuleiro lista a lista para
desenhar todas as linhas: 
draw_tab([], _). 
draw_tab([T|Tabuleiro], [H|L]):‐ 
        write('# '), 
        (write_zero(H) | true), 
        write(H), write('  '), 
        draw_line(T, H), 
        draw_tab(Tabuleiro, L). 
 
Para ficar completo tem que se identificar as colunas com letras de A a J:
desenha_tabuleiro(Tab):‐ 
write('####################################################'), nl, 
                         write('#       A   B   C   D   E   F   G   H   I   J      #'), nl, 
                         draw_tab(Tab, [10,9,8,7,6,5,4,3,2,1]), 
                         write('#       A   B   C   D   E   F   G   H   I   J      #'), nl, 
                         write('####################################################'), 
nl, 
                         nl. 

Conclusões e Perspectivas de Desenvolvimento


Nesta fase de desenvolvimento do trabalho considera-se que os objectivos até
agora foram cumpridos, sendo que é importante realçar que se trata ainda de uma versão
muito primária do que se pretende fazer, pelo que o que é apresentado neste relatório
não é garantido que mantenha na versão final, principalmente no que diz respeito aos
predicados dos movimentos, pois são situações que ainda não foram implementadas, à
excepção da primeira jogada.
Quanto ao tabuleiro, e sua representação, parece que já atingiu a forma que terá
na versão final.
Os materiais contidos na página da disciplina foram bastante úteis para o
desenvolvimento do trabalho até agora e para perceber como funciona a linguagem
Prolog.
Estima-se que cerca de 20 a 30 por cento do trabalho já esteja pronto, pelo que,
daqui para a frente, com o tabuleiro já pronto, entra-se na parte do jogo propriamente
dita que é, previsivelmente, de maior complexidade.

6
Bibliografia
• http://en.wikibooks.org/wiki/Prolog/Lists (Consultado em Outubro de 2008).
• http://brainking.com/pt/GameRules?tp=110 (Consultado em Outubro de 2008).
• http://gollem.science.uva.nl/SWI-Prolog/Manual/db.html (Consultado em Outubro
de 2008).
• Eugénio Oliveira e Luís Paulo Reis, Materiais da Disciplina de Programação em
Lógica, disponível online a partir de http://paginas.fe.up.pt/~eol/LP/0809/
(Consultado em Outubro de 2008).

7
Anexos
• Anexo 1: Diagrama de verificações que antecedem uma jogada.

• Anexo 2: Código Prolog já implementado. Neste estado é possível visualizar o


tabuleiro e fazer a primeira jogada. Para isso deve-se correr: ?- cavalos..
Nota: deve-se colocar letras minúsculas quando é pedido para escolher a
primeira jogada.

% Autores: David Magalhães e Diogo Rocha


% Data: 25-09-2008

:- use_module(library(lists)).

% Dá inicio à Luta de Cavalos


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

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%INICIO DO JOGO%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Inicio do jogo, após a construção do tabuleiro


inicio_jogo(Tab):- abolish(jogador/5),
apresentacao,
tipo_jogo,
desenha_tabuleiro(Tab),
jogada_inicial(1, Tab, NovoTab),
desenha_tabuleiro(NovoTab),
jogada_inicial(2, NovoTab, NovoTab2),
desenha_tabuleiro(NovoTab2).

% Apresentação do jogo
apresentacao:-
8
write('Luta de Cavalos em Prolog - 30/09/2008 - David Magalhaes e Diogo
Rocha'), nl, nl.

% Escolher tipo de jogo


tipo_jogo:-
assert(jogador(1,_,0,-1,-1)), assert(jogador(2,_,0,-1,-1)).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%PRIMEIRA JOGADA%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Primeira jogada
jogada_inicial(NJogador, Tabuleiro, NovoTab):-
write('Indique a coluna que pretende para a primeira jogada'), nl,
repeat, get_code(Letra), Letra>=97, Letra=<106,
vx(Letra, XA),
(primeira_jogada_branco(NJogador, P, XA, Tabuleiro, NovoTab) |
primeira_jogada_preto(NJogador, P, XA, Tabuleiro, NovoTab)).

duplo(48, 0).
duplo(49, 11).
duplo(50, 22).
duplo(51, 33).
duplo(52, 44).
duplo(53, 55).
duplo(54, 66).
duplo(55, 77).
duplo(56, 88).
duplo(57, 99).

% É escolhido o número duplo que se pretende eliminar


get_num_duplo(Duplo):- write('Indique o número que pretende para a eliminar do
tabuleiro'), nl,
write('0 - 0, 1 - 11, 2 - 22, 3 - 33, 4 - 44, 5 - 55, 6 - 66, 7 - 77, 8 - 88, 9 -
99'),
repeat, get_code(Numero), Numero>=48, Numero=<57,
duplo(Numero, Duplo).

% Primeira jogada do cavalo branco (Jogador1)


primeira_jogada_branco(NJogador, P, X, Tabuleiro, NovoTab2):-
NJogador == 1,
muda_tab(-1, X, 9, Tabuleiro, NovoTab, P),
((num_duplo(P), get_num_duplo(IP)) | num_inv(P,IP)),
find_and_replace(IP, -3, NovoTab, NovoTab2),
9
jogador(1, TJ1, _, _, _),
retract(jogador(1, _, _, _, _)),
assert(jogador(1, TJ1, P, X, 9)).

% Primeira jogada do cavalo branco (Jogador2)


primeira_jogada_preto(NJogador, P, X, Tabuleiro, NovoTab2):-
NJogador == 2,
muda_tab(-2, X, 0, Tabuleiro, NovoTab, P),
((num_duplo(P), get_num_duplo(IP)) | num_inv(P,IP)),
find_and_replace(IP, -3, NovoTab, NovoTab2),
jogador(2, TJ2, _, _, _),
retract(jogador(2, _, _, _, _)),
assert(jogador(2, TJ2, P, X, 0)).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%MANIPULAÇÃO DO TABULEIRO%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Altera posição (X, Y) do tabuleiro para Pnov. Os pontos que estavam em (X,Y)
% são devolvidos em Pontos
% Copyright LPR 2003 (Jogo das Damas)
muda_tab(Pnov,X,Y,Tab,NovoTab,Pontos):-
muda_tab2(0,Pnov,X,Y,Tab,NovoTab,Pontos),!.

muda_tab2(_,_,_,_,[],[],_).
muda_tab2(Y,Pnov,X,Y,[Lin|Resto],[NovLin|Resto2],Pontos):-
muda_linha(0,Pnov,X,Lin,NovLin,Pontos),
N2 is Y+1,
muda_tab2(N2,Pnov,X,Y,Resto,Resto2,Pontos).
muda_tab2(N,Pnov,X,Y,[Lin|Resto],[Lin|Resto2],Pontos):-
N\=Y, N2 is N+1,
muda_tab2(N2,Pnov,X,Y,Resto,Resto2,Pontos).

muda_linha(_,_,_,[],[],_).
muda_linha(X,Pnov,X,[H|Resto],[Pnov|Resto2],H):-
N2 is X+1,
muda_linha(N2,Pnov,X,Resto,Resto2,H).
muda_linha(N,Pnov,X,[El|Resto],[El|Resto2],Pontos):-
N\=X, N2 is N+1,
muda_linha(N2,Pnov,X,Resto,Resto2,Pontos).
% End Of Copyright LPR 2003 (Jogo das Damas)

% Valores introduzidos pelo utilizador


vx(97, 0).
10
vx(98, 1).
vx(99, 2).
vx(100, 3).
vx(101, 4).
vx(102, 5).
vx(103, 6).
vx(104, 7).
vx(105, 8).
vx(106, 9).
vy(Input, Output):- Output is 10 - Input - 1.

% Numero Inverno
% num_inv(53,Y) - Devolve Y = 35
num_inv(X,Y):- N1 is floor(X / 10),
N2 is X - (N1*10),
Y is N2*10 + N1.

num_duplo(X):- num_inv(X,Y), Y == X.

value(List, Index, Element) :-


value(List, 1, Index, Element).

value([First|_], Index, Index, First).


value([_|Rest], I0, Index, Element) :-
I is I0 + 1,
value(Rest, I, Index, Element).

fm(List, Index, Index1, Element) :-


fm(List, 1, Index, Index1, Element).

fm([H|_], Index, Index, Index1, Val):- value(H, Index1, Val).


fm([_|Rest], I0, Index, Index1, Val):-
I is I0 + 1,
fm(Rest, I, Index, Index1, Val).

% Encontra Numero e substitui-o por NovoConteudo


find_and_replace(Numero, NovoConteudo, Tabuleiro, NovoTab):-
fm(Tabuleiro, Y, X, Numero),
X1 is X - 1,
Y1 is Y - 1,
muda_tab(NovoConteudo,X1,Y1,Tabuleiro,NovoTab,_).

%move_cavalo(NJogador, X, Y, Tabuleiro, NovoTab).


11
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%CRIA TABULEIRO%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Cria o Tabuleiro de jogo


cria_tabuleiro(X,Y):- shuffle(X,Y).

% Faz o shuffle de uma lista.


% Quando termina inicia a construção do tabuleiro
shuffle([], Lista_shuffle):-adicionaLinhasTab([], Lista_shuffle, 0, []).
shuffle(ListIn, ListOut) :-
choose(ListIn, Element),
delete(ListIn, Element, NewList),
add2end(Element, ListOut, NewList2),
shuffle(NewList, NewList2).

% Vai buscar um valor a uma lista, a partir do índice


getvalor([Head|_], 0, Head).
getvalor([_|Rest], Indice, Valor):-
N is Indice - 1,
getvalor(Rest, N, Valor).

% Escolhe um elemento da lista de forma aleatória


choose([], []).
choose(List, Elt) :-
length(List, Length),
random(0, Length, Index),
getvalor(List, Index, Elt).

% Adiciona um novo elemento à lista


add2end(X,[],[X]).
add2end(X,[H|T],[H|NewT]):-add2end(X,T,NewT).

% Conta as linhas que são adicionadas ao tabuleiro


% Este predicado funciona intercalado com o predicado linha
% Quando as linhas forem todas adicionadas, o jogo começa
adicionaLinhasTab(Tabuleiro, _, 10, _):- inicio_jogo(Tabuleiro).
adicionaLinhasTab(Tabuleiro, ListIn, NA, ListOut):-
NAaux is NA + 1,
linha(Tabuleiro, ListIn, 0, [], ListOut, NAaux).

% Forma e adiciona as linhas ao tabuleiro

12
linha(Tabuleiro, ListIn, 10, LinhaTab, ListOut, NA):- append(Tabuleiro, [LinhaTab],
TabuleiroAux),
adicionaLinhasTab(TabuleiroAux, ListIn, NA, ListOut).
linha(Tabuleiro, [H|ListIn], N, LinhaTab, _, NA):-
Naux is N + 1,
add2end(H, LinhaTab, LinhaTabAux),
delete(ListIn, H, ListInAux),
%write(ListInAux),
linha(Tabuleiro, ListInAux, Naux, LinhaTabAux, ListInAux, NA).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%DESENHA TABULEIRO%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Verificar se o número só tem um digito


write_zero(X):- X > -1, X < 10, write(' ').

% Desenhar valor
draw_casa(Valor):-
write(' '),
((Valor == -1, write('B ')) | (Valor == -2, write('P ')) | (Valor == -3, write(' ')) |
((write_zero(Valor) | true), write(Valor))),
write('|').

% Desenha o número da linha do lado direito


draw_line([], X):-
write(' '),
( write_zero(X) | true ),
write(X),
write(' #'), nl.

% Desenha linhas
draw_line([H|List], X):-
draw_casa(H),
draw_line(List, X).

% Desenha tabuleiro
draw_tab([], _).
draw_tab([T|Tabuleiro], [H|L]):-
write('# '),
(write_zero(H) | true),
write(H), write(' '),
draw_line(T, H),
draw_tab(Tabuleiro, L).
13
desenha_tabuleiro(Tab):-
write('####################################################'), nl,
write('# A B C D E F G H I J #'), nl,
draw_tab(Tab, [10,9,8,7,6,5,4,3,2,1]),
write('# A B C D E F G H I J #'), nl,

write('####################################################'), nl,
nl.

14