Code Density
Alone Coder
As you remember, in Inferno #3 Shiru opu─
glare Snake length 255 bytes, and in Info
Guide #10 I published a simplified one Snake
length 121 bytes. It was written in codes
calculator Basic 48 and seemed quite
dense.
However, thenFirestarteroffered co─
completely new algorithmSnake.He didn’t write
to the editor, he wrote onHabr,and then here
I deleted the article. But save the source itself─
l.s.
The game took93bytes, but it worked
yes. All the "rabbits" are scattered across the field
already at the beginning of the level and are stored directly─
especially in the screen attributes. Array with core─
There are no snake dinatas either, there is only go─
fishing that movesand a certain definition is drawn─
line attribute code meaning “adult─
"loyalty" of the snake. I reduce every game frame─
there are attribute codes throughout the playing field,
except for empty spaces and rabbits. It turns out
that behind the head there is a trace - a “tail”,
a collision with which ends in pla─
obviously. The more mature the snake, the longer it is
this tail. And if she grew up to a certain level─
level, corresponding to full
absence of rabbits on the field, then the game begins─
appears first (but there is no point in this).
Thus, you only need to write ef─
efficient rabbit generation code, checks
cells in the position of the future head, decrement
colors while counting rabbits and
key polling.
After Firestarter abandoned
the project disappeared, the source code was optimized
krt17. He got 85 bytes
same behavior. But it was clear that85bytes -
not the limit. Moreover, it is known that
IBM PC Game
mat 64 byte, and Nibbles (snake that ra─
always walks, without rabbits or tail movement─
that) from the competitionHugi compo - total 48 bytes.
After preliminary acquaintance with
version fromkrt17I won a few bytes
by changing the order of pieces of code. Then
took registers into account when calling from BASIC. But this
It was not the most important reduction. Op code─
the dew of the keys remained quite greasy (after─
lui, fatter than in mine Snake121 ). And I
stepped in the most radical way - keyboard─
Our controls are chosen in such a way that
translated into delta head movement
most briefly: sub (hl):ld c,a:sbc a,a:
ld b,a. To do this, I had to choose enough─
but the original control keys are ","
left, "." right, ENTER up, M down.
The result was worth it - 55 bytes,tremble,
pussies!
At the last moment I discovered that I could─
but win a couple more bytes if you serve
collision with a tail that still exists─
it does, but is already visible as black. So in
in the source code you can switch two options -
55 bytes and 53 bytes!
;Alone Coder 53/55 bytes 11/10/2020
;from basic:
;de=0x5d83/5dcc, hl=0x2d2b, bc=start
;from TR-DOS RUN "..." CODE:
;de=0x5d83, hl=0x2d2b, bc=start
l1
ld d,0x5b
if 1==0 ;55 bytes
ld a,(hl)
inc a
cp 9
jr nc,restart ;collided with visible
;tail
else ;53 bytes
;a=0xff in most cases
add a,(hl)
cp d ;0x5b
jr c,restart ;collided with visible
;or invisible tail
endif
ld a,h
inc a
and 00000011b ;screen margin
jr nz,norestart
restart
;at restart: e=0xff, d=0x5b
ld h,d ;0x5b
rabbit1
ld a,r
ld (hl),e ;0xff
cpl
jr z,rabbit2
inc (hl) ;0x00
rabbit2
dec hl
bit 3,h
jr nz,rabbit1
;l=0xff
;bc=direction (delta)
inc h ;0x58
norestart
ld (hl),0x2d ;0x21 ;head
;, LEFT
;. RIGHT
;ENTER UP
;M DOWN
ld a,(23560)
sub (hl) ;0x2d
ld c,a
sbc a a
ld b,a
;bc=1/-1/32/-32
;ld d,0x5a
move1
ld a,(de)
dec a
cp 0xfe
jr nc,move2 ;was 0xff = rabbit
;or 0x00 = empty
ld (de),a ;move snake
;(decrease colour)
move2
jr nz,move3
dec (hl) ;was 0xff = uneaten rabbit,
;so decrease snake size
;(head color)
move3
dec de
bit 6,d
jr nz,move1
;e=0xff
;d=0x3f
add hl,bc
jr l1
* * *
Пожалуй, самым коротким Тетрисом для
Спектрума долгое время был Impetris - пла─
гин для ACEdit. Занимал он 745 байт, кидал
цветные фигуры, показывал следующую, счи─
тал очки (они даже прыгали на экране) и
даже ускорялся по мере прохождения. Что
касается прыгающих очков и ускорения, это
было довольно излишне в этом классе, да и
остальное выглядит необязательным для ре─
корда. А рекорд на IBM PC кpresent
time dropped below256 bytes (without under─
points count).
What can we do?
I took as a basis notImpetris,but Tetris
from NedoOS.There was quite loose, but
very clear code, previously written in
two hours as a tutorial. I did the work
on the stream, or rather, on two streams - posko─
lku Tetris is really not bad for the game─
assembly language lessons.
For example, what I did:
- replaced the coordinates of the figure with the address.
- combined drawing and erasing procedures
and checks.
- combined the procedures storeposition and
restoreposition.
-reduced key polling to a minimum.
- optimized search and removal of complete ones
lines (contrary to intuition,CPIRdid not help!)
I stopped streaming when I reached256
byte. But I couldn't decideShould I leave the walls?
(their absence led to looping
fields and the inability to show the score). But
then they suggested a simple procedure for me─
chat, and a few more came to mind
ideas, so the final version fits
256 bytes along with display of the score, that is
turns out better than the version forIBM PC!
Please note: the game expects that
at the beginning of the printer buffer (0x5b00) lie
zeros, as required on a48K machine. Cro─
besides, again, as it should be, ek─
the wound is filled with the attribute0x38.Memory after
The program is also considered empty.
The shapes take1bytes for each, except
the last one, and the first two are even used
as a parameterjp.
The walls are at a distance 16 know─
places apart from each other to draw them
one cycle, not two (the bottom is guaranteed
printer buffer).
And you can even play it!
;Alone Coder 02.28.12.2020
;48K or usr0 only!
;(0x5b00..5b1f must be clean)
SCORE=1
WALLS=1
if WALLS
fieldwid=16
else
fieldwid=32
endif
fieldhgt=24
fieldx=16-fieldwid/2
fieldy=0
addrDropFig=0x580c
emptyattr=0x38
newfig=%0110011011000110
if WALLS
;begin=0x6100-(fieldx-1)-16
begin=newfig-13
else
begin=newfig
endif
display "begin=",begin,"=",/D,begin
;hl=0x2d2b
;de=0x5dxx
;bc=begin
;hx=0
;iy=0x5c3a
;bc'=0x1114
;hl'=10072
if WALLS
xor a
ld hl,0x5800+(fieldx-1)
ld (hl),a
ld de,0x5800+(fieldx-1)+fieldwid
;ld b,2 ;bc,0x300-(fieldx-1)-32
ld bc,0x300-(fieldx-1)-16
ldir
;newfig
endif
if (WALLS == 0) && (SCORE == 1)
ld (iy+86),0xff
endif
ld a,r
and 7
ld hl,figs
add a,l
ld l,a
xor a
ld de,curfig
ld (de),a
inc de
ld b,3 ;-
rld
ld (de),a
inc de ;e
djnz $-4
ld hl,addrDropFig
ld (curxy),hl
gameloop
ld a,prfig_pixel&0xff ;proc addr
call prfig_curxy
halt
halt
halt
ld a,prfig_clearpixel&0xff ;procaddr
call prfig_curxy
;nz!!!
call storeposition_ldir ;bc=0
ld hl,(curxy)
ld (oldcurxy),hl
ld hl,(curxy)
xor a ;z!
in a,(0xfe)
rra ;'q'
jr c,$+3
dec l
rra ;'w'
jr c,$+3
inc l
;rra ;'e'
;jr nc,godown ;bug with right+down!!
nodown
jr nz,noautodown ;left or right
;pressed - don't fall
exx
rlc b ;was 0x11, then random(?) with
;few bits on
exx
jr nc,noautodown
godown
ld c,32 ;also flag for stopfig
add hl,bc
noautodown
ld (curxy),hl
rra ;'e';'r'
jr c,norotfig
ld hl,curfig
ld sp,hl
pop bc
pop de
ld sp,hl
rotfig0
xor a
rr c
rla
rr b
rla
rr e
rla
rr d
rla
ld (hl),a
inc l
jr nz,rotfig0
ld c,l ;0 ;or else rotation near a
;wall can stop fig!!!
norotfig
ld lx,c ;32=stopfig for godown,
;0=no godown
ld a,prfig_checkpixel&0xff ;procaddr
call prfig_curxy ;z=collision
call z,restoreposition ;can't move
;in that direction
jr nz,gameloop ;no collision
;a=0
cp lx ;32=stopfig for godown,
;0=no godown
jr z,gameloop ;collided not with gnd
stopfig
ld hl,(oldcurxy)
ld a,prfig_pixel&0xff ;proc addr
call prfig
;search a filled line from above
;if found, shift down (with lddr)
;ld hl,0x5800+fieldx-1
;ld lx,fieldhgt
ld hl,0x5a00+fieldx-1 ;only lowest 8
;lines can be fired
finddellines0
xor a ;for "or (hl)"
ld b,fieldwid
checkfilledline0
inc hl
or (hl)
djnz checkfilledline0
jr nz,finddellines_nofire
;ld a,emptyattr
;ld bc,fieldwid
;cpir
;jr z,finddellines_nofire ;extrabyte
;hl=last byte of current line
push hl
ex de,hl
ld hl,-32
add hl,de
ld a,h
sub 0x58
ld b,a
ld c,l
;bc=(y-1)*32
;bc=hl-#5800=de-#5820
lddr
if SCORE
inc hx
ld c,hx
ld a,22
rst 16 ;requires iy=23610
xor a
rst 16
rst 16
;call 11563
;call 11747
call 0x1a1b ;print bc
endif
pop hl
finddellines_nofire
;ld de,32-fieldwid
;add hl,de
;xor a ;for "or (hl)" in next line
;dec lx
;jr nz,finddellines0
ld a,l
add a,32-fieldwid
ld l,a
jr nc,finddellines0
if SCORE
;ld b,hx ;lines fired (0..4)
;inc a ;ld c,1 ;if there are fired
;lines, c=0 (if there aren't,
;c<<256 = 0)
;add a,a
;djnz $-1 ;a=2,4,8,16
;exx
;add a,h
;ld h,a
;exx
;ld c,a
;ld a,22
;rst 16 ;requires iy=23610
;xor a
;rst 16
;rst 16
;call 11563 ;6683
;call 11747 ;-
endif
;if WALLS
;jp newfig
;else
db 0xc3
;endif
figs
display "figs=",figs
db %11000110 ;zigzag2
db %01100110 ;square
db %00110110 ;zigzag1
db %11110000 ;I
db %00101110 ;L
db %01000111 ;J
db %01001110 ;T
db %01001110 ;T (8 figs)
nfigs=($-figs)
display "nfigs=",nfigs
restoreposition ;z
ld hl,(oldcurxy)
ld (curxy),hl
storeposition_ldir
restoreposition_ldir
;nz=store
;z=restore
ld bc,4
ld de,curfig
ld hl,oldcurfig
jr z,$+3
ex de,hl
ldir
prfig_clearpixel
ld (hl),emptyattr
ret
prfig_pixel
ld (hl),0
prfig_checkpixel
and (hl)
ret
prfig_curxy
ld hl,(curxy)
prfig
;NC!!!
ld (prfig_calladdr),a
or h
;(a&8) != 0
ld de,curfig
prfig_lines
ex de,hl
ld b,(hl) ;%0000pppp
set 4,b ;%0001pppp
ex de,hl
prfig_pixels
prfig_calladdr=$+1
call c,prfig_pixel
inc hl ;x
srl b ;pixel
jr nz,prfig_pixels
ld c,32-5
add hl,bc
inc e
jr nz,prfig_lines
or a
ret
end ;это конец файла игры!
curxy
dw 0
oldcurxy
dw 0
align 256
ds 256-4
curfig
ds 4
nop ;for spoiling
oldcurfig
ds4
* * *
Many have seen the ray trace source on
BASIC, which counts two balls 8cha─
owls This source flashed on the Internet
in2018 and disappeared, but made me think─
Xia: what speed can you expect?
in assembler, and is it possible to fit the render into
1 kilobyte (procedural graphics genre)?
To check, I did the following:
om:
- first rewrote this render in C++
Builder and got the correct picture.
- then replaced the floats with 32-bit ones─
ly numbers.
-then rewrote the code for compatibility
withNedoLang.
- moved the code to NedoLang and added it to
performance.
- procedures compiled by NedoLang
into assembler, optimized one after another.
- determined the required ranges of numbers and ne─
I rewrote everything to 16-bit integers.
-only after that began to drive into the ki─
lobyte taking into account the compressor (Hrum turned out
better than MegaLZ ) and add small op─
timization to be on the edge. Pos─
The latest optimization was on-the-fly calculation
squares, beyond which you definitely will not go─
balls are moving. (I did not enter these times─
measures firmly and even made a version from anime─
tion.)
So, my result (demonstrated on
Cosmonautics Day in 2019 year): exactly 1024
bytes, less than 71 seconds at the Pentagon.
Acceleration400times at the same resolution
256x176,as in the original!
It’s interesting thatDaniel A.Nagyalso provo─
did the same experiment, but used
your library of 2-byte floats (lpfp).
Its version is packaged
about 4 kilobytes and renders the screen more than5
minutes, with the quality just a little bit better,
than in our 16-bit integers...
I’ll save my version as a keepsake in the app─
marriage. The color version is in the repository
NedoOS. Maybe someone can speed it up
or come up with a useful application for this
raytrace?
* * *
There were a few more interesting things on Hugi compo
resin size compo:
-evaluating expressions with +-*/ , brackets
and unary +- ( 117 bytes)
-gamePong ( 142 bytes)
- game "tic-tac-toe" ( 213 bytes)
- game Sokoban with a given map ( 189
byte)
- interpreter brainfuck ( 98 bytes)
- search for prime numbers up to a million ( 76
byte)
There are also known records for the size of the shah─
matte programs (several less versions
512 bytes, game qualitywhich are yet to come─
worth comparing) and in terms of the size of the hatch lo─
gotype Unix ( http://www.deater.net/weave/
vmwprod/asm/ll/ll.html - however, the code for
Z80 is written very poorly there).
Usually x86 outperforms all others
processors. But does this mean that it is impossible
come up with a tighter command system?
Of course not. There's quite a lot of bits in there
is spent on specifying register numbers,
addresses and word width. The byte size itself in
8bit is also not necessarily ideal. Commands
can be shorter, up to4 bits per station─
kov processor.
I conducted such an experiment in a newspaper
ACNews #59;then I finished this engine, but
never used it; then I tried to write
stack processor with 4-bit instructions and
forFPGA.
My last thought on interpretability─
mu stack processorlooked like this (with─
absolutely not tested for functionality─
ness):
;for conditions, outputs and branches 3 commands
; jrNC N, ifZ{scf}
;for mathematics 4 commands
; sub, add, sub1, inc(+rcf)
;for stack 3 commands
; push(dup), pop(skip), swap
;for variables 4 commands:
; getvar N, putvar N, peek8, poke8
;3 commands to control
; callr N, ret, z80
(z80 allows you to further write machine code)
;enter the script using rst #10, for this you need
;patch the address in the channel descriptor #5cb6
;(#5d26 in TR-DOS)
;104b:
_startscript
pophl ;skip #15fe (return from call
;#162c)
pop hl ;restore hl'
exx
;de=вершина стека
pop hl ;address after RST #10
_callq:
ld c,0x55
_getcmd:
ld a,(hl)
rlc c
jr nc,$+2+5 ;первый раз не сдвигаем
rrca
rrca
rrca
rrca
inc hl
and 15
ret z ;0=ret ;флаги сейчас в f',
;на выходе из call они достанутся
ld b,a
push hl ;return addr
ld l,(hl) ;read 8bit
djnz _ncall
;1 ;call N
;lies at the end of the byte, so now
;c=0x55, hl=next byte
call jphl ;jp (hl);call machine code
;procedure ;output flags in f'
_popincq
;flags are now in f'
pop hl ;return addr after call-ret
inc hl ;skip 8bit
jr _callq ;can be converted to
;push bc... pop bc (slower, but
;does ex af,af')
_ncall:
ld h,vars/256
djnz _nputvar
;2 ; putvar N: (var)<=reg (do not delete it,
;otherwise there's an extra pop)
;lies at the end of the byte, so now
;c=0x55, hl=next byte
ld (hl),e
inc hl
ld (hl),d
jr _popincq ;can't throw away
_nputvar:
djnz _ngetvar
;3 ; getvar N: reg<=(var) (overwriting the old one
;TOS, otherwise there is an extra push - but without it you can
;merge branches)
;lies at the end of the byte, so now
;c=0x55, hl=next byte
ld e,(hl)
inc hl
ld d,(hl)
jr _popincq
_ngetvar:
pop hl
ex af,af' ;flags
djnz _njrnc
;4
jr c,_getcmd
ld l,(hl)
_njrnc:
djnz_npush
;5
push de
_npush:
djnz _npop
;6
pop de
_npop:
djnz _nswap
;7
ex de,hl
ex (sp),hl
ex de,hl
_nswap:
ex (sp),hl
djnz _nsub
;8 ;reg<=pop-reg-CY ;_oldminusnew
sbc hl,de
_nsub:
djnz _nadd
;9 ;reg<=reg+pop
add hl,de
_nadd:
djnz _nsub1
;A ;sub1
scf
ld d,b ;0
ld e,b ;0
sbc hl,de
_nsub1:
ex de,hl
pop hl
djnz _ninc
;B ;inc(+rcf)
inc de
or a
_ninc:
djnz _npeek8
;C ;peek8 ;можно сделать h<-l<-(hl)?
ld l,(hl)
ld h,b ;0
_npeek8:
djnz _npoke8
;D ;poke8
ld a,l
pop hl
ld (hl),a
_npoke8:
djnz _nifzscf
;E ;ifZ{scf}
jr nz,_nifzscf
scf
_nifzscf:
ex af,af' ;flags
djnz _getcmd
;F ;z80 - no output to _getcmd
;lies at the end of the byte, so now
;c=0x55, hl=next byte
jp (hl)
We, of course, do not believe in fantastic
numbers like "The code for the M-machine was in three─
three times less code for PDP-11 and i8086
two to three times less code for Motorola
68000 and one and a half to two times less code for
National Semiconductor 32000",but what the hell
not kidding? After all, teams can even be
floating length. And if you sharpen it at all
processor for a specific task (for example─
measures, matrix calculations), then the program
for it may be half as much as for
regular CPU: https://
nabbla1.livejournal.com/234532.html
But the question is: if you didn’t depend on those─
current command systems and ready-made compilations─
ditch, and they designed it themselvesprocessor, then ka─
What command system would you suggest? Auto─
ryRISC-Vclaim that despite the request─
this command system, they have reached density
code is no worsex86.Is this really true?
le? Is it possible to somehow prove “almost-opti─
smallness" of the command system in size for
typical tasks? For example, what is more than
10% can no longer be reduced at the given limits─
tions (say, no more than256 commands)?
Write your thoughts!
Share your thoughts about the article