Criada: 2011-08-14 Modificada: 2011-08-16 (início da especificação juiz-jogador) Modificada: 2011-08-17 (término da especificação juiz-jogador) Modificada: 2011-08-20 (implementação classes básicas e JSP juiz) Modificada: 2011-08-21 (encapsulamento) Modificada: 2011-08-25 (interfaces e mudança no Jogador) Modificada: 2011-09-03 (juiz mais sofisticado: verifica se tem jogada)
O jogo Cilada será usado neste semestre, mas com regras modificadas. Ao invés de ser um jogo solitário, isto é, de um jogador, será transformado num jogo entre dois jogadores, da seguinte forma. Os jogadores se alternam colocando peças sobre o tabuleiro, e aquele que primeiro chegar a uma situação em que não tiver onde colocar nenhuma das peças ainda livres, perde. Se todas as peças forem validamente colocadas no tabuleiro, a partida será considerada empatada.
Cada jogo será mediado por um Juiz, escrito pelo instrutor, e terá
dois contendores, que chamaremos aqui de Jogador 1 e Jogador 2. O
Juiz dará uma ordem de inicialização para ambos os Jogadores,
através do método inicializar
. O único parâmetro deste
método é uma Posicao
, que conterá informações sobre o
tabuleiro e peças a serem usados no jogo. Haverá um limite de tempo
grande para a inicialização.
Após isto, o Juiz continuamente chamará o
método resposta
, alternadamente do Jogador 1 e do
Jogador 2, passando para cada Jogador a última jogada feita e
recebendo a resposta do Jogador em questão. Há uma diferença na
primeira chamada a resposta
, feita ao primeiro Jogador,
quando o parâmetro lance
passado será igual
a null
. Cada lance será analisado pelo Juiz para ver
se é válido antes de ser enviado pelo Juiz ao outro Jogador. Isto
se repetirá até a última jogada. Haverá um limite de tempo de 1
segundo para cada jogada.
Cada aluno deverá implementar a interface Jogador:
Jogador |
void inicializar (Posicao posicaoInicial) Jogada resposta (Jogada lance) |
As interfaces Jogada e Posicao, usadas na interface Jogador, farão
parte de uma base comum que será usada tanto pelos Jogadores como
pelo Juiz. Uma Posicao tem duas componentes: um Tabuleiro e uma
lista de Pecas. Para a lista, será usada a
interface List
oferecida pelo
pacote java.util
.
Posicao |
Tabuleiro getTabuleiro() List<Peca> getPecas() |
A interface de um Tabuleiro fornece o número de linhas, o número de
colunas e o tipo de cada casa, dado por um método
chamado tipo
que recebe uma Casa e retorna seu
tipo.
Tabuleiro |
int getLinhas() int getColunas() TipoCasa tipo (Casa casa) |
A indexação das casas e o significado da rotação (veja abaixo) é dada pela figura a seguir.
O TipoCasa é um enumerado com três valores: BOLA, QUADRO e CRUZ, representados em impressão pelos caracteres 'o', '#' e '+', respectivamente.
Uma Casa é simplesmente um par de coordenadas: linha e coluna.
Casa |
int getLinha() int getColuna() |
A lista de peças é formada por Pecas
. Há dois tipos
de Pecas: peças de duas partes e peças de três partes. Para
acomodar ambos os tipos, a interface Peca
define três
métodos, que retornam os tipos das três partes, caso a peça tenha
três partes, ou de duas partes com o terceiro método
retornando null
, caso a peça tenha apenas duas
partes.
Peca |
TipoCasa getA() TipoCasa getB() TipoCasa getC() |
Para peças de duas partes, é indiferente qual parte é chamada de a ou b. Desta forma, uma peça com tipos a='o', b='+' é equivalente a uma peça com a='+', b='o'.
Porém, o mesmo NÃO ocorre com peças de três partes. É importante observar que as partes a, b e c de uma peça com três partes referem-se às posições mostradas na figura a seguir. Note que toda peça de três partes pode, através de uma rotação, ser colocada na posição mostrada na figura. Lembramos que no jogo não é permitido "virar" uma peça, ou seja, refleti-la geometricamente. Assim, uma peça com tipos a='o', b='#', c='+' não pode ocupar o lugar de uma peça com a='+', b='#', c='o', e vice-versa. Só há uma forma de atribuir os tipos a, b e c a uma peça de três partes.
Por fim, uma Jogada fornece uma peça e um posicionamento para ela. O posicionamento é definido por uma Casa, que identifica a posição ocupada pela parte "a" da peça (seja ela de duas ou três partes), e por um inteiro, que assume valores 0, 1, 2 ou 3, e que define uma rotação, em múltiplos de 90 graus, no sentido anti-horário, a partir do ângulo zero na convenção da trigonometria (ou seja, 0 é indo para leste, 1 é indo para norte, etc.). Esta rotação mostra a posição, relativa a "a", da parte "b" da peça (seja ela de duas ou três partes). Com isto, o posicionamento completo da peça fica definido sem ambiguidade.
Jogada |
Peca getPeca() Casa getParteA() int getRotacao() |
De posse da especificação acima, podemos nos dedicar a implementar
o Juiz e os Jogadores. Chamaremos os tipos acima de tipos
básicos do jogo. Observe que todos são interfaces,
exceto TipoCasa
, que é um tipo enumerado.
Para auxiliar o entendimento, vamos rabiscar um diagrama mostrando a dependência entre os tipos: qual tipo depende de qual outro para ser definido. Para nos ajudar neste diagrama, vamos primeiro extrair todas as dependências dos diagramas UML dos tipos. O resultado seria o seguinte.
O seguinte diagrama resume todas estas dependências.
Note que o diagrama de dependência nos ajuda, ditando a ordem em que os tipos devem ser definidos para não gerar erro. Começamos com Casa, TipoCasa, que são tipos que não dependem de nenhum outro, e prosseguimos, sempre adicionando um tipo que dependa apenas dos que já foram definidos.
Veja aqui a implementação destes tipos básicos.
A técnica Jackson Structured Programming (JSP) é uma ótima alternativa para construir programas pequenos ou pequenas rotinas (métodos, em Java). Trata-se de estruturar seu programa como uma árvore, onde cada nó pode ser de uma dos seguintes tipos: sequência, iteração ou seleção. Se os filhos de um nó são sequências, significa que aquele nó é decomposto na execução sequencial de seus filhos, na ordem em que aparecem. Se os filhos de um nó são seleções, significa que apenas uma das opções será executada ("if"). Se o filho de um nó é iteração, significa que o nó é um loop que repetirá o filho até que alguma condição aconteça ("while"). Os filhos de um nó devem ser todos do mesmo tipo, e nós do tipo iteração só tem um filho.
Vamos exemplificar a técnica tentando descrever a ação de um Juiz em nosso jogo de cilada. A seguir vemos um diagrama JSP descrevendo o que seria o método main do Juiz, em alto nível.
Agora cada caixinha deve ser programada, isto é, transformada em código (na linguagem que for; no caso, faremos em Java) . As caixas que são nós internos da árvore (não são folhas) são facilmente programáveis, uma vez que correspondem simplesmente a sequências de comandos, ou "if"s, ou "while"s. As condições dos "while"s têm que ser programadas também. Entre as folhas, teremos aquelas que são simples e podem ser transformadas em código facilmente. Se alguma folha for medianamente complexa, poderá ser também desenhada via JSP.
© 2011 João Meidanis