; Copyright (c) 2013, Wamanuz
; All rights reserved.
;
; Redistribution and use in source and binary forms, with or without
; modification, are permitted provided that the following conditions are met:
;     * Redistributions of source code must retain the above copyright
;       notice, this list of conditions and the following disclaimer.
;     * Redistributions in binary form must reproduce the above copyright
;       notice, this list of conditions and the following disclaimer in the
;       documentation and/or other materials provided with the distribution.
;     * Neither the name of the organization nor the
;       names of its contributors may be used to endorse or promote products
;       derived from this software without specific prior written permission.
;
; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
; DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
; (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
; ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

SET PC, main              ; Jump directly to main, so that it doesn't try to 'execute' the variables. 

:GOL_BOARD DAT 0x784    ; The start address of the first GoL-board. The GoL-board starts 4 words after font-memory to simplify some calculations. 
:GOL_BOARD2 DAT 0x1000  ; The start address of the second GoL-board. 

:main 
    SET A, 0x500         
    JSR init_lem1802     ; Assign VRAM to A and forward
    
    SET A, 1             ; Set font 
    SET B, [LEM_VRAM]    
    ADD B, 0x180         ; directly after VRAM
    SET [LEM_FONT], B
    HWI [LEM_INDEX]
    
    JSR generate_font 
    
    SET A, [GOL_BOARD]   ; Write a Glider at the top left corner. 
    JSR write_glider 
    
    ; The main loop. Infinitly prints the game board 
    ; and calculate the ticks. Alternates between two different boards to speed up 
    ; calculation at the cost of memory (but there is plenty of that). 
    SET X, 0 
    :main_loop
        IFE X, 0 
            SET A, [GOL_BOARD]
        IFE X, 1 
            SET A, [GOL_BOARD2]
            
        JSR print_board 
    
        IFE X, 0 
            SET A, [GOL_BOARD]
        IFE X, 1 
            SET A, [GOL_BOARD2]
        JSR run_tick 
        
        XOR X, 1
    SET PC, main_loop


:generate_font 
    SET PUSH, X
    SET PUSH, Y
    SET PUSH, Z
    
    SET X, 0           ; X is a counter that the fonts generates from 
    
    :generate_font_loop
        SET A, X
        
        SET Y, X             ; Set Y to the current char-number. 
        MUL Y, 2             ; Multiply it by two since every char is 2 words 
        ADD Y, [LEM_FONT]    ; Then add the adress to font memory to get this characters position.
        
        AND A, 0xf
        JSR generate_word 
        SET [Y], B           ; write word to font-memory 
        ADD Y, 1             ; Add one to get the next place 
        
        SET A, X
        SHR A, 4             ; Shift the other 4 bits in place 
        JSR generate_word 
        SET [Y], B           ; write the second word to font memory 
        ADD X, 1
        IFG X, 127
            SET PC, generate_font_end  ; repeat while X <= 128 
    SET PC, generate_font_loop
    :generate_font_end 
    SET Z, POP 
    SET Y, POP
    SET X, POP 
    SET PC, POP

; In register A is 4-bits that tells it what font-word to generate. 
; It then returns the generated word in register B
:generate_word
    SET PUSH, X
    SET PUSH, Y
    SET PUSH, Z
    
    SET B, 0
    SET Y, 1
    :generate_word_loop
        SET X, A 
        SET Z, cells   ; reset Z every iteration, otherwise it will add and add and add... 

        IFB X, Y       ; If X&Y >= 1, then get the current cell by adding Z and Y 
            ADD Z, Y
        IFB X, Y       ; BOR with the corresponding cell
            BOR B, [Z]
        SHL Y, 1       ; Shift Y to the left to get the next bit, and repeat. 

        IFG Y, 8       ; If Y is greater than 4, break 
            SET PC, generate_word_end 
    SET PC, generate_word_loop
    :generate_word_end 
    
    SET Z, POP
    SET Y, POP
    SET X, POP
    SET PC, POP

:cells 
    DAT 0x0000  ; 0
    DAT 0x0303  ; 1
    DAT 0x0c0c  ; 2
    DAT 0x0000
    DAT 0x3030  ; 4
    DAT 0x0000 
    DAT 0x0000 
    DAT 0x0000
    DAT 0xc0c0  ; 8


    
; Prints board where the start word is given in A, 
; Assumes 4 words wide and 3 words tall board.  
:print_board 
    SET PUSH, X
    SET PUSH, Y
    SET PUSH, Z 
    
    SET Z, A       ; Store the adress in Z. 
    SET Y, 0       ; Offset on screen 
    SET X, 0       ; Counts the number of rows printed 
    
    :print_board_loop
        SET A, Z
        SET B, Y
        JSR print_row 
        
        ADD X, 1      ; Printed 1 row. 
        ADD Y, 32     ; Add 32 so the next row will be printed at the next row. 
        ADD Z, 16     ; Hop down to the next row. 
        
        IFG X, 11     ; Break when all the rows are printed. 
            SET PC, print_board_loop_end 
    SET PC, print_board_loop
    
    :print_board_loop_end
    
    SET Z, POP
    SET Y, POP
    SET X, POP
    SET PC, POP

; Prints 4 words starting from the word given in A. Prints them 
; in position given in B. 
:print_row 
    SET PUSH, X
    SET PUSH, Y
    SET PUSH, Z
    
    SET Z, A       ; Store the adress in Z. 
    SET Y, B       ; Offset on screen 
    SET X, 0       ; Counts the number of words printed 
    
    :print_row_loop 
        SET A, Z     ; Set A to the word that should be printed. 
        SET B, Y     ; And B to where on the screen. 
        JSR print_word
        
        ADD X, 1     ; Add to word counter. 
        ADD Y, 8     ; Add 8 to Y since each word has 8 characters in them. 
        ADD Z, 1
        
        IFG X, 3    ; All 4 words have been printed, break 
            SET PC, print_row_loop_end 
    SET PC, print_row_loop 
    
    :print_row_loop_end 
    
    SET Z, POP
    SET Y, POP
    SET X, POP
    SET PC, POP
    
; Prints all 8 cells contained in the word given in A, write on screen position given 
; in B to B + 8
:print_word 
    SET PUSH, X
    SET PUSH, Y
    SET PUSH, Z 
    SET Z, A           ; Save the address in Z. 
    SET X, B           ; Store the screen position in B. 
    
    SET Y, 0           ; Y is the bit offset
    :print_word_loop
        SET A, Z       ; Move the current address to A. 
        SET B, Y       ; Move the current bit-offset to B. 
        JSR get_cell 
    
        SET A, B       ; Move the returned cell to A 
        SET B, Y       ; Divide the bit offset to get position on screen. 
        DIV B, 2
        ADD B, X 
        SET PUSH, A    ; Push the A value cause' print_char changes it, and it is needed 
                           ; here later. 
        IFL A, 128     ; If the returned cell is lesser than 128, print it normaly. 
            JSR print_char 
        SET A, POP     
            
        IFG A, 127     ; But if it's 128 or greater, flip the bits and print it inverted. 
            SET PC, print_inverted
            
        ADD Y, 2       ; Increase the bit offset by 2. 
            
        IFG Y, 15      ; All cells in this word has been read, move on to the next. 
            SET PC, print_word_loop_end
            
    SET PC, print_word_loop
    
    :print_inverted 
        XOR A, 0xff 
        JSR print_char_s 
        ADD Y, 2 
        IFG Y, 15 
            SET PC, print_word_loop_end 
    SET PC, print_word_loop
    
    :print_word_loop_end
    SET Z, POP
    SET Y, POP
    SET X, POP
    SET PC, POP
    
; Prints the cell given stored in memory position A and the byte offset given in B. 
; Assumes 4 words wide and 3 words tall board. 
; Returns the cell in register B. 
:get_cell 
    SET PUSH, X
    SET PUSH, Y
    SET PUSH, Z 
    SET X, 0    ; The resulting byte will be stored here  
    
    SET C, 0x8000  ; C is the bit-pointer. It filters out the wanted bit. 
    SHR C, B       ; B is the offset, so shift the pointer to the right that amount. 
    
    IFB [A], C
        BOR X, 1
    
    ADD A, 4 
    IFB [A], C
        BOR X, 2
    
    ADD A, 4
    IFB [A], C
        BOR X, 4
    
    ADD A, 4
    IFB [A], C
        BOR X, 8
    
    SUB A, 12
    SHR C, 1
    
    IFB [A], C
        BOR X, 16
    
    ADD A, 4 
    IFB [A], C
        BOR X, 32
    
    ADD A, 4
    IFB [A], C
        BOR X, 64
    
    ADD A, 4
    IFB [A], C
        BOR X, 128
    
    SET B, X
    
    SET Z, POP
    SET Y, POP
    SET X, POP
    SET PC, POP

:CURRENT_BOARD DAT 0x0000  ; A variable for easy access to the current board. 
:OTHER_BOARD DAT 0x0000   ; A variable for easy access to the other board. 
; Simulates a game of life tick, removes and 
; add cells. Takes the start position of the board in register A. 
:run_tick 
    SET PUSH, X
    SET PUSH, Y
    SET PUSH, Z 
    
    SET Z, A     ; Save the start address in Z
    SET [CURRENT_BOARD], A  ; And in the CURRENT_BOARD variable 
    
    IFE Z, [GOL_BOARD]     
        SET [OTHER_BOARD], [GOL_BOARD2]   
    IFE Z, [GOL_BOARD2]
        SET [OTHER_BOARD], [GOL_BOARD]
    
    SET A, [OTHER_BOARD]
    SET B, 192 
    JSR clear_mem    ; Clears the other board because we will save to there. 
    
    :run_tick_outer_loop 
    SET Y, 0     ; Save the bit offset in Y 
    
    ; run_tick_inner_loop loops over a word and for each bit calculates the new state 
    ; for the cells. 
    :run_tick_inner_loop 
        SET A, Z 
        SET B, Y 
        JSR count_neighbours 
        SET PUSH, B  ; Push the number of neighbours 
        
        SET A, Z
        SET B, Y
        JSR is_alive ;checks if current cell is alive 
        
        IFE B, 0 
            SET PC, run_tick_dead 
        SET PC, run_tick_alive     
    
    ; The checked cell is dead, determine what to do next. 
    :run_tick_dead 
    SET B, POP  ; POP back the number of neighbours in B 
    
    IFE B, 3            ; If a dead cell has exactly 3 neighbours 
        SET A, 0x8000   ; it should come alive. 
    IFN B, 3 
        SET A, 0 
    
    SET PC, run_tick_write 
    
    ; The checked cell is alive, determine what to do next. 
    :run_tick_alive 
    SET B, POP   ; POP back the number of neighbours 
    SET A, 0x8000 
    
    IFL B, 2       ; If a live cell has less than 2 neighbours it dies 
        SET A, 0 
    
    IFG B, 3       ; IF a live cell has more than 3 neighbours it dies s
        SET A, 0 
    
    ; Write the the cells state to the other board in memory. 
    :run_tick_write   
    
    SHR A, Y                 ; shift to position 
    SET X, Z                 ; Store the value to the second board in X 
    SUB X, [CURRENT_BOARD]   ; Subtract the start address to get the word offset from the board. 
    ADD X, [OTHER_BOARD]     ; Add the addres to the second board 
    BOR [X], A               ; Merge the word there with this bit.
    
    ADD Y, 1  
    IFG Y, 15                ; If all bits in the inner loop has been checked, 
        SET PC, run_tick_inner_loop_end  ; change word and reset the bit-offset. 
    SET PC, run_tick_inner_loop 
    
    :run_tick_inner_loop_end 
    ADD Z, 1 
    
    SET X, Z 
    SUB X, [CURRENT_BOARD]   ; Subtract the current word start 
    
    IFG X, 192    ; If the word-counter is outside the board, break. 
        SET PC, run_tick_outer_loop_end 
    SET PC, run_tick_outer_loop 
    
    :run_tick_outer_loop_end 
    
    ; The entire board have been searched through, now copy the temporary board 
    ; to the real board. 
    
    ;SET Z, [GOL_BOARD]
    ;SET X, [GOL_BOARD2]
    
    ;:run_tick_copy_loop 
    ;    SET [Z], [X]
    ;    SET [X], 0 
    ;    ADD X, 1 
    ;    ADD Z, 1 
    ;    SET Y, [SCND_BOARD]
    ;    ADD Y, 192 
    ;    IFG X, Y 
    ;        SET PC, run_tick_copy_loop_end 
    ;SET PC, run_tick_copy_loop 
    
    :run_tick_copy_loop_end 
    
    SET Z, POP
    SET Y, POP
    SET X, POP
    SET PC, POP

; Takes an adress in A and a bit-offset in B. 
; Returns the value in that word and bit offset in B. 
:is_alive 
    SET C, 0x8000 
    SHR C, B 
    
    SET B, 0 
    IFB [A], C 
        SET B, 1 
        
    SET PC, POP 

    
; Takes an adress to a word in A and a bit offset in B. Then counts the 
; number of neigbours that cell has and returns it it B. 
:count_neighbours 
    SET PUSH, X
    SET PUSH, Y
    SET PUSH, Z
    
    SET C, 0x8000     ; Start with the left-most bit. 
    SHR C, B          ; Bitshift it to place 
    
    SET PUSH, A       ; Push the address and bit offset for later use 
    SET PUSH, C
    
    SET B, 0          ; B is now the number of neigbours 
    
    ; Start the checking of neighbours 
    ; First check the one to the left 
    SHL C, 1
    IFE C, 0       ; Overflow, no bit to the left in current word, 
        SET PC, decr_addr   ; Set it to the next word 
    SET PC, check_left
    
    :decr_addr
    SET C, 1 
    SUB A, 1
    SET X, A  
    MOD X, 4    ; Calculate the modulo of the adress.  
    IFE X, 3    ; If it is equal to 3 it has wrapped around and left is not a valid direction 
        SET PC, skip_left 
    
    :check_left 
    IFB [A], C     ; The left one is alive, add it to neighbours 
        ADD B, 1   ; 
    
    SUB A, 4       ; To check the top left corner, subtract 4 from the adress 
    IFB [A], C
        ADD B, 1 
    
    ADD A, 8       ; Add 8 to check the bottom left corner.   
    IFB [A], C
        ADD B, 1
    
    :skip_left 
    SET C, POP     ; Reset the adress and bit offset to the main cell, 
    SET A, PEEK     ; without removing the values from the stack. 
    SET PUSH, C 
    
    ; Check the neighbour to the right
    SHR C, 1
    IFE C, 0               ; Overflow, no bit to the right in current word, 
        SET PC, incr_addr  ; Set it to the next word 
    SET PC, check_right 
    
    :incr_addr 
    SET C, 0x8000 
    ADD A, 1 
    SET X, A
    MOD X, 4
    IFE X, 0
        SET PC, skip_right    ; right is not a valid direction
    
    :check_right
    IFB [A], C     ; The right one is alive, add it to neighbours 
        ADD B, 1   ; 
    
    SUB A, 4       ; To check the top right corner, subtract 4 from the adress 
    IFB [A], C
        ADD B, 1 
    
    ADD A, 8       ; Add 8 to check the bottom right corner.   
    IFB [A], C
        ADD B, 1
    
    :skip_right
    
    ; Only the directly above and below cells are remaining. 
    SET C, POP    ; Reset to main cell again, but this time remove 
    SET A, POP    ; them from the stack. 
    
    SUB A, 4
    IFB [A], C
        ADD B, 1
    
    ADD A, 8
    IFB [A], C
        ADD B, 1
    
    SET Z, POP
    SET Y, POP
    SET X, POP
    SET PC, POP
    
    ; Version: 2013-01-26
; Written for LM1802

; 
; This program is using the principle that A, B and C acts like arguments to 
; subroutines, and the data is not guaranteed to be the same after the
; subrotine end. 
; Register X, Y and Z must however always be returned as they were sent in. 
; Other registers are undefined. 


; 
; Inits the LEM1802 display by finding the 
; monitor hardware index. The VRAM is 
; placed where the A register is pointing at. 
; If A equals to 0 the monitor will disconnect. 
; 
; The monitor index will be conveniently be saved to 
; [LEM_INDEX] if succeded, otherwise 0xffff.
; Also saves the handy a handy pointer [LEM_VRAM] to 
; where it's allocated. 
; 

:LEM_INDEX DAT 0x0000
:LEM_VRAM DAT 0x0000 
:LEM_FONT DAT 0x0000

:init_lem1802 
    SET [LEM_VRAM], A  ; Save the pointer to VRAM start, given as a parameter. 
    HWN I          ; Store the number of connected hardware devices 
    
    :init_lem_loop ; Weird name to avoid name collisions 
        SUB I, 1 
        SET [LEM_INDEX], I  ; Every loop save I to [LEM_INDEX]

        ; Overflow happened, meaning all the hw-indices has been searched. 
        IFE I, 0xFFFF   
            SET PC, init_end  ; 0xffff has already been saved to [MEMORY_INDEX], end loop. 
    
        HWQ I
        IFE A, 0xF615    ; The monitor has been found
            SET PC, init_end  ; End the loop

    SET PC, init_lem_loop

    :init_end
        SET A, 0
        SET B, [LEM_VRAM]
        HWI [LEM_INDEX]      ; Initialize the monitor VRAM using the given pointer. 
        SET PC, POP          ; Return to where it was called from. 


        
:LEM_COLOR DAT 0xf000 

; Prints a character stored in register A, using using the foreground color 
; and background color stored in the 8 first MSB in color, in VRAM-index B.
:print_char 
     AND A, 0x007F  ; Zero all bits except the 7 LSB 
     BOR A, [LEM_COLOR]
     ADD B, [LEM_VRAM]
     SET [B], A
     SET PC, POP 


; Prints the char A using B and C as X and Y-values instead. 
:print_char2 
	MUL C, 32 
	ADD B, C
	JSR print_char 
	SET PC, POP

; Like print_char but prints it with swapped foreground and background colors. 
:print_char_s
    SET PUSH, A    ; Push A and B to stack
    SET PUSH, B
    SET A, [LEM_COLOR]  
    AND A, 0xf000         ; Store the 4 first MSBs in A 
    SHR A, 4              ; Shift them to the right 
    SET B, [LEM_COLOR]    
    AND B, 0x0f00         ; Store the second group of 4 bits in B 
    SHL B, 4              ; Shift them left 
    BOR A, B              ; Merge them together in A 
    
    SET B, POP            ; POP the coord
    ADD B, [LEM_VRAM]
    BOR A, POP            ; POP the char and store merge with color. 
    SET [B], A
    SET PC, POP
    
; A subroutine that prints the current. Usefull when using a custom one. 
:print_font
    SET PUSH, X 
    SET X, 0 
    :print_font_loop 
        SET A, X
        SET B, X
        JSR print_char
        ADD X, 1
        IFG X, 128
            SET PC, print_font_end 
    SET PC, print_font_loop
    :print_font_end
    SET X, POP
    SET PC, POP


; A subroutine that prints the character in A over the entire screen. 
; Usefull for clearing the screen by using a full sized character. 
:fill_screen
    SET PUSH, X
    SET PUSH, Y
    SET X, A
    SET Y, 0
    :fill_screen_loop
        SET A, X
        SET B, Y
        JSR print_char 
        ADD Y, 1
        IFG Y, 0x180 
            SET PC, fill_screen_end 
        SET PC, fill_screen_loop
    :fill_screen_end
    
    SET Y, POP
    SET X, POP
    SET PC, POP





; Takes an adress from the GoL-board in the A register and writes 
; a glider to it. 
:write_glider 
    SET [A], 0x4000      ; Store a glider at the upper left corner. 
    ADD A, 4
    SET [A], 0x2000 
    ADD A, 4 
    SET [A], 0xe000
    ADD A, 4 
    SET [A], 0x0000
    
    SET PC, POP 

; Takes an adress from the GoL-memory area in the A register and 
; writes the pattern using it as the top left corner. 
:write_acorn 
    SET [A], 0x4000 
    ADD A, 4
    SET [A], 0x1000 
    ADD A, 4 
    SET [A], 0xce00 
    
    SET PC, POP




; Reads register A and returns a number in register B that has that bit set. 
; Eg: A = 4, returns B = 0b1000
; Eg: A = 6, returns B = 0b100000
; If A > 16, B = -1 is retured 
:set_bit
    IFG A, 16
        SET B, -1 
    IFG A, 16 
        SET PC, set_bit_end 
    
    SET B, 1
    SHL B, A
    :set_bit_end 
    SET PC, POP

; Returns the rightmost bit in register A and returns it in register B. 
; Eg: A = 0xc => B = 2 
; If there is no ones in A, -1 will be returned. 
:get_rightmost_bit 
    IFG A, 0 
        SET B, -1 
    IFG A, 0 
        SET PC, get_rightmost_bit_end 
    
    SET B, -1 
    
    :get_rightmost_bit_end
    SET PC, POP


; Clears an area in the memory. Takes the start position in the A register and 
; the number of elements that should be cleared in the B register. 
:clear_mem 
    IFE B, 0 
        SET PC, POP 
    SET [A], 0 
    ADD A, 1
    SUB B, 1 
    SET PC, clear_mem 
    
    
    