#include "queens.h"
int CalculateSolutions(int rows, QUEENS_CALLBACK callback)
{
__asm
{
/*///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////Main Code///////////////////////////////////*/
/*//////////////////////////////////////////////////////////////////////
//Initialization:
// 1)save any of the registers we are about to use on the stack
// 2)save "this" position in the base pointer (just after the regs)
// 3)save width ("C" param: rows) on the stack
// 4)save callback function ("C" param: callback) on the stack
// NOTE: we have to save the width and callback BEFORE
// manipulating the base pointer
// 5)create memory on stack for the board (rows^2 bytes)
// NOTE: esp must point to EVEN memory address
// 6)clear this memory (clear board)
///////////////////////////////////////////////////////////////////////*/
pushfd
pushad //save our regs
mov eax, DWORD PTR [rows] //put width in eax
push eax //save width on stack
push DWORD PTR [callback] //save callback on stack
lea ebp, [esp + 4] //ebp is last reg. pushed
mul al //eax = width*width (maximum width: 255)
add eax, 3
shr eax, 2 //eax is # DWORDS needed to clear board
mov ecx, eax //get ready to rep stos
xor eax, eax //store 0's on stack
lea edi, [esp - 4] //[edi] is the first DWORD below callback
std //rep stos downward
rep stos DWORD PTR [edi] //zero ECX DWORDS below ESP
add edi, 4 //rep stos decrements one extra time
mov esp, edi //now stack points to top of board
/*/////////////////////////////////////////////
//Stack Check:
// |---------------|
// | (pushad) |
// | eax, edx, ecx | (in this order)
// | ebx, esb, esp | 32 bytes
// | esi, edi |
// |---------------|
//EBP-->| width | 4 bytes
// |---------------|
// | callback | 4 bytes
// |---------------|
// | (padding) | (rows^2 % 4) bytes
// |---------------|
// | board end |
// | .... |
// ~~~~~~~~~~~~~~~~~ (rows^2) bytes
// | .... |
//EDI-->| board start |<--ESP (currently)
// |---------------|
//
//Regs:
// EBP, EDI, ESP: as above
// EAX, ECX: 0
// (All are saved and can be manipulated
//
//Todo:
// Call StartOnRow and allow it to
// recursively solve the queen's problem
// with brute force (return in EAX)
/////////////////////////////////////////////*/
mov ebx, DWORD PTR [ebp] //get the width from memory
mov edx, edi //pointer to (0, 0) on board
call StartOnRow //start brute force algorithm
push ebp //save our entry point
push eax //and return value
//callback->finished(edi, MSG_FINISHED, 0, 0, 0, 0)
sub esp, 16 //last 4 params unused
mov ebx, MSG_FINISHED //make sure we push a DWORD
push ebx //msg
push edi //ptr to board
call DWORD PTR [ebp - 4] //see stack diagram above
add esp, 24 //restore stack
pop eax //restore return value
pop ebp //use this to find entry point
jmp FinishedASM //cleanup and return
/*///////////////////////////////////////////////////////////////////////////////
///////////////////////////////End of Main Code////////////////////////////////*/
/*///////////////////////////////////////////////////////
//StartOnRow()
//
// Function uses brute force and recursion to solve
// the queen's problem. Returns value is the
// number of solutions, and it is stored in EAX.
//
// Assumptions:
// EDI: points to start of board (and increasing
// the pointer travels towards the end of
// the board; only used with callback)
// AL: row that this call starts on
// EDX: points to the starting position on the
// board; (0, AL)
// EBP: points to the 4 byte width of the board
// and directly below it in memory is the
// 4 byte callback pointer
// EBX: width of the board (must be less than 255)
//
// Registers Used:
// EAX (during call): AL is current row and
// AH is current column
// EAX: (upon returning): Holds return value;
// previous contents destroyed
// EBX, EDI, EBP: remain unchanged
// ECX, EDX: previous contents destroyed
/////////////////////////////////////////////////////////*/
StartOnRow:
mov ah, 0 //always start on first column
xor ecx, ecx //clear ecx
push ecx //this will be our solutions counter
mov ecx, ebx //loop for each square in the row
//RowLoop: for each square in the row; if queen can be placed, place and recurse
RowLoop:
push ecx //and ecx
call IsThreatened //can we place a queen here?
cmp ch, 0 //check the return value
pop ecx //restore ecx
je QueenAccepted //ch == 0; place queen, recurse on next row
//can't place a queen, clear the return value and move on
xor ch, ch
jmp RowLoopEnd
//queen can be placed here, if this is the last row, increment counter and
//move on. If this is not the last row, recurse
QueenAccepted:
inc al //row WAS zero-indexed
cmp al, bl //our row compared to board size
jb RecurseNextRow //our row less than total rows
dec al //return al to zero-indexed
inc DWORD PTR [esp] //increment our solutions counter
pushad
//callback->solution(edi, MSG_SOLUTION, 0, 0, 0, 0)
sub esp, 16 //last 4 params unused
mov ebx, MSG_SOLUTION //make sure we push a DWORD
push ebx //msg
push edi //board pointer
call DWORD PTR [ebp - 4] //see stack diagram above
add esp, 24 //restore stack
popad
mov BYTE PTR [edx], 0 //remove the queen
jmp RowLoopEnd //go to next square
RecurseNextRow:
dec al //return al to zero-indexed
//IsThreatened places the queen on the board
push eax //save our current row/column
push edx //save our pointer to our row/column
push ecx //save our counter
add edx, ebx //go one row further
inc al //move coordinates one row further
movzx ecx, ah //move our width into 4 byte ecx
sub edx, ecx //move pointer to first of row
call StartOnRow //recurse
add DWORD PTR [esp + 12], eax //add retval to our solutions counter
pop ecx //restore our counter
pop edx //restore ptr to our row/column
pop eax //restore our row/column
mov BYTE PTR [edx], 0 //remove the queen and go to next square
pushad
//callback->backtracking(edi, MSG_BACKTRACK, al, 0, 0, 0)
sub esp, 12 //last 3 params unused
movzx ebx, al //use ebx to extend al to a DWORD
push ebx //push row we are backtracking to
mov ebx, MSG_BACKTRACK //make sure we push a DWORD
push ebx //msg
push edi //pointer to board
call DWORD PTR [ebp - 4] //see stack diagram above
add esp, 24 //restore stack
popad
RowLoopEnd:
inc edx //move pointer over one
inc ah //move column over one
loop RowLoop
//End of RowLoop
pop eax //put solutions counter in EAX
ret
/*///////////////////////////////////////////////////////////////////////////////
//////////////////////////////End of StartOnRow////////////////////////////////*/
/*/////////////////////////////////////////////////////////////
//IsThreatened()
//
// Function checks if it would be safe to place a
// queen at the given position of the board.
// If the queen would be threatened at the position
// CH is set to 1; otherwise, no changes are made to ECX
// If a position is found to be unthreatened, a queen is
// placed in that position to allow the callback to
// display the corrent board configuration.
//
// Assumptions:
// EDI: points to start of board (and increasing
// the pointer travels towards the end of
// the board; only used with callback)
// AL: row of queen to be placed
// AH: column of queen to be placed
// EDX: points to the position of the queen to
// be placed (AH, AL) on the board
// EBP: points to the 4 byte width of the board
// and directly below it in memory is the
// 4 byte callback pointer
// EBX: width of the board
//
// Registers Used:
// EAX (during call): (AH, AL) is the coordinates
// of the possible threatening queen
// EBX, EDI, EBP, EDX, EAX: remain unchanged
// ECX: previous contents destroyed
// CH: if queen is threatened, set to 1 and previous
// contents destroyed. Otherwise, it is unchanged
///////////////////////////////////////////////////////////////*/
IsThreatened:
push eax //save possible queen's board coordinates
push edx //save ptr to possible queen's position
cmp al, 0 //trivial acceptance (top row)
je NotThreatened //can't be threatened on top row
//Loop going up from our position (# loops == our row # [if zero indexed])
movzx ecx, al //loop once for each row above us
//LoopCheckNorth: check all rows directly above our own
LoopCheckNorth:
dec al //keep track of where we are at for callback
sub edx, ebx //move ptr one row up
cmp BYTE PTR [edx], 0 //is a queen here?
jne Collision //our queen is threatened
loop LoopCheckNorth
//End of LoopCheckNorth
mov edx, DWORD PTR [esp] //restore ptr to position (leave on stack)
mov eax, DWORD PTR [esp + 4] //restore row/column (leave on stack)
//Loop going up and to the left (# loops == minimum of row # and column #)
inc ebx //temporarily change the width; now,
//subtracting this will go up and to left
cmp ah, 0 //make sure we aren't on left edge
je SkipLoopCheckNW //on left edge, no need to check this
cmp ah, al //compare row # to column #
ja ColumnsGreater
movzx ecx, ah //rows greater; loops == column # (zero-indexed)
jmp LoopCheckNorthWest //start looping
ColumnsGreater:
movzx ecx, al //columns greater; loops == row # (zero-indexed)
//LoopCheckNorthWest: check all threats diagonally up and left of our position
LoopCheckNorthWest:
sub edx, ebx //move pointer up one and left one
sub ax, 0x0101 //rows--, columns-- (same time, save a tick)
cmp BYTE PTR [edx], 0 //is a queen here?
jne Collision //our queen is threatened
loop LoopCheckNorthWest
//End of LoopCheckNorthWest
SkipLoopCheckNW:
mov edx, DWORD PTR [esp] //restore ptr to position (leave on stack)
movzx eax, BYTE PTR [esp + 5] //restore column; (row in a sec)
sub ebx, 2 //now (edx -= ebx) will move up to right
//now we need to check diagonally up to the right; the number of squares
//(number of loops) will be the minimum of our distance from the right
//side of the board, and our distance from the top. min(width - column, row)
mov ecx, DWORD PTR [ebp] //ecx = width
dec ecx //now width and column are zero-indexed
sub ecx, eax //ecx = width - column (distance from right)
cmp ecx, 0 //check if square is on right side of board
je NotThreatened //no need to check if on right side
movzx eax, BYTE PTR [esp + 4] //eax = row
cmp ecx, eax //find min(width - column, row)
jb TravelToRightSide //distance to right side is less
mov ecx, eax //distance to top is less
mov eax, DWORD PTR [esp + 4] //restore board coordinates
jmp LoopCheckNorthEast //start checking diagonally up-right
TravelToRightSide: //distance to right side is less
//ecx already has the distance to the top of the board
mov eax, DWORD PTR [esp + 4] //restore the board coordinates
//LoopCheckNorthEast: check for threats diagonally up and to the right of our square
LoopCheckNorthEast:
sub edx, ebx //move pointer up one and right
inc ah //columns++
dec al //rows--
cmp BYTE PTR [edx], 0 //is a queen here?
jne Collision //our queen is threatened
loop LoopCheckNorthEast
//End of LoopCheckNorthEast
NotThreatened:
pop edx //restore pointer to position
mov BYTE PTR [edx], 1 //place the queen
//the saved eax (board coordinates) are at the top of the stack now
pushad //now, saved eax is at [esp + 32]
//callback->accepted(edi, MSG_ACCEPTED, pushed ah, pushed al, 0, 0)
sub esp, 8 //last 2 params unused (eax at [esp + 40])
movzx ebx, BYTE PTR [esp + 41] //use ebx to extend saved row to DWORD
push ebx //push row (eax at [esp + 44])
movzx ebx, BYTE PTR [esp + 44] //use ebx to extend saved column to DWORD
push ebx //push column
mov ebx, MSG_ACCEPTED //make sure we push a DWORD
push ebx //msg
push edi //pointer to the board
call [ebp - 4] //see stack diagram above
add esp, 24 //restore stack
popad
jmp IsThreatenedDone //made it this far with no collisions
Collision:
pop edx //restore pointer to position
mov BYTE PTR [edx], 1 //place the queen, remove it before return
//the saved eax (board coordinates) are at top of the stack
pushad //saved eax is at [esp + 32]
//callback->collision(edi, MSG_COLLISION, saved ah, saved al, ah, al)
movzx ebx, ah //use ebx to extend al to DWORD
push ebx //push collision row (eax at [esp + 36])
movzx ebx, al //use ebx to extend ah to DWORD
push ebx //push collision column (eax at [esp + 40])
movzx ebx, BYTE PTR [esp + 41] //use ebx to extend pushed al to DWORD
push ebx //push row (eax at [esp + 44])
movzx ebx, BYTE PTR [esp + 44] //use ebx to extend pushed ah to DWORD
push ebx //push column
mov ebx, MSG_COLLISION //make sure we push a DWORD
push ebx //msg
push edi //pointer to the board
call [ebp - 4] //see stack diagram above
add esp, 24 //restore stack
popad
mov BYTE PTR [edx], 0 //remove it, it was threatened
mov ch, 1 //set return value
IsThreatenedDone:
pop eax //restore square's coordinates
mov ebx, DWORD PTR [ebp] //restore width
//callback->abort_check(edi, MSG_ABORT_CHECK, 0, 0, 0, 0)
pushad
sub esp, 16 //4 unused params
mov ebx, MSG_ABORT_CHECK //make sure it is 32-bit
push ebx //msg
xor ebx, ebx //clear ebx
push ebx //param unused
call [ebp - 4] //see stack diagram above
add esp, 24 //restore stack
cmp eax, 0 //return non-zero: return to caller
popad
jnz ThrowAbort //like throwing an exception
ret //return back to caller
ThrowAbort: //Callback proc told us to abort
mov eax, -1 //return value
jmp FinishedASM //restore regs and exit
//as long as EBP hasn't changed,
//we can exit anytime we want
/*///////////////////////////////////////////////////////////////////////////////
//////////////////////////////End of IsThreatened//////////////////////////////*/
/*////////////////////////////Cleanup and Return/////////////////////////////////
///////////////////////////////////////////////////////////////////////////////*/
FinishedASM:
lea esp, [ebp + 4] //esp is now top of pushed regs
mov [esp + 28], eax //overwrite saved eax value
popad //leaves eax unchanged
popfd
}
//let "C" return from its own function with return value in EAX
}