Skip to content

Commit

Permalink
reduce two bytes by replacing jae and xor with adc (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
donno2048 authored Apr 13, 2024
1 parent aea033b commit 823cd09
Show file tree
Hide file tree
Showing 5 changed files with 18 additions and 21 deletions.
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ It is so small I could fit it into a single QR:

<img src="/demo/qr.png" width="250"/>

It's 60 bytes.
It's 58 bytes.

How little is 60 bytes? Well, this line of text weighs more than 70 bytes.
How little is 58 bytes? Well, this line of text weighs more than 70 bytes.

And so does this arbitrary sequence of emojis: 👩🏼‍❤️‍💋‍👨🏼🧔🏽‍♀️👩🏼‍❤️‍💋‍👨🏼

Expand All @@ -94,19 +94,17 @@ An **empty** C program generated with `gcc -Os -w -xc - <<< "main;"` on linux-x8
<br/>

```
c53000b80000cd108b3f
8d22e54021c30837bbd0
0778f5e4606bc00ad414
d5449801c739df73dc30
1d79d8ad893a7bdc880f
83eb5079f95b202779d6
c53000b80000cd108b3f8d22e5402
1c3300fbbd0077af5e4606bc00ad4
14d5449801c739dfad10257bd9893
a74de880f83eb5079f95b88277bd8
```
</details>

### Comparison

||My version|MattKC's version|ibara's version|
|-|-|-|-|
|Bytes|60|~1400|2024|
|Bytes|58|~1400|2024|
|QR|<img src="/demo/qr.png" width="250"/>|<img src="https://mattkc.com/etc/snakeqr/code.png" width="250"/>|<img src="https://raw.githubusercontent.com/ibara/snakeqr/master/snakeqr.png" width="250"/>|
|Link|https://github.com/donno2048/snake|https://mattkc.com/etc/snakeqr/|https://github.com/ibara/snakeqr|
Binary file modified demo/qr.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo/snake.zip
Binary file not shown.
4 changes: 2 additions & 2 deletions docs/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

hexdata = open(0).read().replace("\n", "")

length = len(hexdata) // 2
length = len(hexdata)

div = next(filter(lambda x: not length % x, range(int(length ** .5), length)))

open("README.md", "w").write(open(Path(__file__).parent.resolve() / 'template.md').read().format(size = length, hex = "\n".join(findall('..' * div, hexdata)), platform = get_platform(), empty_size = Path("a.out").stat().st_size))
open("README.md", "w").write(open(Path(__file__).parent.resolve() / 'template.md').read().format(size = length // 2, hex = "\n".join(findall('.' * div, hexdata)), platform = get_platform(), empty_size = Path("a.out").stat().st_size))
19 changes: 9 additions & 10 deletions snake.asm
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
; register usage during main loop
; DS: 0xB800, segment of screen buffer
; BX: 0x7D0, screen size (40x25x2 bytes), used in food generation, edge checks and for snake character, also used for screen accesses but constantly reinitialized
; BX: 0x7D0, screen size (40x25x2 bytes), used in food generation, edge checks, also used for screen accesses but constantly reinitialized
; DI: position of the snake head
; SI: pointer to memory location where the current position of the snake head is stored (actual pointer is BP+SI because it defaults to SS)
lds si, [bx+si] ; SI=0x100 and BX=0x0 at program start in most DOS versions, this initializes DS and SI (machine code at 0x100 is c5 30 00 b8)
Expand All @@ -13,27 +13,26 @@ start: ; reset game
.food: ; create new food item
in ax, 0x40 ; read 16 bit timer counter into AX for randomization
and bx, ax ; mask with BX to make divisible by 4 and less than or equal to screen size
or [bx], dh ; place food item and check if position was empty by applying OR with DH (assumed to be 1)
xor [bx], cl ; place food item and check if position was empty by applying XOR with CL (assumed to be 0xFF)
.input: ; handle keyboard input
mov bx, 0x7D0 ; initialize BX
js .food ; if position was occupied by snake or wall in food generation => try again, if we came from main loop SF=0
jp .food ; if position was occupied by snake or wall in food generation => try again, if we came from main loop PF=0
in al, 0x60 ; read scancode from keyboard controller - bit 7 is set in case key was released
imul ax, BYTE 0xA ; we want to map scancodes for arrow up (0x48/0xC8), left (0x4B/0xCB), right (0x4D/0xCD), down (0x50/0xD0) to movement offsets
aam 0x14 ; IMUL (AH is irrelevant here), AAM and AAD with some magic constants maps up => -80, left => -2, right => 2, down => 80
aad 0x44 ; using arithmetic instructions is more compact than checks and conditional jumps
cbw ; but causes weird snake movements though with other keys
add di, ax ; add offset to head position
cmp di, bx ; check if head crossed vertical edge by comparing against screen size in BX
jae start ; if DI<0 or DI>=BX => game over
xor [di], bl ; XOR head position with snake character
jns start ; if it already had snake or wall in it, SF=0 from XOR => game over
lodsw ; load 0x2007 into AX from off-screen screen buffer and advance head pointer
adc [di], ah ; ADC head position with 0x20 to set snake character
jnp start ; if it already had snake or wall in it or if it crossed a vertical edge, PF=0 from ADC => game over
mov [bp+si], di ; store head position, use BP+SI to default to SS
jnp .food ; if food was consumed, PF=0 from XOR => generate new food
jz .food ; if food was consumed, ZF=1 from ADC => generate new food
.wall: ; draw an invisible wall on the left side
mov [bx], cl ; store wall character
sub bx, 0x50 ; go one line backwards
sub bx, BYTE 0x50 ; go one line backwards
jns .wall ; jump to draw the next wall
pop bx ; no food was consumed so pop tail position into BX
and [bx], ah ; clear old tail position on screen, AND also clears SF
jns .input ; loop to keyboard input
mov [bx], ah ; clear old tail position on screen
jnp .input ; loop to keyboard input, PF=0 from SUB

0 comments on commit 823cd09

Please sign in to comment.