Hacker News new | past | comments | ask | show | jobs | submit login
LeanChess: The smallest chess-playing program at 328 bytes (leanchess.github.io)
4 points by krilovsky on Dec 1, 2019 | hide | past | favorite | 5 comments



This is really cool.

One trick I used to use to keep code small, was to take advantage of the fact that CX always equals 00h upon exiting a loop (when there are no short-cutting JMP related statements before it, of course).

Same goes for when you know what the registers will be set to after a particular interrupt has been triggered, such as if it has counted characters or loops or whatever. Not all of the return values are standardized, but a good lot of them are.

I've noticed sometimes people preserve registers a lot more often than they have to, like this:

    enter:
      push ax
      push bx

      mov  ax, ?  ; whatever value
      mov  bx, ?  ; whatever value
      int  21h    ; for example....

      pop  bx
      pop  ax

    exit:
      mov  ax, 0h
      mov  ax, bx
      ...
... to me this is really stupid code, but I see it a LOT in assembler. Instead, they could do something tinier like this:

    enter:
      mov ax, ?  ; whatever value
      mov bx, ?  ; whatever value
      int 21h    ; for example....

    exit:
      ; now stuff the regs to what you
      ; want them to be, keeping in mind
      ; the known exit values from the
      ; interrupt
I used to have a lot of tricks, since programming on the 8086 pretty much ensured you needed to know at least a few of them.

It's a rabbit hole, but a fun one to follow if you are trying to shrink your code. And if AX is already zero, use INC AX instead of MOV. But those kinds of things I'm sure you already do in your code. I haven't reviewed it all that thoroughly.


Thanks.

Indeed, the very first instruction is

  int 10h  ;BIOS display mode 0
that relies on AX being 0.

As for CX, there aren't that many loops in LeanChess (precisely 5, but who's counting :)). However, CH being always 00h allows to save a couple more bytes by doing

  mov cl, 8
instead of

  mov cx, 8


Yup, I actually got to the end of reading the code and noticed you do things like that. You have to read slowly to get all the nuances, so it was totally worth the effort.

The weirdest DOS "shortcut" I ever did was to remap INT 21h onto INT 3, giving me a single-byte INT 21h with a debug-killing side effect. Of course you have to have enough INT 21h's to convert to make that worthwhile, but it is a neat trick to have about if you have a lot of the same INT repeated throughout.

    sub     ax,ax
    mov     ds,ax
    mov     es,ax
    mov     si,21h*4
    mov     di,3*4
    movsw
    movsw
Of course if you aim it just right, you don't need the sub ax,ax. For that matter, it's an ideal place to use cx if you just exited a loop. And even then, you could probably find other short methods to make similar change. You don't even need to cli/sti because the chances of an INT 3 happening in the middle of the change is of no consequence, and all your INT 3 calls will only happen after this part runs.


I'm not the author. I'm just fascinated with size optimisations and I thought that it was an interesting achievement. But yes, the simplest optimisation is to not write code that you don't need to.

I've had my fair share of x86 assembly programming, and I used to do all sorts of tricks back then as well (I vaguely remember calling DOS interrupts just to get multiple registers into the state I wanted them when speed didn't matter as much as size), but I don't think that I'd have been able to cramp a working chess program in such a small amount of bytes.


Agreed. It's smaller than a boot sector, yet does a whole lot more.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: