Skip to content

11. Objects

phu54321 edited this page Feb 10, 2018 · 3 revisions

Table of Contents generated with DocToc

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

Throughout the chapter 11-12, We will implement Castling and En passant. To implement this we will discuss about a concept of the object.

Last moved turn number

Castling is rook and king swapping positions in a rough sense. See wikipedia article for more info. Castling rule can be described in a picture.

Castling rule. Source: Wikipedia (Castling)

Three things should be checked before performing a castling.

  1. Both the king piece and the rook piece shouldn't have moved before.
  2. There are no pieces between two pieces.
  3. King is not currently in check, and king won't be checked after the castling
    • Any squares the king would pass during the castling is not being attacked by opponent

The third condition will be discussed later when we talk about check and checkmates. The second condition is easy to implement. The problem is the first condition. To check the first condition, we should know if the piece had moved or not for each pieces.

En passant is a special rule of the pawn. Pawn can move two cell forward on its first move, but other pawn right next to the moved pawn can capture it, as if the captured pawn has moved only one cell.

En passant rule. Source: Wikipedia (En passant)

To perform an en passant, we should check for two conditions.

  1. Captured pawn has just moved two squares.
  2. Capturing pawn is right next to the captured pawn.

The second condition is easy to implement. Again, the problem is the first problem. We should know exactly when did the piece moved.

To solve these two problems, we will store the last turn the piece have moved for each pieces. For example in the opening below, the moved pawn have its last moved turn number 1, and the rest of the pieces would have their last moved turn number 0.

King's opening

By doing so, we can implement the first conditions of Castling and En passant like this.

  • Castling's first condition: If both the king and the rook's last moved turn number is 0.
  • En passant's first condition: If the captured pawn's last moved turn number is (currentTurn - 1).

Utilizing the last moved turn number, we can implement Castling and En passant quite easily.

By the way, the word "Last moved turn number" is quite long. I'll abbreviate that as "LMTN". If you have better name for this term, feel free to contact me at trgk @ www.staredit.net (this is not an email), or at http://cafe.naver.com/edac.

Storing LMTN in the in-memory board

LMTN is best stored for each pieces. The best place to store that is of course, the in-memory board. Mighty in-memory board. Like when we used setTile(x, y, unitType + 1000 * player) to store both unitType and player information to a single array EUDArray, we can just store unitType + 1000 * player + 10000 * LMTN for each cell.

Later when we implement a check, we will learn function pointer and also store function pointers for each cell. Then we have to cramp four different variables for each array item. This unnecessarily complicates the code. So, introducing an Object.

Objects

Object in euddraft is similar to struct in C/C++. Object is a really big concept in a programming world. But because this course is strictly for non-programmers, I will keep everything super simple and I won't introduce any non-essential things.

OK. Enough small talks for programmers.

Object is a pack of variables. Object have various variables inside. In this chapter we will create a Piece object TYPE, which defines three variables inside.

  • player: Owner of the piece.
  • unitType: Unit type of the piece.
  • lastMovedTurn: LMTN.

We define a Piece object type with this code.

object Piece {
    var player;
    var unitType;
    var lastMovedTurn;
};

Next, we create a Piece type object. Piece.alloc() creates a new piece type object. Each Piece object have three variables called player, unitType, and lastMovedTurn, as defined in Piece object type. We call these variables members of the Piece type. So player, unitType, and lastMovedTurn are members of the Piece type. After we create an Piece object, we can access their members like this.

const p = Piece.alloc();
p.player = 1;
p.unitType = $U('Zerg Scourge');
p.lastMovedTurn = 0;

Beware the difference between object type and objects! Think object type as a template, and object as a real thing. Object type defines what kind members each object should have. It's object that really has these members. See this illustration.

Object type vs Object

I may mix these terms in later chapters, as most programmers do. But please keep in mind this difference. When I say 'members of Piece', it means 'members of Piece type object'.

After we finished using the object p, we should free the object. Like StarCraft recycles 1700 unit slots to create units, Piece.alloc internally recycles some 16384 object slots. So if you don't free the object properly you will probably get 'Cannot allocate more objects' error after some time.

In fact you won't really see any error message. Your program will silently fail and randomly pump out "Unsupported EUD" errors.

const p = Piece.alloc();
// Use p
Piece.free(p);

So this is the basics of objects.

Using object on in-memory board.

First, we have to define a Piece object type. We place the Piece type definition on the top of piece.eps, before any imports.

It is VERY important to place the object type definition at the top of the file! euddraft may raise random error if you didn't.

object Piece {
    var unitType;
    var player;
    var lastMovedTurn;
};

Next, we tell tile.eps to use this definition.

  • array should have Piece objects as items.
  • In setTile, check if the cell was already set. If the cell was set, we free that object.
  • tile(x, y) should return Piece type, and setTile should get Piece type object as newVal parameter.
import piece;

const array = EUDArray(64);

function tile(x, y): piece.Piece {
    return array[8 * y + x];
}

function setTile(x, y, newVal: piece.Piece) {
    if (array[8 * y + x]) {
        piece.Piece.free(array[8 * y + x]);
    }
    array[8 * y + x] = newVal;
}

Here, function tile(x, y): piece.Piece means that the function will return a piece.Piece type object. We can directly get player member from piece object, so we no longer need getTileOccupiedPlayer function. So I removed it.

Note that we're actually storing the address of Piece object to array. So, if the value of array[8 * y + x] is 0, then it means that the cell doesn't point to any object. So here we check if (array[8 * y + x]) { to find out if the cell have previously assigned piece.

Next, we modify placePiece function a bit. It allocates a new instance of piece and calls tile.setTile with that piece.

function placePiece(x, y, unitType, player) {
    loc.move1x1Loc(x, y);
    CreateUnit(1, unitType, '1x1', player);

    const piece = Piece.alloc();
    piece.player = player;
    piece.unitType = unitType;
    piece.lastMovedTurn = 0;
    tile.setTile(x, y, piece);
}

Also, we modify other parts of the code. For instance, in main.eps,

beforeUnitPlayer = tileVal / 1000;
beforeUnitType = tileVal % 1000;

becomes

beforeUnitPlayer = tileVal.player;
beforeUnitType = tileVal.unitType;

You can compile the code after fixing main.eps and some pieceRules folder codes.

You can run main.edd during this process to see exactly what code needs to be fixed. For example, if you see error message like this during compilation,

    File "F:\Starcraft\maps\BroodWar\eudplib_teach\chess\pieceRule\pawn.eps", line 25, in f_getPossibleDestination
        if (x > 0 && tile.getTileOccupiedPlayer(x - 1, y + dy) == opponentPlayer) {
    AttributeError: 'module' object has no attribute 'f_getTileOccupiedPlayer'

it means that f_getTileOccupiedPlayer we just deleted were being used in pawn.eps, and we should modify pawn.eps to use piece.player instead.

After you run the code, you may get the following unsupported EUD error like this as you select a pawn.

Unsupported EUD FFFFFE5F

If you didn't, congratulations! Move on to the conclusions.

EUD Error: 0xFFFFFE5F means that some trigger wanted to read or write offset 0x000001A0 (= 0xFFFFFFFF - 0xFFFFFE5F) Since this is not what we've intended, we have to fix this bug.

Using epTrace to debug "Unsupported EUD" error

debug = de + bug. Removing bug.

epTrace is a tool that helps you debug this kind of errors. It tells exactly where you have made a mistake. Also it helps you optimize your epScript code.

You need at least euddraft 0.8.2.0 version to follow this step. Update your euddraft if you haven't.

First, add debug: 1 to [main] section of main.edd file.

[main]
input: basemap.scx
output: chess.scx
debug: 1

[main.eps]
[eudTurbo]

You'll have chess.scx.epmap on the chess folder. Open this file with epTrace.exe, like how you opened main.edd with euddraft.exe on chapter 2. epTrace.exe is in the same folder euddraft.exe is. You'll get this black screen.

epTrace scree

Next, run chess.scx in StarCraft.exe and select a pawn. You'll get this screen.

epTrace scree

This screen tells you exactly where did the code stopped. Lines in this screen is called stacktrace. The first line [0] ~\main.eps|afterTriggerExec|70 means that StarCraft is currently executing afterTriggerExec function inside main.eps, line 70. main.eps looks like this.

        /* line 69 */ 
        /* line 70 */ if (unitType == $U("Pawn")) pawn.getPossibleDestination(beforeUnitPlayer, selX, selY);
        /* line 71 */ else if(unitType == $U("Knight")) knight.getPossibleDestination(beforeUnitPlayer, selX, selY);

So StarCraft is now executing a line if (unitType == $U("Pawn")) pawn.getPossibleDestination(beforeUnitPlayer, selX, selY);. It checked the if condition and went through pawn.getPossibleDestination, as indicated by the second line [1] ~\pieceRule\pawn.eps|f_getPossibleDestination|25 on epTrace. This line means that the StarCraft jumped into afterTriggerExec inside pieceRule/pawn.eps, and now executing line 25 of pawn.eps. My pawn.eps looks like this.

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

        /* Line 24 */
        /* Lien 25 */ if (x > 0 && tile.tile(x - 1, y + dy).player == opponentPlayer) {
        /* Line 26 */    common.placeScourge(player, x - 1, y + dy);

        }

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

epTrace says that the 25th line of this code is broken. One can suspect that the line failed because the value of tile.tile(x - 1, y + dy) is 0, and we couldn't get player out of nowhere. So to fix this problem, we can just check if the value of tile.tile(x - 1, y + dy) is 0 before comparing its player value with opponentPlayer. We make an helper function for that purpose in common.eps.

function isTileOwnedBy(x, y, player) {
    const piece = tile.tile(x, y);
    if(!piece) return false;
    if(piece.player == player) return true;
    else return false;
}

false = 0, true = 1

And use this function in pawn.eps.

    /* Skipping */
        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 && common.isTileOwnedBy(x - 1, y + dy, opponentPlayer)) {  // Here (*)
            common.placeScourge(player, x - 1, y + dy);
        }

        if (x < 7 && common.isTileOwnedBy(x + 1, y + dy, opponentPlayer)) {  // Here (*)
            common.placeScourge(player, x + 1, y + dy);
        }
    /* Skipping */

Game will run well after this fix. Note that this fix isn't needed on getQRBPossibleDestination, since this function checks if the tile value is 0 before checking if the tile is occupied by opponents.

Conclusion

So we integrated Piece object type to our game. We haven't used or updated LMTN yet, but since this chapter already holds a lot of concept, I'll end this chapter here. In the next chapter we will update and use LMTN value to implement castling and en passant. See you soon!