A sample of a Pyramid of Characters:
A
ppppp
rrrrrrr
yyyyyyyyy
aaaaaaaaaaa
mmmmmmmmmmmmm
iiiiiiiiiiiiiii
ddddddddddddddddd
ooooooooooooooooooooo
fffffffffffffffffffffff
ccccccccccccccccccccccccccc
hhhhhhhhhhhhhhhhhhhhhhhhhhhhh
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
ccccccccccccccccccccccccccccccccccccc
ttttttttttttttttttttttttttttttttttttttt
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
sssssssssssssssssssssssssssssssssssssssssssss
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
So how would you code this? Open up a text editor/IDE that shows line and column numbers, type in a sample pyramid and study it. What do you notice? If I type in a pyramid consisting of "abcde":
a bbb ccccc ddddddd eeeeeeeee
Here is what I notice:
- The length of the entered string is 5
- The start pos of the first character equals the string length and decreases on each line.
- The amount of spaces on the first line is (string length - 1) and decreases on each line.
- The amount each character repeats on each line equals (current line number + previous line number)
- The amount of lines to print is the same as the entered strings length
- The length of the last line is (entered string length * 2 - 1)
Now, let's enter a string and create a pyramid. WAIT, not so fast! Start slow, break it down! Get the entered string into a buffer and loop through each character and print each one on a separate line.
ShowPyramid:
%define _CharStartPos dword [ebp - 4]
%define _CharsToPrint dword [ebp - 8]
%define _CurLine dword [ebp - 12]
%define _Pyramid dword [ebp - 16]
%define _Dummy [ebp - 20]
;###### prologue #######################
push ebp
mov ebp, esp
sub esp, 4 * 5
mov eax, dword [lpNumRead]
add eax, eax
push eax
push HEAP_ZERO_MEMORY
push dword [hHeap]
call HeapAlloc
mov _Pyramid, eax
;###### initialize vars to 1
mov _CurLine, 1
mov esi, buffer
.NextLine:
mov edi, _Pyramid
mov al, byte [esi]
mov byte [edi], al
mov byte [edi + 1], 10
lea eax, _Dummy
push NULL
push eax
push 2
push _Pyramid
push dword [stdout]
call WriteFile
inc _CurLine
inc esi
mov eax, dword [lpNumRead]
cmp _CurLine, eax
jng .NextLine
push _Pyramid
push NULL
push dword [hHeap]
call HeapFree
;###### eplilogue ######################
add esp, 4 * 5
mov esp, ebp
pop ebp
ret
This will get the size of entered string that was returned from ReadFile - lpNumRead and double the size to hold the last line, create a buffer to hold it, loop through entered string, move one character into the _Pyramid buffer, tack on a Linefeed char (10), print it, check to see if we are on the last line, if not loop and do next char.
ShowPyramid:
%define _CharStartPos dword [ebp - 4]
%define _CharsToPrint dword [ebp - 8]
%define _CurLine dword [ebp - 12]
%define _Pyramid dword [ebp - 16]
%define _Dummy [ebp - 20]
;###### prologue #######################
push ebp
mov ebp, esp
sub esp, 4 * 5
mov eax, dword [lpNumRead]
add eax, eax
add eax, 2
push eax
push HEAP_ZERO_MEMORY
push dword [hHeap]
call HeapAlloc
mov _Pyramid, eax
;###### initialize vars to 1
mov _CurLine, 1
We will use 5 variables on the stack. I like to use named variables, which make code a bit self documenting, so we will tell NASM that anywhere it sees the word after %define, to replace it with with dword [ebp + #] . Anywhere it sees _CharStartPos, it will replace it with dword [ebp - 4]
Next we use setup the stack and make room for our 5 variables with sub esp, 4 * 5.
One of the things I noticed was:
Quote
The length of the last line is (entered string length * 2 - 1)
So, we will take the entered string length and double it which will leave room for a linefeed char, and save the returned pointer to our local var - _Pyramid
mov eax, dword [lpNumRead]
add eax, eax
add eax, 2
push eax
push HEAP_ZERO_MEMORY
push dword [hHeap]
call HeapAlloc
mov _Pyramid, eax
We are not using (entered string length * 2 - 1), but instead (entered string length * 2) We don't need the buffer to be (entered string length * 2) just to print 1 character on each line, but it is done and out of the way.
We need loop counter to know when to stop printing, so we will use _CurLine and initialize it to 1.
mov esi, buffer
.NextLine:
mov edi, _Pyramid
mov al, byte [esi]
mov byte [edi], al
mov byte [edi + 1], 10
lea eax, _Dummy
push NULL
push eax
push 2
push _Pyramid
push dword [stdout]
call WriteFile
inc _CurLine
inc esi
mov eax, dword [lpNumRead]
cmp _CurLine, eax
jng .NextLine
push _Pyramid
push NULL
push dword [hHeap]
call HeapFree
;###### eplilogue ######################
add esp, 4 * 5
mov esp, ebp
pop ebp
ret
We setup esi to contain the pointer to our entered text buffer
edi will contain the pointer to the buffer that holds the line to print.
Get a byte from our input buffer, copy it to our line buffer, and tack on a linefeed char
.NextLine:
mov edi, _Pyramid
mov al, byte [esi]
mov byte [edi], al
mov byte [edi + 1], 10
Print the character to console:
lea eax, _Dummy
push NULL
push eax
push 2
push _Pyramid
push dword [stdout]
call WriteFile
inc _CurLine
inc esi
mov eax, dword [lpNumRead]
cmp _CurLine, eax
jng .NextLine
increase our entered text buffer pointer with inc esi. Say what?!?! Ok, pointers go like this:
We entered 5 characters - abcde and stored it in our buffer called buffer. When we run our program, buffer starts at address 123456 and ends at address 123516 (addresses just for example), we reserved 60 bytes for this buffer in our .bss section with buffer resb MAX_CHARS_TO_READ where MAX_CHARS_TO_READ is defined as %define MAX_CHARS_TO_READ 60 hence a 60 byte address spread.
When we first enter the loop, esi contains the address 123456 which contains the letter a. Then we increase the pointer by one with inc esi so now esi contains the address 123457 which the letter b is at.
esi 123456 "a" inc esi esi 123457 "b" inc esi esi 123458 "c" inc esi esi 123459 "d" inc esi esi 123460 "e"
increase our current line counter inc _CurLine and check to see if it is more than the entered string length, if not loop back and do next character. Which takes care of observation:
Quote
The amount of lines to print is the same as the entered strings length

Ok, so that is the loop to go through each entered character and print each one on a line. Next we expand it to repeat each character a certain amount of times
Quote
The amount each character repeats on each line equals (current line number + previous line number)
;###### initialize vars to 1
mov _CurLine, 1
mov _CharsToPrint, 1
mov esi, buffer
.NextLine:
mov edi, _Pyramid
mov ecx, _CharsToPrint
mov al, byte [esi]
.FillLine:
mov byte [edi], al
inc edi
dec ecx
jnz .FillLine
mov edi, _Pyramid
call StrLen
lea eax, _Dummy
push NULL
push eax
push ecx
push _Pyramid
push dword [stdout]
call WriteFile
mov eax, _CurLine
inc _CurLine
add eax, _CurLine
mov _CharsToPrint, eax
inc esi
mov eax, dword [lpNumRead]
cmp _CurLine, eax
jng .NextLine
We will use the variable _CharsToPrint to keep track of how many characters to print. Somethings were added/changed/removed from this loop. We also added another loop called FillLine
.FillLine:
mov byte [edi], al
inc edi
dec ecx
jnz .FillLine
On the first loop of NextLine, _CharsToPrint is 1, so this loop will just add 1 character to our _Pyramid buffer. This will take the character we got at the current pointer index in esi, copy it into the current pointer index in edi and decrease ecx and loop until it is zero. Next we get the length of the string to print (The StrLen function also tacks on a linefeed for us) and prints out each repeated character on a line.
So how do we figure out how many times to repeat the character on the line?
Quote
The amount each character repeats on each line equals (current line number + previous line number)
mov eax, _CurLine
inc _CurLine
add eax, _CurLine
mov _CharsToPrint, eax
We move the _CurLine count into eax, advance the _CurLine (Which makes the variable the "Next line", add the "Next Line" to eax to give us the total amount of chars to print on the next line.

Now that we wrote the code to deal with 4 of the 6 observations, now we continue on to add the correct amount of spaces to each line.
The length of the entered string is 5- The start pos of the first character equals the string length and decreases on each line.
- The amount of spaces on the first line is (string length - 1) and decreases on each line.
The amount each character repeats on each line equals (current line number + previous line number)The amount of lines to print is the same as the entered strings lengthThe length of the last line is (entered string length * 2 - 1)
We need to add 2 labels 1 more loop, and 11 instructions to accomplish #2 and #3.
Towards the top of ShowPyramid, right after the prologue, we save the entered string length to _CharStartPos
mov eax, dword [lpNumRead]
mov _CharStartPos, eax
Now that we have that, we can print the correct number of spaces in front of the repeated characters.
The beginning of the NextLine loop gets changed to:
.NextLine:
mov edi, _Pyramid
mov edx, _CharStartPos
dec edx
test edx, edx
jz .SpaceDone
.FillSpaces:
mov byte [edi], 32
inc edi
dec edx
jnz .FillSpaces
.SpaceDone:
mov ecx, _CharsToPrint
We take the value in of _CharStartPos, move it into edx, decrease the value in edx by one, and check to see if it is zero. If it is zero, we skip the FillSpaces loop. Now edx contains the amount of spaces to add, we will use this as our counter in the FillSpaces loop. We move the space character (32) into the pointer index in edi increase our pointer and keep repeating until edx == 0. Once we are done adding spaces, our pointer will be incremented to the correct spot to add our character(s).
a bbb ccccc ddddddd eeeeeeeee
First line, the letter "a". Entered string length == 5, start pos for "a" is 5, total spaces to print before "a" is 4.
Before we check to see if the NextLine loop should continue, we decrease the value in _CharStartPos
dec _CharStartPos
inc esi
mov eax, dword [lpNumRead]
cmp _CurLine, eax
jng .NextLine

Complete code:
%include "externs.inc"
%define MAX_CHARS_TO_READ 60 ; needs to be total chars wanted + 2
%define STD_INPUT_HANDLE -10
%define STD_OUTPUT_HANDLE -11
%define NULL 0
%define HEAP_ZERO_MEMORY 0x00000008
SECTION .data
szPrompt db "Ready to make an alphabet pyramid?", 13, 10
db "Enter some letters from the alphabet (58 max): ", 0
Prompt_Len equ $-szPrompt
szExitPrompt db "Press any key to exit", 0
szfmt db "%s%s", 0
szfmt2 db "%d%s", 0
szCRLF db 13,10,0
SECTION .bss
buffer resb MAX_CHARS_TO_READ
stdin resd 1
stdout resd 1
lpNumRead resd 1
hHeap resd 1
SECTION .text
main:
;~ Some initialization
push STD_INPUT_HANDLE ;
call GetStdHandle ; get handle of console input
mov [stdin], eax ; save handle
push STD_OUTPUT_HANDLE ;
call GetStdHandle ; get handle of console output
mov [stdout], eax ; save it
call GetProcessHeap ;
mov [hHeap], eax ;
.ShowPrompt:
push NULL ;
push lpNumRead ;
push Prompt_Len ;
push szPrompt ;
push dword [stdout] ;
call WriteFile ;
push NULL ;
push lpNumRead ;
push MAX_CHARS_TO_READ ; 2 more than expected chars, CRLF tacked on
push buffer ;
push dword [stdin] ;
call ReadFile ;
mov eax, dword [lpNumRead] ; chars read
sub eax, 2 ; subtract 2 from total read
test eax, eax ; is it zero, if so
jz .ShowPrompt ; go back and show prompt
mov dword [lpNumRead], eax ; save total chars read - CRLF
call ShowPyramid ; display pyramid
call __getch ; To hell with you, it is a Windows app, no cross OS :-p
ret
;~ #########################################
StrLen:
xor ecx, ecx
not ecx
xor eax, eax
cld
repne scasb
not ecx ; length of string in ecx
mov byte [edi - 1], 10
ret
;~ #########################################
ShowPyramid:
%define _CharStartPos dword [ebp - 4]
%define _CharsToPrint dword [ebp - 8]
%define _CurLine dword [ebp - 12]
%define _Pyramid dword [ebp - 16]
%define _Dummy [ebp - 20]
;###### prologue #######################;
push ebp
mov ebp, esp
sub esp, 4 * 5 ; make room on stack for 6 "local" vars
mov eax, dword [lpNumRead] ;
mov _CharStartPos, eax ; first char is printed at NumRead
add eax, eax ; double chars read total
push eax ;
push HEAP_ZERO_MEMORY ;
push dword [hHeap] ;
call HeapAlloc ; create buffer for longest line
mov _Pyramid, eax ; save pointer
;###### initialize vars to 1 ;
mov _CurLine, 1 ;
mov _CharsToPrint, 1 ;
mov esi, buffer ; esi == pointer to inputed chars
.NextLine:
mov edi, _Pyramid ; edi == pointer to line to print;;;;;
mov edx, _CharStartPos ; check to see if starting pos is 0
dec edx ; if it is, we don't need to add any spaces
test edx, edx ;
jz .SpaceDone ;
; num spaces == CharStartPos - 1
.FillSpaces:
mov byte [edi], 32 ; add space char to our buffer
inc edi ; increase our buffer pointer index by 1
dec edx ; decrease our spaces to print counter
jnz .FillSpaces ; until 0
.SpaceDone:
mov ecx, _CharsToPrint ; ecx == how many times to repeat char
mov al, byte [esi] ; get char to repeat
.FillLine:
mov byte [edi], al ; add char to our buffer at current pointer index
inc edi ; increase pointer index
dec ecx ; repeat while ecx != 0
jnz .FillLine ;
mov edi, _Pyramid ;
call StrLen ; get length of current line
lea eax, _Dummy ;
push NULL ;
push eax ;
push ecx ;
push _Pyramid ;
push dword [stdout] ;
call WriteFile ; Write current line to console
mov eax, _CurLine ;
inc _CurLine ;
add eax, _CurLine ;
mov _CharsToPrint, eax ; Chars to print == Current Line number + Prev Line Number
dec _CharStartPos ; Char start pos == Char start pos - 1
inc esi ; increase pointer index of chars to print
mov eax, dword [lpNumRead] ; check to see if we are at last line
cmp _CurLine, eax ; if Cur line < total chars to print
jng .NextLine ; then do next line
push _Pyramid ;
push NULL ;
push dword [hHeap] ;
call HeapFree ; free our buffer
;###### eplilogue ######################;
add esp, 4 * 5 ;
mov esp, ebp ;
pop ebp ;
ret ; no parameters passed, just need ret

See, not too bad once you break it down and write the loop in parts.
Here is a challenge for you: Modify the code to create a diamond instead of a pyramid. Can it be done? Can you do it? Give it a try!
Attached File(s)
-
Pyramid.zip (3.96K)
Number of downloads: 69







MultiQuote


|