Page 1 of 1

## NASM/Windows - Let's create a pyramid of characters!

### #1 GunnerInc

• "Hurry up and wait"

Reputation: 902
• Posts: 2,349
• Joined: 28-March 11

Posted 01 December 2012 - 04:21 PM

You will see this question from time to time: "How do I create a pyramid/diamond out of (some character here) in (your language here)?" Do you wonder why this task is given? It teaches you how to use loops. A loop is a widely used way to repeat a task.

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

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
cmp     _CurLine, eax
jng     .NextLine

push    _Pyramid
push    NULL
push    dword [hHeap]
call    HeapFree

;###### eplilogue ######################
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

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]

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
cmp     _CurLine, eax
jng     .NextLine

push    _Pyramid
push    NULL
push    dword [hHeap]
call    HeapFree

;###### eplilogue ######################
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
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
mov     _CharsToPrint, eax

inc     esi
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
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 length
• The 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
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
stdin           resd 1
stdout          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    Prompt_Len                      ;
push    szPrompt                        ;
push    dword [stdout]                  ;
call    WriteFile                       ;

push    NULL                            ;
push    MAX_CHARS_TO_READ               ; 2 more than expected chars, CRLF tacked on
push    buffer                          ;
push    dword [stdin]                   ;

sub     eax, 2                          ; subtract 2 from total read
test    eax, eax                        ; is it zero, if so
jz      .ShowPrompt                     ; go back and show prompt

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     _CharStartPos, eax              ; first char is printed at NumRead

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                        ;
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)

Is This A Good Question/Topic? 0

Page 1 of 1

 .related ul { list-style-type: circle; font-size: 12px; font-weight: bold; } .related li { margin-bottom: 5px; background-position: left 7px !important; margin-left: -35px; } .related h2 { font-size: 18px; font-weight: bold; } .related a { color: blue; }