A tecnologia da Microsoft veio para ficar. Este blog regista o evoluir do meu conhecimento sobre .NET. No entanto este blog não pretende focar apenas a tecnologia em si mas também todas as aplicações desta.

Domingo, Fevereiro 06, 2005

Intersecções (4)

O nosso método Play tem um problema. É possível passar valores que não são possíveis no contexto do jogo. Por exemplo, é possível passar um valor negativo ou maior que quatro (o tabuleiro tem indices de zero a quatro). É também possível indicar o mesmo valor duas vezes, o que levaria a jogar numa casa já com uma peça. Vamos fazer alguns testes para demonstrar o problema.

[Test] public void GreaterThanFour() {
    try {
        Board board = new Board(2, 3, Piece.White);
        board.Play(7);
        Assert.Fail();
    }
    catch(BoardException exception) {
    }
}

Seria este o objectivo. Caso se tentasse jogar para numa posição não válida, era lançada uma excepção BoardException. Neste caso é lançada uma excepção IndexOutOfRangeException pois tenta-se indexar com um valor maior que o tamanho da matriz. Para resolver este problema basta apanhar essa excepção e lançar então uma excepção BoardException.

public void Play(Int32 move) {
    try {
        if(Piece.White == LastPlay()) PlayBlack(move);
        else PlayWhite(move);
    }
    catch(IndexOutOfRangeException exception) {
        throw new BoardException("Posição inválida.", exception);
    }
}

Com este código estou a envolver a excepção IndexOutOfRange numa excepção de tabuleiro, com a mensagem de posição inválida. Esta mensagem tem mais significado no contexto do jogo do que uma mensagem de indexação inválida. Temos agora de definir uma sobrecarga do construtor da excepção para receber uma string e uma excepção.

class BoardException : Exception {
    public BoardException() { }
    public BoardException(String message, Exception inner)
    : base(message, inner) { }
}

É assim possível lançar uma excepção de tabuleiro não indicando mais nada ou então passando uma mensagem e uma outra excepção. A palavra reservada base representa a classe base, neste caso System.Exception. Estamos assim a invocar o construtor da classe base, passando-lhe a mensagem e a excepção. Mas as jogadas inválidas não se resumem a valores demasiado elevados ou demasiado baixos passados ao Play. Também não se pode jogar numa célula que já contenha uma peça da nossa cor. Consideremos o seguinte teste:

[Test] public void DoublePlay() {
    try {
        Board board = new Board(2, 3, Piece.White);
        board.Play(3);
        board.Play(4);
        board.Play(2);
        board.Play(3);
        Assert.Fail();
    }
    catch(BoardException exception) {
    }
}

Este jogo vai colocar as seguintes peças: brancas(2, 3), negras(3, 3), brancas(3, 4), negras(2, 4), brancas(2, 3). Como a última jogada é igual à primeira, uma excepção deveria ser lançada. Para corrigir este problema podemos verificar se a célula para a qual pretendemos jogar já está ocupada por uma peça da nossa cor.

private void PlayBlack(Int32 move) {
    if(Piece.Black == board[move, originalWhite]) {
        throw new BoardException();
    }
    originalBlack = move;
    board[originalBlack, originalWhite] = Piece.Black;
    }
private void PlayWhite(Int32 move) {
    if(Piece.White == board[originalBlack, move]) {
        throw new BoardException();
    }
    originalWhite = move;
    board[originalBlack, originalWhite] = Piece.White;
}

Vamos compilar e correr todos os testes. Sim, tudo a verde. Já corrigimos alguns bugs. Antes de continuar a ver os problemas com as jogadas, vamos fazer algumas tarefas que tinham ficado pendentes. Primeiro, acho que fica melhor Cell em vez de Piece, dado que podemos considerar as células negras, brancas ou vazias. Estou, portanto, a considerar que não existem peças no jogo, existindo sim uma mudança das propriedades das células. Não fica mal, na minha opinião. Mas isto é claramente uma questão de gosto.
A outra tarefa consiste em iniciar o tabuleiro, dado que se pretende um valor específico e não um qualquer valor por omissão.

private void InitBoard() {
    for(Int32 i = 0; i != boardSize; ++i) {
        for(Int32 j = 0; j != boardSize; ++j) {
            board[i, j] = Cell.Empty;
        }
    }
}

O construtor deve, então, chamar este método.

public Board(Int32 originalBlack, Int32 originalWhite, Cell first) {
    if(first == Cell.Empty) throw new BoardException();
    InitBoard();
    this.originalBlack = originalBlack;
    this.originalWhite = originalWhite;
    board[originalBlack, originalWhite] = first;
}

Compilar e correr os testes. Tudo ok. O nosso código vai melhorando a pouco e pouco, penso eu. Mas ainda existem problemas. Primeiro, não se pode jogar numa casa que tenha uma peça do adversário a não ser que toda a linha ou coluna já esteja totalmente ocupada. Segundo, é necessário verificar se já existem quatro peças da mesma cor em linha. São estes dois pontos que vão ocupar a próxima mensagem. Assim que eles estiverem resolvidos, acho que é tempo de começar a pensar no interface com o utilizador.