12. Castling and En passant
The material used in this chapter can be downloaded at here.
We introduced a concept of objects in the last chapter. In this chapter, we implement castling and en passant on this chapter.
Castling | En passant |
---|---|
On chapter 11 we created lastMovedTurn
member, but we haven't yet used it. Currently this field is initialized to 0, but isn't updated thereafter. To use this field, we first have to put a correct number to lastMovedTurn
member. To do that we modify main.eps a bit.
/* Skipping */
var beforeUnitPlayer, beforeUnitType;
var beforeUnitX, beforeUnitY = -1, -1;
var currentTurn = 1; // New
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);
tile.tile(selX, selY).lastMovedTurn = currentTurn; // New
currentTurn++; // New
}}} /* Skipping */
Put simply, we created currentTurn
variable to store the current turn number, and as we place the pieces we update their lastMovedTurn number. The expression tile.tile(selX, selY).lastMovedTurn = currentTurn;
may seem complex, but you can paraphrase this expression to this code.
// Create a piece on (selX, selY)
const piece = tile.tile(selX, selY); // Get the created piece object.
piece.lastMovedTurn = currentTurn; // Set its LMTN to currentTurn
So we have valid LMTN value for each pieces. Let's jump on implementing castling.
To perform a castling, we move the king pieces by two cells right or left, and then we move the rook right next to the king, on the opposite direction the king has moved to. So it's the king that first moves on the castling. So wee need to modify king.eps to perform castling. king.eps currently looks like this.
function getPossibleDestination(player, x, y) {
const dxTable = [-1, 0, 1, -1, 1, -1, 0, 1];
const dyTable = [-1, -1, -1, 0, 0, 1, 1, 1];
for(var direction = 0 ; direction < 8 ; direction++) {
const dx, dy = dxTable[direction], dyTable[direction];
if (common.canPieceGoTo(player, x + dx, y + dy)) {
common.placeScourge(player, x + dx, y + dy);
}
}
}
Remember three conditions for castling?
- Both the king piece and the rook piece shouldn't have moved before.
- There are no pieces between two pieces.
- King is not currently in check, and king won't be checked after the castling.
We won't think about the third condition for now. Just check the first and the second one. The first condition can be checked with if (kingPiece.lastMovedTurn == 0 && rookPiece.lastMovedTurn == 0)
, where const kingPiece = tile.tile(x, y);
. We can check the second condition and get what rookPiece
is with a simple for loop, like this.
// Going to the left side
for(var x1 = x - 1 ; x1 < 8 ; x1--) {
const piece = tile.tile(x1, y);
if(piece) {
if(x1 == 0 && piece.unitType == $U('Rook') && piece.player == player) {
// rookPiece is piece.
break;
}
else break; // Something is between rook and king, or rook is not at x1=0.
}
}
// Going to the right side
for(var x1 = x + 1 ; x1 < 8 ; x1++) {
const piece = tile.tile(x1, y);
if(piece) {
if(x1 == 7 && piece.unitType == $U('Rook') && piece.player == player) {
// rookPiece is piece.
break;
}
else break; // Something is between rook and king, or rook is not at x1=7.
}
}
Two for loops look similar, so we merge them.
// Going to the left side
const dxTable = [-1, 1]; // dx
const rookXTable = [0, 7]; // corresponding rook's expected column.
for(var i = 0 ; i < 2 ; i++) {
const dx = dxTable[i];
for(var x1 = x + dx ; x1 < 8 ; x1 += dx) {
const piece = tile.tile(x1, y);
if(piece) {
if(x1 == rookXTable[i] && piece.unitType == $U('Rook') && piece.player == player) {
// rookPiece is piece.
if (piece.lastMovedTurn == 0) {
// We can perform a castling
break;
}
}
else break; // Something is between rook and king, or rook is not at x1=0.
}
}
}
To summarize, king.eps now looks like this.
import tile;
import loc;
import pieceRule.common;
function getPossibleDestination(player, x, y) {
const dxTable = [-1, 0, 1, -1, 1, -1, 0, 1];
const dyTable = [-1, -1, -1, 0, 0, 1, 1, 1];
for(var direction = 0 ; direction < 8 ; direction++) {
const dx, dy = dxTable[direction], dyTable[direction];
if (common.canPieceGoTo(player, x + dx, y + dy)) {
common.placeScourge(player, x + dx, y + dy);
}
}
const kingPiece = tile.tile(x, y);
if (kingPiece.lastMovedTurn == 0) {
const dxTable2 = [-1, 1]; // dx
const rookXTable = [0, 7]; // corresponding rook's expected column.
for(var i = 0 ; i < 2 ; i++) {
const dx = dxTable2[i];
for(var x1 = x + dx ; x1 < 8 ; x1 += dx) {
const piece = tile.tile(x1, y);
if(piece) {
if(x1 == rookXTable[i] && piece.unitType == $U('Rook') && piece.player == player && piece.lastMovedTurn == 0) {
common.placeScourge(player, x + dx * 2, y);
}
else break;
}
}
}
}
}
Done that, we now have this. I moved the right rook back and forth to update its LMTN. We now know the directions where a king can perform castling to.
Unlike any other movement in chess, you move two pieces during the castling. Piece moving is handled on afterTriggerExec
on main.eps. We have to modify this logic a bit, too. This modification is dead simple.
if(unitType == $U('Zerg Scourge')) {
piece.removePiece(selX, selY);
piece.placePiece(selX, selY, beforeUnitType, beforeUnitPlayer);
tile.tile(selX, selY).lastMovedTurn = currentTurn;
// Special rule for castling
if(beforeUnitType == $('King') && beforeUnitY == selY) {
const deltaX = selX - beforeUnitX; // X change of the king piece.
if(deltaX == -2 || deltaX == 2) { // Castling was performed.
// Also move rook.
const rookBeforeX = (deltaX == -2) ? 0 : 7;
const rookAfterX = selX + (deltaX == -2) ? 1 : -1;
piece.removePiece(rookBeforeX, selY);
piece.placePiece(rookAfterX, selY, $U('Rook'), beforeUnitPlayer);
tile.tile(rookAfterX, selY).lastMovedTurn = currentTurn;
}
}
currentTurn++; // We update currentTurn after processing castling rule.
piece.removePiece(beforeUnitX, beforeUnitY);
RemoveUnit('Zerg Scourge', Force3);
beforeUnitX = -1;
beforeUnitY = -1;
}
And now we have a castling!
Time for the re-factoring. afterTriggerExec
now takes up to 52 lines. A file with more than 200 lines is to big. Then, a function with more than 50 lines should be MASSIVE. Our afterTriggerExec
is an example of such massive function. We need to split it.
This section is still under construction.
-
Creating chess
-
Appendixes