; Minesweeper Kata in Assembly ; ---------------------------- ; Copyright (c) 2015, Peter Kofler, licensed under BSD License. bits 32 ; 32 bit architecture, implied by NASM -fwin32 register_size equ 4 ; external functions in system libraries extern _ExitProcess@4 ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms682658%28v=vs.85%29.aspx extern _GetStdHandle@4 ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms683231%28v=vs.85%29.aspx extern _WriteFile@20 ; https://msdn.microsoft.com/en-us/library/windows/desktop/aa365747%28v=vs.85%29.aspx extern _ReadFile@20 ; https://msdn.microsoft.com/en-us/library/windows/desktop/aa365467%28v=vs.85%29.aspx %define NULL dword 0 section .bss max_dimension equ 4 last_field_in_grid equ max_dimension * max_dimension - 1 ; TODO maybe common, see 5.11.7 COMMON: Defining Common Data Areas grid: resb max_dimension * max_dimension width: resd 1 height: resd 1 section .text ; Save EBP, ESI, EDI, DS and SS if they are clobbered. ; Create a stack frame (if stack frames are used). This is done by setting: ; 1. EBP = ESP ; 2. ESP = ESP - framesize ; The stack frame must contain space for local variables. ; Save the direction flag (EFlags.DF), if it is altered %macro enter_method 1 ; cdecl calling convention push ebp ; Save old stack pointer mov ebp, esp ; Create a new stack frame sub esp, %1 * register_size ; Allocate memory for local variabels [ebp-4] %endmacro ; Restore all saved registers and the direction flag (if it was saved) ; Pop the stack frame by setting ESP = EBP ; Return to the caller by using the ret instruction (ret pops the return address, and jumps to it) %macro exit_method 0 ; mov esp, ebp ; Restore stack pointer ; pop ebp ; Restore old stack frame leave %endmacro ; -------------------------------------------------------------------------------- ; get handle for stdout into eax _getStdOutFileHandle: ; HANDLE GetStdHandle(_In_ DWORD nStdHandle) push STD_OUTPUT_HANDLE call _GetStdHandle@4 ret STD_OUTPUT_HANDLE equ -11 ; -------------------------------------------------------------------------------- ; print the grid to file, "void _printGrid(HANDLE fileHandle)" _printGrid: ; parameters, starting memory[ebp + 8] fileWriteHandle equ 4 + 1 * register_size ; local variables, starting memory[ebp - 4] numberOfBytesWritten equ -1 * register_size enter_method 1 ; stack frame push esi push edi .loop_all_lines: ; for (edi = height; edi > 0; edi--) mov esi, 0 ; address of current line mov edi, [height] ; loop counter .loop: lea edx, [ebp + numberOfBytesWritten] ; lpNumberOfBytesWritten lea eax, [grid + esi] ; addr of output (lpBuffer) mov ebx, [ebp + fileWriteHandle] ; hstdOut ; write the next line ; BOOL WriteFile(fileHandle, message, length(message), &bytes, 0); push NULL ; _Inout_opt_ LPOVERLAPPED lpOverlapped push edx ; _Out_opt_ LPDWORD lpNumberOfBytesWritten push dword [width] ; _In_ DWORD nNumberOfBytesToWrite push eax ; _In_ LPCVOID lpBuffer push ebx ; _In_ HANDLE hFile call _WriteFile@20 lea edx, [ebp + numberOfBytesWritten] ; lpNumberOfBytesWritten mov ebx, [ebp + fileWriteHandle] ; hstdOut ; write a newline push NULL push edx push len_cr push cr push ebx ; hstdOut call _WriteFile@20 .next: add esi, max_dimension ; go down next line sub edi, 1 ; edi-- jnz .loop ; edi > 0 pop edi ; drop stack frame pop esi exit_method ret cr: db 13, 10 ; Windows \n\r len_cr equ $-cr ; -------------------------------------------------------------------------------- _exit: ; void ExitProcess(_In_ UINT uExitCode) push 0 call _ExitProcess@4 ; never here hlt ; -------------------------------------------------------------------------------- ; determine hints for bombs in the grid "void _solveGrid()" _solveGrid: push esi ; stack frame push edi .loop_all_fields_backwards: ; for (edi = last_field_in_grid; edi >= 0; edi--) mov edi, last_field_in_grid .loopFields: cmp [grid + edi], byte is_bomb ; is it a bomb? (or modified bomb field) jb .nextField ; it is bomb, increment all neighbouring hints .loop_all_neighbours_backwards: ; for (ecx = 7; ecx >= 0; ecx--) mov ecx, 7 ; 8 neighbour coordinates to test .loopNeighbours: mov esi, edi ; add to current bomb's position... add esi, dword [around + ecx * register_size] cmp esi, last_field_in_grid ; is the neighbour position valid? ja .nextNeighbour ; neighbour position is outside add [grid + esi], byte 1 ; else add bomb count for neighbour .nextNeighbour: sub ecx, 1 ; ecx-- jnc .loopNeighbours ; ecx >= 0 .nextField: sub edi, 1 ; edi-- jnc .loopFields ; edi >= 0 pop edi ; drop stack frame pop esi ret is_bomb equ 'x' around: dd - max_dimension - 1 dd - max_dimension dd - max_dimension + 1 dd - 1 dd 1 dd max_dimension - 1 dd max_dimension dd max_dimension + 1 ; -------------------------------------------------------------------------------- ; format the grid (e.g. replace 0 with blank) _formatGrid: push esi ; stack frame push edi .loop_all_fields_backwards: ; for (edi = last_field_in_grid; edi >= 0; edi--) mov edi, last_field_in_grid .loopFields: cmp [grid + edi], byte is_bomb ; is it a bomb? jnb .formatBomb ; bombs are increased as well, so we check for >= cmp [grid + edi], byte is_empty ; is it empty? je .formatEmpty .formatHint: ; it is a hint, need to make it a number add [grid + edi], byte emptyToHint jmp .nextField .formatEmpty: ; it is empty, need to make blank mov [grid + edi], byte blank jmp .nextField .formatBomb: ; bomb looks different mov [grid + edi], byte bomb .nextField: sub edi, 1 ; edi-- jnc .loopFields ; edi >= 0 pop edi ; drop stack frame pop esi ret is_empty equ '-' emptyToHint equ '0' - is_empty blank equ ' ' bomb equ 'B' ; -------------------------------------------------------------------------------- ; get stdin handle _getStdInFileHandle: ; HANDLE GetStdHandle(_In_ DWORD nStdHandle) push STD_INPUT_HANDLE call _GetStdHandle@4 ret STD_INPUT_HANDLE equ -10 ; -------------------------------------------------------------------------------- ; read decimal digits from text file "int _readDigit(stdInHandle)" ; and consume single whitespace/byte afterwards _readDigit: ; parameters fileReadHandle equ 4 + 1 * register_size ; local variables numberOfBytesRead equ -1 * register_size byteBuffer equ -2 * register_size number equ -3 * register_size enter_method 3 ; stack frame mov [ebp + number], dword 0 .read_next_character: lea edx, [ebp + numberOfBytesRead] ; lpNumberOfBytesRead lea eax, [ebp + byteBuffer] ; lpBuffer mov ebx, [ebp + fileReadHandle] ; hstdIn ; read a single character ; BOOL ReadFile(fileHandle, buffer, length, &bytes, 0); push NULL ; _Inout_opt_ LPOVERLAPPED lpOverlapped push edx ; _Out_opt_ LPDWORD lpNumberOfBytesRead push dword 1 ; _In_ DWORD nNumberOfBytesToRead push eax ; _Out_ LPVOID lpBuffer push ebx ; _In_ HANDLE hFile call _ReadFile@20 mov eax, [ebp + byteBuffer] and eax, 0xff cmp eax, '0' jb .finished sub eax, '0' ; multiply by 10 and add new number mov ebx, [ebp + number] shl ebx, 1 ; * 2 mov ecx, ebx shl ecx, 2 ; * 8 add ebx, ecx ; = * 10 add ebx, eax mov [ebp + number], ebx jmp .read_next_character .finished: mov eax, [ebp + number] exit_method ; drop stack frame ret ; -------------------------------------------------------------------------------- ; read the grid from file "void _readGrid(stdInHandle)" _readGrid: enter_method 2 ; stack frame push esi push edi .loop_all_lines: ; for (edi = height; edi > 0; edi--) mov esi, 0 ; address of current line mov edi, [height] ; loop counter .loop: lea edx, [ebp + numberOfBytesRead] ; lpNumberOfBytesRead lea eax, [grid + esi] ; addr of output (lpBuffer) mov ebx, [ebp + fileReadHandle] ; hstdIn ; read a line ; BOOL ReadFile(fileHandle, buffer, length, &bytes, 0); push NULL ; _Inout_opt_ LPOVERLAPPED lpOverlapped push edx ; _Out_opt_ LPDWORD lpNumberOfBytesRead push dword [width] ; _In_ DWORD nNumberOfBytesToRead push eax ; _Out_ LPVOID lpBuffer push ebx ; _In_ HANDLE hFile call _ReadFile@20 lea edx, [ebp + numberOfBytesRead] ; lpNumberOfBytesRead lea eax, [ebp + byteBuffer] ; lpBuffer mov ebx, [ebp + fileReadHandle] ; hstdIn ; read a single character push NULL push edx push dword 1 push eax push ebx call _ReadFile@20 .next: add esi, max_dimension ; go down next line sub edi, 1 ; edi-- jnz .loop ; edi > 0 pop edi ; drop stack frame pop esi exit_method ret ; -------------------------------------------------------------------------------- global _main _main: ; steps (not TDD but from feedback) ; * print hello world ; * print first line of maximum grid ; * print all lines of maximum grid ; * print customized width and height of grid ; * convert read grid to hint numbers ; * sum around middle bombs (problem, need debugger ;-) ; * sum around corner bombs (problem again, need more test data) ; * format grid to expected output ; * (massive rename, format, document) ; * read width from stdin ; * read height from stdin (ignore whitespace after) ; * read first line from stdin ; * read all lines from stdin call _getStdInFileHandle push eax push eax push eax ; hstdIn call _readDigit add esp, register_size mov [width], eax ; return value first number ; 2nd eax still pushed call _readDigit add esp, register_size mov [height], eax ; return value ; 3rd eax still pushed call _readGrid add esp, register_size call _solveGrid call _formatGrid call _getStdOutFileHandle push eax ; hstdOut call _printGrid add esp, register_size jmp _exit