MELT.COM
MELT.COM was written by an unknown author in the 1980s. Originally 49 bytes in size, it performs the following cute effect:
(The video is simulated and shows how MELT performs on the old hardware it was written for.) This effect is achieved by increasing or decreasing each onscreen character's value until it reaches #32, the space character. The original source is lost to history, but here's a quick commented disassembly:
org 100h
mov ax, 0B800h
mov es, ax ; es now points to screen segment
doScreen:
mov cx, 2000 ; Going to loop over all 2000 characters
; (80 * 25 = 2000)
xor bx, bx ; bx = 0
; bx is also our "num of altered chars" counter
mov di, bx ; es:di now points at the screen (b800:0000)
alterChars:
mov ah, es:[di] ; Retreive onscreen character
cmp ah, 32 ; comp to a space character (#32)
jz short nextChar ; If already a space, do nothing
jl short upToSpace ; If lower than a space, increase upward
dec ah ; If higher than a space, decrease downward
mov es:[di], ah ; Store altered character back to screen
inc bx ; increase "number of processed chars" counter
jmp short nextChar ; Keep processing characters
; ---------------------------------------------------------------------------
upToSpace:
inc ah ; Increase character upwards towards a space
mov es:[di], ah ; Store altered character back to screen
inc bx ; increase "number of processed chars" counter
nextChar:
inc di
inc di ; es:di now points to next character
loop alterChars ; Continue processing characters
cmp bx, 0 ; Were any characters processed?
jnz short doScreen ; If so (bx != 0), keep processing
mov ah, 4Ch ; Otherwise, get ready to terminate
int 21h ; DOS - 2+ - QUIT WITH EXIT CODE (EXIT)
There are some very quick wins right off the bat:
- AX is already 0, so set AX=B800 by setting AH=B8
- Replace DOS exit sequence with RET, since this is a .COM file
- There is inefficient loading and saving of screen characters with manually advancing DI twice -- 80x86 has 1-byte string instructions to do that, so we'll use LODSW and STOSW. This will load the character attribute byte redundantly, but we don't care about speed, we care about size.
This shaves 10 bytes down to 39 total. At this point we can make some drastic changes that will shave bytes, but also make the program not behave exactly as it did before. I chose to do the following:
- 2000 is 7D0 in hex. Change MOV CX,2000 (decimal) to MOV CH,08 (hex) to shave a byte. This could result in CX being anywhere in the range 0800 to 08FF but the difference is minimal at execution time. It's also larger than the original area, but that is fine since there is there is extra screen RAM after the visible portion of the screen.
- The code contains a check for characters below #32 (space) and moves them upward. Most characters onscreen are going to be above #32, so this isn't really necessary and I removed the check. Even if it were, removing the check will just rotate them downward until they wrap around to 255, then go downward again until hitting #32 and stopping.
This gets the code down to 31 bytes:
org 100h
mov ah, 0B8h
mov es, ax ; es now points to screen segment
mov ds, ax ; ds = es so we can LODS and STOS to the same place
doScreen:
mov ch, 08 ; (80 * 25 = 2000/07d0h)
xor bx, bx ; bx is also our "num of altered chars" counter
mov di, bx ; es:di now points at the screen (b800:0000)
mov si, di ; ds:si = es:di, needed for lods/stos
alterChars:
lodsw ; Retreive onscreen character
cmp al, 32 ; comp to a space character (#32)
jz short nextChar ; If already a space, do nothing
dec al ; If higher than a space, decrease downward
inc bx ; increase "number of processed chars" counter
nextChar:
stosw
loop alterChars ; Continue processing characters
cmp bx, 0 ; Were any characters processed?
jnz short doScreen ; If so (bx != 0), keep processing
ret ; exit
Optimizing this further is left as an exercise to the reader. It may be possible to shrink it further using instructions for the 80186 and later processors, but it runs way too quickly on those systems, so if doing so you may want to insert a HLT to pace the animation at 18.2 Hz. It may also be possible to shrink it by taking advantage of the fact that ASCII character #0 is a null and shows nothing onscreen (just like the #32 space character), so instead of decrementing down to #32, you could decrement down to #0 in a more optimized form (like using the zero flag).