; coded by Soron of http://0x10cforum.com/, for the DCPU

; hereby released into the public domain

; (or use the Unlicense, CC0, etc., if your country doesn't have public domain)

; targetted for the DCPU-16 Studio assembler/emulator

; may or may not work for other emulators, since I/O hasn't been made official

; the snake is drawn on the background, and the food on the foreground

; suggested tweaks:

; adjust sleep_duration to speed up/slow down the game

; adjust the upper nybble of food_char to change food color

; adjust the lower byte of food_char to change food shape

; adjust prng_dat to start with a different RNG seed

; if you're not using a 32x16 window, adjust assorted things to not break

; (things such as dat_width and dat_height, up_dir and down_dir, etc.)

; note: assumes that it'll be run as a standalone program

; this could also probably be made more efficient, but, uh... not a priority,

; given that we're looping 0x1000 times just to slow things down!

; directions:

; 0x0001 = right

; 0x0020 = down (assuming a 32 char-width screen)

; 0xFFFF = left

; 0xFFE0 = up

; up/down WILL need to be changed if you use a different-width screen

; how registers, etc., are used:

; A = head position

; B = direction

; C = food position

; X = position of thing to draw on screen, etc

; Y = position of last segment touched

; Z = temporary data storage, for assorted calculations

; I = for loops and other iterations

; J = address of the stack element which stores the pos of start of tail

; SP = address of the stack element which stores the pos of end of tail

; O = not used. Why would you use this for Snake? What sort of madman ARE you?

:init

JSR clear_screen

SET A, 0x0001 ; head position (add 0x8000 to get screen pos)

SET B, 0x0001 ; direction

JSR gen_food

SET J, SP ; keep track of where OUR stack starts

SET PUSH, 0x0000 ; start of tail position

BOR [0x8000], [tail_color]

BOR [0x8001], [head_color]

:game_loop

JSR sleep

IFE [0x9000], [up_key]

SET PC, turn_up

IFE [0x9000], [down_key]

SET PC, turn_down

IFE [0x9000], [left_key]

SET PC, turn_left

IFE [0x9000], [right_key]

SET PC, turn_right

:move_snake

SET [0x9000], 0

SET Y, A

ADD A, B ; yay overflow ^_^!

SET X, A

:check_collision

SET Z, [dat_width]

MUL Z, [dat_height]

IFG X, Z ; went off the screen somehow

SET PC, lose_game

SET Z, X

ADD Z, 0x8000

IFE [Z], [tail_color] ; hit the tail!

SET PC, lose_game

IFE B, [dir_left]

SET PC, check_edge_collision

IFE B, [dir_right]

SET PC, check_edge_collision

SET PC, draw_snake

:check_edge_collision

;change this next block if you changed screen width

SET X, A

AND X, 0xFFE0

SET Z, Y

AND Z, 0xFFE0

IFN X, Z

SET PC, lose_game ; we have gone off the left/right edge!

SET X, A

:draw_snake

ADD X, 0x8000

BOR [X], [head_color]

SET I, J

:tail_loop

SUB I, 1

IFG SP, I

SET PC, finish_tail

SET Z, [I]

SET [I], Y

SET Y, Z

SET X, [I]

ADD X, 0x8000

BOR [X], [tail_color]

SET PC, tail_loop

:turn_left

IFN B, [dir_right]

SET B, [dir_left]

SET PC, move_snake

:turn_right

IFN B, [dir_left]

SET B, [dir_right]

SET PC, move_snake

:turn_up

IFN B, [dir_down]

SET B, [dir_up]

SET PC, move_snake

:turn_down

IFN B, [dir_up]

SET B, [dir_down]

SET PC, move_snake

:finish_tail

IFE A, C

SET PC, grow_tail

SET X, Y

ADD X, 0x8000

AND [X], 0xF0FF

SET PC, game_loop

:grow_tail

SET PUSH, Y

SET X, C

ADD X, 0x8000

AND [X], 0x0F00 ; make sure to clear the old food item!

IFE SP, 0xFFF5

JSR win_game

JSR gen_food

SET PC, game_loop

:gen_food

JSR rand

SET C, [prng_dat]

SET Z, [dat_width]

MUL Z, [dat_height]

MOD C, Z

SET X, C

ADD X, 0x8000

BOR [X], [food_char]

SET PC, POP

:win_game

SET J, 0x0

SET [J], 0x7041

ADD J, 0x1

SET [J], 0x8443

ADD J, 0x1

SET [J], 0x1051

ADD J, 0x1

SET [J], 0xa852

ADD J, 0x1

SET [J], 0x15b1

ADD J, 0x1

SET [J], 0x7031

ADD J, 0x1

SET [J], 0x3181

ADD J, 0x1

SET [J], 0x8442

ADD J, 0x1

SET [J], 0x144d

ADD J, 0x1

SET [J], 0x0dc1

ADD J, 0x1

SET [J], 0x3181

SET J, PC

ADD J, 0x8

SET [J], 0x7dc1

ADD J, 1

SET [J], 0x0

ADD J, 0x1

SET PC, init

:lose_game

SET PC, init

:sleep

SET I, 0

:sleep_loop

ADD I, 1

IFG I, [sleep_duration]

SET PC, POP

SET PC, sleep_loop

:sleep_duration

DAT 0x1000

:clear_screen

SET A, SP

SET SP, 0x8000

:clear_screen_loop

SET POP, 0

IFG SP, 0x8200

SET PC, clear_screen_exit

SET PC, clear_screen_loop

:clear_screen_exit

SET SP, A

SET PC, POP

:rand

;introduce at least a LITTLE unpredictability

MUL [prng_dat], A

ADD [prng_dat], SP

;now do the actual work

MUL [prng_dat], 31421

ADD [prng_dat], 6927

SET PC, POP

:prng_dat

DAT 0x13AD ; a reasonable start value

;if you change width, you WILL need to double-check edge-of-screen collision

:dat_width

DAT 32

:dat_height

DAT 16

:head_color

DAT 0x0700

:tail_color

DAT 0x0F00

:food_char

DAT 0xC040 ; "@" char, for all y'all roguelike fans :)

:dir_right

DAT 0x0001

:dir_down

DAT 0x0020 ; relies on screen width

:dir_left

DAT 0xFFFF

:dir_up

DAT 0xFFE0 ; relies on screen width

:up_key

DAT 0x0003

:down_key

DAT 0x0004

:left_key

DAT 0x0001

:right_key