Skip to content

09. Knight and Bishops

phu54321 edited this page Feb 10, 2018 · 8 revisions

Table of Contents generated with DocToc

The material used in this chapter can be downloaded at here.

Extending over other piece types.

In this chapter, we will implement knight and bishop's move. Currently function afterTriggerExec looks like this.

function afterTriggerExec() {
    const unitType, selX, selY = sel.getSelectedUnit();
    if (unitType != -1) {
        if(unitType == $U('Zerg Scourge')) {
            piece.removePiece(selX, selY);
            piece.placePiece(selX, selY, beforeUnitType, beforeUnitPlayer);
            piece.removePiece(beforeUnitX, beforeUnitY);
            RemoveUnit("Zerg Scourge", Force3);

            beforeUnitX = -1;
            beforeUnitY = -1;
        }
        else if(beforeUnitX != selX && beforeUnitY != selY) {
            const tileVal = tile.tile(selX, selY);
            beforeUnitPlayer = tileVal / 1000;
            beforeUnitType = tileVal % 1000;
            beforeUnitX = selX;
            beforeUnitY = selY;
            RemoveUnit("Zerg Scourge", Force3);
            pawn.getPossiblePawnDestination(beforeUnitPlayer, selX, selY);
        }
    }
    else {
        RemoveUnit("Zerg Scourge", Force3);
    }
    SetInvincibility(Enable, "(any unit)", AllPlayers, 'Anywhere');
}

Everything except pawn.getPossiblePawnDestination(beforeUnitPlayer, selX, selY); line is not tied to pawn. That is, every line except getPossiblePawnDestination line can be shared between different types of pieces. So to implement a knight, all we need to implement is knight.getPossibleKnightDestination(beforeUnitPlayer, selX, selY);, and we just choose which getPossibleDestination variant to call for each specific piece type. Here's the completed code.

function afterTriggerExec() {
    const unitType, selX, selY = sel.getSelectedUnit();
    if (unitType != -1) {
        if(unitType == $U('Zerg Scourge')) {
            piece.removePiece(selX, selY);
            piece.placePiece(selX, selY, beforeUnitType, beforeUnitPlayer);
            piece.removePiece(beforeUnitX, beforeUnitY);
            RemoveUnit("Zerg Scourge", Force3);

            beforeUnitX = -1;
            beforeUnitY = -1;
        }
        else if(beforeUnitX != selX || beforeUnitY != selY) {
            const tileVal = tile.tile(selX, selY);
            beforeUnitPlayer = tileVal / 1000;
            beforeUnitType = tileVal % 1000;
            beforeUnitX = selX;
            beforeUnitY = selY;
            RemoveUnit("Zerg Scourge", Force3);

            // We choose what getPossibleDestination to use based on the unit type.
            if (unitType == $U("Pawn")) {
                pawn.getPossiblePawnDestination(beforeUnitPlayer, selX, selY);
            }
            else if(unitType == $U("Knight")) {
                knight.getPossibleKnightDestination(beforeUnitPlayer, selX, selY);
            }
            // We will add bishops later.
        }
    }
    else {
        RemoveUnit("Zerg Scourge", Force3);
    }
    SetInvincibility(Enable, "(any unit)", AllPlayers, 'Anywhere');
}

Knights

This is it. Knights. I really love knights. My chess openings start with knights. Really, knights rules. Knight can move in 8 directions like seen in this picture.

Knight's move

So if the knight is at (x, y), the knight could move to (x ± 2, y ± 1) and (x ± 1, y ± 2). Knight can move to those positions if those positions are within the board, and those positions are not occupied by the piece of the same color. Here we will split this code into several functions.

In total, knight.eps looks like this.

import tile;
import loc;

function inBounds(x, y) {
    return (x < 8 && y < 8) ? 1 : 0;
}

function canPieceGoTo(player, x, y) {
    if(!inBounds(x, y)) return 0;
    if (tile.tile(x, y) == 0 || tile.getTileOccupiedPlayer(x, y) != player) {
        return 1;
    }
    else return 0;
}

function placeScourge(player, x, y) {
    loc.move1x1Loc(x, y);
    CreateUnit(1, 'Zerg Scourge', '1x1', player);
}

function getPossibleKnightDestination(player, x, y) {
    if (canPieceGoTo(player, x + 1, y + 2)) placeScourge(player, x + 1, y + 2);
    if (canPieceGoTo(player, x + 1, y - 2)) placeScourge(player, x + 1, y - 2);
    if (canPieceGoTo(player, x - 1, y + 2)) placeScourge(player, x - 1, y + 2);
    if (canPieceGoTo(player, x - 1, y - 2)) placeScourge(player, x - 1, y - 2);
    if (canPieceGoTo(player, x + 2, y + 1)) placeScourge(player, x + 2, y + 1);
    if (canPieceGoTo(player, x + 2, y - 1)) placeScourge(player, x + 2, y - 1);
    if (canPieceGoTo(player, x - 2, y + 1)) placeScourge(player, x - 2, y + 1);
    if (canPieceGoTo(player, x - 2, y - 1)) placeScourge(player, x - 2, y - 1);
}

inBounds function checks if position (x, y) is within the board. We have a new syntax here, called ternary operator. A ? B : C means that if select B if condition A is met, and select C otherwise. So return (x < 8 && y < 8) ? 1 : 0; means that if (x, y) position are within board boundary return 1, and return 0 otherwise. Think it as a shorthand of this code.

    if (x < 8 && y < 8) return 1;
    else return 0;

Why are we only checking if x and y are under 8, and not checking if x and y are over 0? That's because variables are unsigned in euddraft. Unsigned means that the variable can only have positive value, and negative values are internally processed as corresponding positive values instead. Negative value -x is internally processed as 4294967296 - x in euddraft. So, -1 is in fact 4294967295 inside euddraft. Really, this is a complex matter for non-computer-science-degrees and I won't dive deep into this matter. You can ignore this fact when doing addition/subtraction/multiplication, but you should consider this fact when doing division/comparison.

  • (Addition/Subtraction) 4294967295 + 3 = 2 in euddraft, Just like (-1) + 3 = 2.
  • (Multiplication) 4294967295 * 4294967295 = 1 in euddraft, just like -1 * -1 = 1.
  • (Equality) -1 == 4294967295 in euddraft.
  • (Division) 4294967295 / 3 = 1431655765, which is not what you'd expect in (-1) / 3.
  • (Comparison) However 4294967295 > 0 in euddraft, even if -1 < 0 in real world.

See an appendix about the two-complement system for more information.

Also in getPossibleKnightDestination function, we're putting canGoTo(player, x + 1, y + 2) to if statement. This is a shortcut for if (canGoTo(player, x + 1, y + 2) != 0). Same applies for if(!inBounds(x, y)) return 0; in canPieceGoTo function. Other than that the code is quite straightforward.

The result looks like this.

Knight implemented

Bishops

Bishop can move diagonally as much as it can until it meets the end of the board or another piece.

Bishop's move

This can be crafted quite easily with simple while loops. Given the position (x, y) of a bishop, we can check if the bishop can go to (x ± a, y ± a) for each a and find the lowest a where the position is occupied by another piece, of the position is out of bounds. Considering this, we can create bishop.eps like this.

import tile;
import loc;

// function inBounds(x, y) - same as knight.eps
// function placeScourge(player, x, y - same as knight.eps

function getPossibleBishopDestination(player, x, y) {
    const dxTable = [-1, -1, 1, 1];
    const dyTable = [-1, 1, 1, -1];
    var dx, dy;
    for(var direction = 0 ; direction < 4 ; direction++) {
        dx, dy = dxTable[direction], dyTable[direction];
        var x1, y1 = x + dx, y + dy;

        while(inBounds(x1, y1)) {
            if(tile.tile(x1, y1) == 0) placeScourge(player, x1, y1);
            else if(tile.getTileOccupiedPlayer(x1, y1) != player) {
                placeScourge(player, x1, y1);
                break;
            }
            else break;
            x1 += dx;
            y1 += dy;
        }
    }
}

Here we met another syntax. [-1, -1, 1, 1]; means EUDArray having -1, -1, 1, 1 as its items. Remember that you can reference each array item's value via array[i]? Likewise, you can access the values of dxTable like dxTable[0], dxTable[1], dxTable[2], dxTable[3]. Same goes for dyTable.

For direction = 0, 1, 2, 3, we're getting dx and dy values from dxTable and dyTable via dx, dy = dxTable[direction], dyTable[direction];. values of dx and dy looks like this.

direction dx dy
0 -1 -1
1 -1 1
2 1 1
3 1 -1

(dx, dy) pair points in four directions where bishops can go to. For example, when direction is 0, (x + dx, y + dy) is (x-1, y-1), which is the cell right at the upper left side of the current cell. (dx, dy) gets added to (x1, y1) in each loop, effectively advancing (x1, y1) to the direction one cell at a time.

For each (x1, y1), the cell may be unoccupied, occupied by the piece of the same color, or occupied by the piece of different color. If the cell is unoccupied then we mark the cell as a possible destination and advance (x1, y1). If the cell is occupied, then the loop stops and we add the cell to the list of possible destination if the cell is occupied by an enemy piece. The code itself is quite straightforward.

Next, we add a handler for the bishop at afterTriggerExec.

function afterTriggerExec() {
    /* Skipping... */
            // We choose what getPossibleDestination to use based on the unit type.
            if (unitType == $U("Pawn")) {
                pawn.getPossiblePawnDestination(beforeUnitPlayer, selX, selY);
            }
            else if(unitType == $U("Knight")) {
                knight.getPossibleKnightDestination(beforeUnitPlayer, selX, selY);
            }
            else if(unitType == $U("Bishop")) {
                bishop.getPossibleBishopDestination(beforeUnitPlayer, selX, selY);
            }
    /* Skipping... */
}

The result looks like this. Bishop works as intended.

Bishop implemented

Refactoring

Refactoring means cleaning the code. This includes splitting code files appropriately, making repeated code to functions, and putting appropriate names to each function or files.

We see that three files in pieceRules share a lot of code, so we can split some commonly used functions to common.eps.

import tile;
import loc;

function inBounds(x, y) {
    return (x < 8 && y < 8) ? 1 : 0;
}

function canPieceGoTo(player, x, y) {
    if(!inBounds(x, y)) return 0;
    if (tile.tile(x, y) == 0 || tile.getTileOccupiedPlayer(x, y) != player) {
        return 1;
    }
    else return 0;
}

function placeScourge(player, x, y) {
    loc.move1x1Loc(x, y);
    CreateUnit(1, 'Zerg Scourge', '1x1', player);
}

Next, we modify pawn.eps, bishop.eps, and knight.eps to use common.eps. For example, pawn.eps now looks like this.

import tile;
import loc;
import pieceRule.common;

function getPossiblePawnDestination(player, x, y) {
    var opponentPlayer, yStart, dy;
    if (player == $P7) {
        opponentPlayer = $P8;
        yStart = 1;
        dy = 1;
    }
    else {
        opponentPlayer = $P7;
        yStart = 6;
        dy = -1;
    }

    if (y + dy < 8) {
        if (tile.tile(x, y + dy) == 0) common.placeScourge(player, x, y + dy);

        if (y == yStart && tile.tile(x, y + dy) == 0 && tile.tile(x, y + 2 * dy) == 0) {
            common.placeScourge(player, x, y + 2 * dy);
        }

        if (x > 0 && tile.getTileOccupiedPlayer(x - 1, y + dy) == opponentPlayer) {
            common.placeScourge(player, x - 1, y + dy);
        }

        if (x < 7 && tile.getTileOccupiedPlayer(x + 1, y + dy) == opponentPlayer) {
            common.placeScourge(player, x + 1, y + dy);
        }
    }
}

See the completed material for more info. You can download the material here.