11. Objects
Table of Contents generated with DocToc
- Last moved turn number
- Storing LMTN in the in-memory board
- Objects
- Using object on in-memory board.
- Using epTrace to debug "Unsupported EUD" error
- Conclusion
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.
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.
Three things should be checked before performing a 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
- 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.
To perform an en passant, we should check for two conditions.
- Captured pawn has just moved two squares.
- 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.
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.
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.
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.
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.
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 havePiece
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 returnPiece
type, and setTile should getPiece
type object asnewVal
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 toarray
. So, if the value ofarray[8 * y + x]
is 0, then it means that the cell doesn't point to any object. So here we checkif (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 usepiece.player
instead.
After you run the code, you may get the following unsupported EUD error like this as you select a pawn.
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.
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.
Next, run chess.scx in StarCraft.exe and select a pawn. You'll get this screen.
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.
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!
-
Creating chess
-
Appendixes