mirror of
https://github.com/pkivolowitz/asm_book.git
synced 2026-06-21 01:56:47 +08:00
added walkies
This commit is contained in:
parent
cbb6f722d2
commit
2f52f006df
6 changed files with 376 additions and 0 deletions
155
projects/WALKIES/README.md
Normal file
155
projects/WALKIES/README.md
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
# Walkies
|
||||
|
||||
Your program will animate a single character back and forth across the
|
||||
terminal in such a way that it appears to walk. OK, tumble across the
|
||||
screen might be more correct but then titling this page *Tumbling*
|
||||
wouldn't have the same ring.
|
||||
|
||||

|
||||
|
||||
## Overview
|
||||
|
||||
Looking at these characters in sequence:
|
||||
|
||||
| Order | Character | Comment |
|
||||
| ----- | --------- | ------- |
|
||||
| 0 | \| | below delete key - this is called the pipe character |
|
||||
| 1 | / | slash |
|
||||
| 2 | _ | underscore |
|
||||
| 3 | \\ | bash |
|
||||
|
||||
Notice how the characters can be repeated to approximate a tumbling
|
||||
animation.
|
||||
|
||||
You will loop these characters in such a way that they will be drawn
|
||||
marching first to the right across the screen and then back to the left
|
||||
as in the animation above.
|
||||
|
||||
The width of the tumbling range will be 60 (the animation above shows a
|
||||
width of 40 but you are to use 60).
|
||||
|
||||
The program runs forever... terminate it with ^c (control-c).
|
||||
|
||||
## Use of Carriage Return To Cause Redrawing
|
||||
|
||||
After emitting a full line of characters, your cursor must be forced
|
||||
back to column 0. You can accomplish this by emitting `\r` as almost
|
||||
the last
|
||||
thing you print. This is the carriage return... think getting to the end
|
||||
of a line on an old time typewriter.
|
||||
|
||||

|
||||
|
||||
Kermit must be writing in assembly language because all the lines he's
|
||||
typing are very short.
|
||||
|
||||
## Do You Need Any Spaces To The Right Of The Character?
|
||||
|
||||
Asking for a friend.
|
||||
|
||||
## Forcing Output Without New Lines in C++
|
||||
|
||||
You're used to this:
|
||||
|
||||
```c++
|
||||
cout << "Foo" << endl;
|
||||
```
|
||||
|
||||
The `endl` is doing two things for you:
|
||||
|
||||
1. Of course, it's giving you a new line but it is also...
|
||||
2. Triggers the output to actually render on your console
|
||||
|
||||
Actual output via streams like `cout` and `cerr` only happens
|
||||
when new lines are emitted. This is called "buffering". Buffering is
|
||||
a powerful technique to increase efficiency when:
|
||||
|
||||
* the amount of output is a little at a time
|
||||
|
||||
* output is made frequently
|
||||
|
||||
* the cost of emitting the output is high
|
||||
|
||||
In C++, to force output that's been buffered up in an output stream,
|
||||
(without requiring a new line), do:
|
||||
|
||||
```c++
|
||||
cout.flush();
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```c++
|
||||
cout << blah << flush;
|
||||
```
|
||||
|
||||
The choice of the method name `flush()` is apropos in that you're
|
||||
"flushing" any buffered characters all the way to their ultimate end
|
||||
point.
|
||||
|
||||
Note that `cout` can be replaced with the name of any output stream.
|
||||
|
||||
## Forcing Output In Assembly Language
|
||||
|
||||
This program can use the low level `write()` which is *not* buffered
|
||||
to emit characters on-demand. `write()` looks like this:
|
||||
|
||||
```c
|
||||
ssize_t write(int fildes, const void *buf, size_t nbyte);
|
||||
```
|
||||
|
||||
where:
|
||||
|
||||
* `ssize_t` means a 64-bit integer.
|
||||
|
||||
* `fildes` is a file descriptor - a value of 1 means console out.
|
||||
|
||||
* `buf` is a pointer to the data to be printed.
|
||||
|
||||
* `nbyte` is the number of characters to print.
|
||||
|
||||
## Causing A Delay in C++
|
||||
|
||||
Since C++ 11, the standard library has provided a portable means of
|
||||
delaying execution of your program, a *pause* in other words. To use
|
||||
this method you need the following includes:
|
||||
|
||||
```c++
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
```
|
||||
|
||||
When it is time to delay, use the following:
|
||||
|
||||
```c++
|
||||
this_thread::sleep_for(chrono::milliseconds(MILLISECONDS_DELAY));
|
||||
```
|
||||
|
||||
## Causing A Delay in Assembly Language
|
||||
|
||||
Once again, we'll use lower level functions. In this case, we'll use
|
||||
`usleep()` where the `u` stands in for `mu`, the Greek letter
|
||||
indicating microseconds, millionths of a second.
|
||||
|
||||
`usleep()` looks like this:
|
||||
|
||||
```c
|
||||
int usleep(useconds_t microseconds);
|
||||
```
|
||||
|
||||
* `useconds_t` is a synonym for `int`.
|
||||
|
||||
## Barbara Woodhouse
|
||||
|
||||
[Barbara Woodhouse](https://en.wikipedia.org/wiki/Barbara_Woodhouse) was
|
||||
a pre-Internet phenom world renowned dog trainer. From her, "Walkies!"
|
||||
and "Sit tah!" entered the world's lexicon.
|
||||
|
||||
Here she is:
|
||||
|
||||

|
||||
|
||||
## Sample Implementation
|
||||
|
||||
A sample implementation can be found [here](./main.s). Try this
|
||||
yourself first before looking at our code.
|
||||
BIN
projects/WALKIES/bwoodhouse.png
Normal file
BIN
projects/WALKIES/bwoodhouse.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2 MiB |
BIN
projects/WALKIES/ktpng.gif
Normal file
BIN
projects/WALKIES/ktpng.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1 MiB |
138
projects/WALKIES/main.s
Normal file
138
projects/WALKIES/main.s
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
.global main
|
||||
.align 2
|
||||
|
||||
/* Walkies - a silly animation using four characters on the Linux
|
||||
console. This program demonstrates low level IO (write()) and
|
||||
is written in the form of an infinite loop (i.e. ^C or kill will
|
||||
be required to halt the program).
|
||||
|
||||
The approach used here avoids use of two for loops, one for moving
|
||||
to the right and one for moving to the left. Rather, we notice that
|
||||
the only difference between the two for loops would be that one
|
||||
increments and the other decrements. Otherwise, they'd be the same.
|
||||
|
||||
Instead of the two for loops we'll use a toggle called delta. It
|
||||
will alternate between 1 and -1 when counter reaches MAX_COLUMN (on
|
||||
the way left to right / transition from 1 to -1) or 0 on the way
|
||||
back (-1 to 1)
|
||||
*/
|
||||
|
||||
.equ MAX_COLUMN, 60
|
||||
.equ NUM_CHARS, 4
|
||||
|
||||
counter .req w20
|
||||
delta .req w21
|
||||
|
||||
.section .rodata
|
||||
|
||||
CHARS: .asciz "|/_\\"
|
||||
TRM: .asciz " \r" // The space is important.
|
||||
|
||||
.text
|
||||
|
||||
main: stp x29, x30, [sp, -16]!
|
||||
stp counter, delta, [sp, -16]!
|
||||
|
||||
mov counter, wzr
|
||||
mov delta, 1
|
||||
mov w0, wzr
|
||||
|
||||
0: bl Pad
|
||||
bl Emit
|
||||
bl Terminator
|
||||
add counter, counter, delta
|
||||
mov w0, MAX_COLUMN
|
||||
cmp w0, counter
|
||||
bne 1f
|
||||
neg delta, delta
|
||||
b 2f
|
||||
1: cbnz counter, 2f
|
||||
neg delta, delta
|
||||
|
||||
2: ldr x0, =usec
|
||||
ldr w0, [x0]
|
||||
bl usleep
|
||||
b 0b
|
||||
|
||||
// Because of the infinite loop, this code will not
|
||||
// be reached but is here out of habit and good practices.
|
||||
ldp counter, delta, [sp], 16
|
||||
ldp x29, x30, [sp], 16
|
||||
mov w0, wzr
|
||||
ret
|
||||
|
||||
/* Terminator - outputs "\r " - notice the space after
|
||||
the carriage return.
|
||||
*/
|
||||
|
||||
Terminator:
|
||||
stp x29, x30, [sp, -16]!
|
||||
mov w0, 1 // 1 is stdout
|
||||
ldr x1, =TRM // Pointer to string
|
||||
mov w2, 2 // 2 characters being printed
|
||||
bl write
|
||||
ldp x29, x30, [sp], 16
|
||||
ret
|
||||
|
||||
/* Emit - puts out the symbol derived from counter.
|
||||
*/
|
||||
|
||||
Emit: stp x29, x30, [sp, -16]!
|
||||
mov w0, counter
|
||||
mov w1, NUM_CHARS
|
||||
bl mod
|
||||
ldr x1, =CHARS
|
||||
add x1, x1, x0
|
||||
mov w0, 1
|
||||
mov w2, 1
|
||||
bl write
|
||||
ldp x29, x30, [sp], 16
|
||||
ret
|
||||
|
||||
|
||||
/* Pad - prints w0 spaces to the console. It does this using
|
||||
a for loop printing one space at a time. A reasonable optimization
|
||||
would be to create a large array of spaces and use the length
|
||||
parameter to write() to replace the loop.
|
||||
*/
|
||||
|
||||
Pad: stp x29, x30, [sp, -16]!
|
||||
str delta, [sp, -16]! // REUSING w21 !!!
|
||||
mov w21, wzr
|
||||
|
||||
1: ldr x1, =buff
|
||||
mov w2, ' '
|
||||
strb w2, [x1] // ' ' is pointed to by x1
|
||||
mov w0, 1 // 1 is stdout
|
||||
mov w2, 1 // emitting 1 byte
|
||||
bl write
|
||||
add w21, w21, 1
|
||||
cmp w21, counter
|
||||
blt 1b
|
||||
|
||||
ldr delta, [sp], 16
|
||||
ldp x29, x30, [sp], 16
|
||||
ret
|
||||
|
||||
/* mod(a, b) - implements a % b - AARCH64 lacks a mod instruction.
|
||||
A strange place to economize, but there you have it.
|
||||
|
||||
a comes to us in x0
|
||||
b comes to us in x1
|
||||
method:
|
||||
* integer divide a by b
|
||||
for example - 5 % 3 would need 5 // 3 yielding 1
|
||||
* multiply result by b
|
||||
1 * 3 is 3
|
||||
* subtract result from a
|
||||
5 - 3 is 2 and that's our return value.
|
||||
*/
|
||||
|
||||
mod: sdiv x2, x0, x1 // x2 gets a // b
|
||||
msub x0, x2, x1, x0 // x0 gets a - a // b * b
|
||||
ret
|
||||
|
||||
.data
|
||||
buff: .space 4
|
||||
usec: .int 75000
|
||||
|
||||
83
projects/WALKIES/single_star.md
Normal file
83
projects/WALKIES/single_star.md
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# Single Star
|
||||
|
||||
In this program, you'll animate a single asterisk causing it to bounce back and forth from column zero to some column (currently defined as 40).
|
||||
|
||||
You'll learn:
|
||||
|
||||
* how to force output to happen even before a new line character
|
||||
* how to overwrite a line on the console (rather than advancing to a new line)
|
||||
* how to delay your program's execution for a short while (for timing purposes)
|
||||
* usage of `cinttypes` which makes the specific flavor of an integer unambiguous
|
||||
|
||||
Write a program that animates a single asterisk like this:
|
||||
|
||||

|
||||
|
||||
Don't worry about the program terminating - just kill it when you're done looking.
|
||||
|
||||
No data structure is needed for this program as everything you need to do is based on a counter.
|
||||
|
||||
You'll need these include files:
|
||||
|
||||
```c++
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <cinttypes>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
```
|
||||
|
||||
I used the features of `iomanip` to space out the animating asterisk.
|
||||
|
||||
`cinttypes` contains aliases for integer types that are unambiguous. That is, when you say `int`, how big is it? Depends. Using `cinttypes` you can declare:
|
||||
|
||||
| type | meaning | without cinttypes |
|
||||
| ---- | ------- | ----------------- |
|
||||
| uint8_t | unsigned 8 bit integer | unsigned char |
|
||||
| uint16_t | unsigned 16 bit integer | unsigned short |
|
||||
| uint32_t | unsigned 32 bit integer | unsigned int |
|
||||
| uint64_t | unsigned 64 bit integer | unsigned long |
|
||||
| int8_t | signed 8 bit integer | char |
|
||||
| int16_t | signed 16 bit integer | short |
|
||||
| int32_t | signed 32 bit integer | int |
|
||||
| int64_t | signed 64 bit integer | long |
|
||||
|
||||
`chrono` and `thread` are used together to implement delays in your program. Without pausing your program, it will update the screen so quickly, you won't be able to enjoy the animation.
|
||||
|
||||
For example:
|
||||
|
||||
```c++
|
||||
this_thread::sleep_for(chrono::milliseconds(16));
|
||||
```
|
||||
|
||||
will cause your program to sleep for at least 16 milliseconds but maybe a tad more. The value 16 milliseconds is chosen as it is 1/60th of a second.
|
||||
|
||||
## Output without new lines
|
||||
|
||||
You're used to this:
|
||||
|
||||
```c++
|
||||
cout << "Foo" << endl;
|
||||
```
|
||||
|
||||
The `endl` is doing two things for you:
|
||||
|
||||
1. Of course, it's giving you a new line but it is also
|
||||
2. Triggering the output to actually render on your console
|
||||
|
||||
Console output is buffered for efficiency. Actual output only happens when new lines are emitted. In this program, we're not using new lines at all. Instead, after text is output, we'll emit only a carriage return ('\r').
|
||||
|
||||
To force output (without a new line), do:
|
||||
|
||||
```c++
|
||||
cout.flush();
|
||||
```
|
||||
|
||||
## Source code
|
||||
|
||||
DO NOT LOOK AT [THIS](./single_star.cpp) UNTIL YOU HAVE TRIED TO WRITE THE CODE YOURSELF! With that said, don't feel bad about taking a peek and reading the comments.
|
||||
|
||||
## Related projects
|
||||
|
||||
* [Cylon Effect](./cylon.md)
|
||||
* [Walkies](./walkies.md)
|
||||
BIN
projects/WALKIES/walkies.gif
Normal file
BIN
projects/WALKIES/walkies.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 148 KiB |
Loading…
Reference in a new issue