


;; v 0.2 TriMounts, (c) 1996 Josh Pieper
;; ppieper@nemonet.com
;; Feel free to distribute this file, just don't change it
;; in any way.

;; This code is horribly unoptimized and in future versions
;; I will condense it a bit.

;; Some of the comments are outdated, I will fix them
;; when I have some time.

;; Variables, SEED is for the Random Number Engine
;; CARDS is the deck of cards
;; CINDEX is the index to the top card of the deck
;; PEAK is the matrix holding the cards in trimounts positions
;; MONEY is a count of your current money
;; PILE has the current card on top of the pile
;; STREAK has the number of hits in a row w/o pressing enter
;; PKS has the number of peaks hit, used to determine final amount
;; TEMPSTR is used to display the amount of money

#include "ti-85.h"

randvar = $80DF
TEMP =  $80E1
CARDS = $80E3
CINDEX = $8117
MINDEX = $8119
;; 4x23 matrix for storing cards
PEAK   = $811B
PILE   = $8179
STREAK = $817B
PKS    = $817C
CL     = $817D
TEMP2  = $817E
TEMP3  = $8180
TEMPSTR = $8183


.org 0
.db "Josh",39,"s TriMounts v0.2",0

;; Clear screen and display title

  ld hl,$8C40
  set 0,(hl)             ; Allow for permanent variables

  ROM_CALL(CLEARLCD)          ;; Clear the screen
  ld hl,(PROGRAM_ADDR)        ;; Display title screen
  ld de,Title1           ;; Address of strings stored in hl
  add hl,de
  ld a,0
  ld (CURSOR_ROW),a
  ld a,2
  ld (CURSOR_COL),a
  ROM_CALL(D_ZT_STR)          ;; Rom call to display zero terminated string

  ld a,2
  ld (CURSOR_COL),a
  ld a,7
  ld (CURSOR_ROW),a
  ROM_CALL(D_ZT_STR)          ;; Last call automaticly increased hl to next str

  ld a,3
  ld (CURSOR_ROW),a
  ld a,2
  ld (CURSOR_COL),a
  ROM_CALL(D_ZT_STR)
  
  ld a,4
  ld (CURSOR_ROW),a
  ld a,1
  ld (CURSOR_COL),a
  ROM_CALL(D_ZT_STR)

  ld a,6
  ld (CURSOR_ROW),a
  ld a,1
  ld (CURSOR_COL),a
  ROM_CALL(D_ZT_STR)
;; Wait for enter to be pressed
;; This is where we will get our seed for the random number engine.
  
  ld bc,$0
KeyWait1:  
  call GET_KEY           ; Wait for key
  inc bc
  cp 54
  jr z,ClrStats               ; Counter is incremented for each pass of the
  cp $9                  ; loop and is used as the seed for the random
  jr z, EnterPressed          ; number generator
  jr KeyWait1

ClrStats:
  ld hl,(PROGRAM_ADDR)
  ld de,HIGHSTRK
  add hl,de
  ld a,0
  ld (hl),0
  ld hl,0
  CALL_(PutMoney)
  jr KeyWait1

EnterPressed:
  ld a,c
  ld (randvar),a

;; Fill the deck with four of each card
;; This is done with a nested loop
  ROM_CALL(CLEARLCD)          ; Display status messaqe
  ld a,7
  ld (CURSOR_ROW),a
  ld a,2
  ld (CURSOR_COL),a
  ld hl,(PROGRAM_ADDR)
  ld de,Msg1
  add hl,de
  ROM_CALL(D_ZT_STR)

  ld hl,CARDS
  ld a,13
ODealLp:  
  dec a
  ld b,4
IDealLp:
  ld (hl),a
  inc hl
  djnz IDealLp
  cp $0
  jr nz, ODealLp

;; This code was a last minute addition to allow the player
;; to see their money without actually having to start a
;; game

  ld a,51
  ld (CINDEX),a
  ld a,0
  ld (CL),a
  ROM_CALL(CLEARLCD)
  JUMP_(DisplayStats)

NewGame:
  ld a,30
  ld (CINDEX),a                ;Set the Deck counter
					;This is where we go to start new game.
  ld (CL),a
  ld b,92           ;Fill the PEAK matrix with 13's
  ld hl,PEAK
FlLoop:
  ld (hl),13
  inc hl
  djnz FlLoop

;; Now that the deck is filled we must shuffle it.  
  ld b,255               ; Shuffling is done by the following TI-BASIC
ShufLp:                  ; code: rand->a
  push bc           ; rand->b
  CALL_(Random)               ; CARDS(A)->temp
  ld b,h            ; CARDS(B)->CARDS(A)
  ld c,l            ; temp->CARDS(B)
  CALL_(Random)               ; This is repeated 255 times to get a good
  ld d,h            ; shuffle.
  ld e,l
  ld hl,CARDS
  add hl,bc
  ld a,(hl)
  ld hl,CARDS
  add hl,de
  ld d,(hl)
  ld (hl),a
  ld hl,CARDS
  add hl,bc
  ld (hl),d
  pop bc
  djnz ShufLp

;; Deal the deck into the PEAK matrix.
;; bc will be the index for the Tpos array and for the CARDS list
;; when it == 30 time to quit
  ld a,14           ; This uses a list to determine which indices
  ld (TEMP),a            ; of the PEAKS matrix get cards.  It just
  ld bc,0           ; runs through the list and puts the next
DealLoop:                ; card in the appropriate cell.
  ld hl,CARDS            ; 14 is added to the first 18 cards so that they
  add hl,bc              ; appear as #'s on the screen. The 13's we
  ld a,(hl)              ; filled in earlier appear as spaces.
  ld hl,TEMP
  add a,(hl)
  ld hl,(PROGRAM_ADDR)
  ld de,TPos
  add hl,de
  add hl,bc
  ld e,(hl)
  ld d,0
  ld hl,PEAK
  add hl,de
  ld (hl),a
  inc bc
  ld a,18
  cp c
  jr nz, SkipChng
  ld a,0
  ld (TEMP),a
SkipChng:
  ld a,30
  cp c
  jr nz,DealLoop

;; Display the deck in TriMounts format
;; the X and Y pos are in the temp variable 
;; de is the index to PEAK
;; b is a counter of the number of pos's to be read
;; should be displayed
  ROM_CALL(CLEARLCD)         ;Clear the screen

  ld b,5
  ld c,6
  ld (TEMP),bc
  ld b,92           ; cycle through the matrix indicing its contents
  ld de,0           ; with the Nums string so that the cards appear
AnDispLoop:              ; as A-K
  push bc
  ld hl,PEAK
  add hl,de
  ld a,(hl)
  ld hl,(PROGRAM_ADDR)
  ld bc,Nums
  add hl,bc
  ld c,a
  ld b,0
  add hl,bc
  ld bc,(TEMP)
  ld a,b
  ld (CURSOR_X),a
  ld a,c
  ld (CURSOR_Y),a
  ld b,1
  ROM_CALL(D_LM_STR)
SkipDisp:
  inc de
  ld bc,(TEMP)
  ld a,b
  add a,5
  ld b,a
  cp $78
  jr nz, BefKeyWait4
  ld b,5
  ld a,c
  add a,6
  ld c,a
BefKeyWait4:
  ld (TEMP),bc
  pop bc
  djnz AnDispLoop
  ld a,0
  ld (STREAK),a
  ld (PKS),a
  ld c,4                    ; c hold the current x pos of pointer
  ld hl,CARDS
  ld a,(CINDEX)
  ld e,a
  ld d,0
  add hl,de
  ld a,(hl)
  ld (PILE),a
  
DisplayStats:
  push bc                ; save current x pos
;; Now we'll show the rest of the Screen
  ld a,44           ; Display the stats at the bottom
  ld (CURSOR_Y),a        ; As well as the reminder on keys.
  ld a,10
  ld (CURSOR_X),a
  ld hl,(PROGRAM_ADDR)        ; Display "Money:"
  ld de,Scrn1
  add hl,de
  ROM_CALL(D_ZM_STR)         ;Display zero-string, menu

  ld a,44
  ld (CURSOR_Y),a        ; Display "Pile:"
  ld a,67
  ld (CURSOR_X),a
  ROM_CALL(D_ZM_STR)

  ld a,58
  ld (CURSOR_Y),a
  ld a,10           ; Display help at bottom
  ld (CURSOR_X),a
  ROM_CALL(D_ZM_STR)

  ld a,51
  ld (CURSOR_Y),a        ; Display "Cur. Streak:"
  ld a,10
  ld (CURSOR_X),a
  ROM_CALL(D_ZM_STR)

  ld a,51
  ld (CURSOR_Y),a
  ld a,67           ; Display "Longest Streak:"
  ld (CURSOR_X),a
  ROM_CALL(D_ZM_STR)

  ld de,TEMPSTR
  ld a,(STREAK)
  ld l,a
  ld h,0
  CALL_(CONV_HL_STR)
  ld a,51
  ld (CURSOR_Y),a
  ld a,40
  ld (CURSOR_X),a
  ld b,6
  ld hl,TEMPSTR
  ROM_CALL(D_LM_STR)

  ld de,TEMPSTR
  ld hl,(PROGRAM_ADDR)
  ld bc,HIGHSTRK
  add hl,bc
  ld a,(hl)
  ld l,a
  ld h,0
  CALL_(CONV_HL_STR)
  ld a,51
  ld (CURSOR_Y),a
  ld a,100
  ld (CURSOR_X),a
  ld b,6
  ld hl,TEMPSTR
  ROM_CALL(D_LM_STR)

  CALL_(GetMoney)
  ld de,TEMPSTR               ; Use my CONV_HL_STR proc to convert hl to a

  CALL_(CONV_HL_STR)          ; outputted in menu text. (Otherwise I would
  ld a,40           ; have used D_HL_DECI)
  ld (CURSOR_X),a
  ld a,44
  ld (CURSOR_Y),a
  ld b,6
  ld hl,TEMPSTR
  ROM_CALL(D_LM_STR)         ;Display length-string, menu

  ld a,(PILE)
  ld e,a
  ld d,0
  ld hl,(PROGRAM_ADDR)
  ld bc,Nums
  add hl,bc
  add hl,de
  ld a,87
  ld (CURSOR_X),a
  ld a,44
  ld (CURSOR_Y),a
  ld b,1
  ROM_CALL(D_LM_STR)

;; Here is where we get the card that the user want to remove.
;; c holds the x pos of the pointer.

  pop bc            ;retrieve current xpos
GetInput:
  ld a,32
  ld (CURSOR_Y),a
  ld a,c
  ld (CURSOR_X),a
  ld a,6
  ld (TEMP),a
  ld b,1
  ld hl,TEMP
  ROM_CALL(D_LM_STR)
WaitForKey:
  CALL GET_KEY
  cp $0
  jr z, WaitForKey
  cp 55
  JUMP_Z(TentEGme)
  cp 2
  jr z,Move
  cp 3
  jr z,Move
  cp 4
  JUMP_Z(TryClrCard)
  cp 9
  JUMP_Z(PutCardOnPile)
  cp 54
  JUMP_Z(TentNGme)
  jr WaitForKey


;; Right here is where we erase the old pointer.
;; I was going to use direct memory access, but it kept
;; locking up the calculator!!
Move:
  push af
  ld a,32
  ld (CURSOR_Y),a
  ld a,c
  ld (CURSOR_X),a
  ld b,5
RemOld:
  ld a,32
  ROM_CALL(M_CHARPUT)
  djnz RemOld
  pop af
  cp 2
  jr z,Left
  cp 3
  jr z,Right

;; Now we change c to point to the new cursor.
Left:
  ld a,c
  sub 5
  ld c,a
  cp 255
  jr nz, GetInput
  ld c,114
  jr GetInput
Right:
  ld a,c
  add a,5
  ld c,a
  cp 119
  jr nz, GetInput
  ld c,4
  jr GetInput

TentNGme:
  CALL_(CancelGame)
  cp 0
  JUMP_Z(DisplayStats)
  ROM_CALL(CLEARLCD)
  JUMP_(NewGame)

TentEGme:
  CALL_(CancelGame)
  cp 0
  JUMP_Z(DisplayStats)
  ret
  
  
PutCardOnPile:
  push bc
  ld bc,5
  ld a,(CINDEX)
  inc a
  cp 52 
  jr nz, SkipChng2
  ld a,51
  ld bc,0
SkipChng2:
  ld (CINDEX),a
  ld e,a
  ld d,0
  ld hl,CARDS
  add hl,de
  ld a,(hl)
  ld (PILE),a
  CALL_(GetMoney)
  sbc hl,bc
  CALL_(PutMoney)
  ld a,0
  ld (STREAK),a
  pop bc
  JUMP_(DisplayStats)
  
TryClrCard:
  ld a,(CL)
  cp $0
  JUMP_Z(GetInput)
  push bc
  ld b,c
  inc b
  ld d,0
DivLoop:
  ld a,b
  ld b,4
  sbc a,b
  ld b,a
  inc d
  djnz DivLoop


  ld a,68
  add a,d
  ld e,a
  ld d,0
  ld hl,PEAK
  add hl,de
  ld de,23
  ld bc,$0500
  ld (TEMP),bc
  ld b,4
  ld a,5
ScanLoop:
  ld a,(hl)
  add a,243
  jr c,SkipSet
  ld (TEMP),bc
  ld (MINDEX),hl
  
SkipSet:
  scf
  ccf
  sbc hl,de
  djnz ScanLoop
  ld bc,(TEMP)
  ld a,b
  cp 5
  jr nz, PosGood
  pop bc
  JUMP_(GetInput)

PosGood:
  ld hl,(MINDEX)
  ld a,(hl)
  ld b,a
  ld a,(PILE)
  add a,0
  sub b
  cp 255
  jr z,Good
  cp 1
  jr z,Good
  cp 12
  jr z,Good
  cp 244
  jr z,Good
  pop bc
  JUMP_(GetInput)
Good:
; Check for the top of each peak
  ld a,(hl)
  ld (PILE),a
  ld bc,(TEMP)
  ld a,1
  cp b
  ld a,0
  ld (TEMP3),a
  jr nz, NotAPeak
  ld a,1
  ld (TEMP3),a
  ld a,(PKS)
  inc a
  ld (PKS),a
  cp 3
  jr nz, NotLastPeak
  
  ld de,30
  CALL_(GetMoney)
  add hl,de
  CALL_(PutMoney)
  jr NotAPeak
NotLastPeak:
  ld de,15
  CALL_(GetMoney)
  add hl,de
  CALL_(PutMoney)
NotAPeak:

  ld a,(CL)         ;Update Streaks,money, and cards left
  dec a
  ld (CL),a
  ld a,(STREAK)
  inc a
  ld (STREAK),a
  ld hl,(PROGRAM_ADDR)
  ld de,HIGHSTRK
  add hl,de
  ld d,a
  ld a,(hl)
  ld e,a
  ld a,255
  add a,0
  sub e
  add a,d
  jr nc,NotHighStreak
  ld a,(STREAK)
  ld (hl),a
NotHighStreak:
  ld a,(STREAK)
  ld e,a
  ld d,0
  CALL_(GetMoney)
  add hl,de
  CALL_(PutMoney)

  ld hl,(MINDEX)    ;Set PEAKS as visited this card
  ld (hl),13

  ld bc,(TEMP)      ;Blank out card on screen
  ld e,6
  ld d,0
  CALL_(Mult)
  ld a,l
  ld (CURSOR_Y),a
  pop bc
  ld a,c
  push bc
  ld (CURSOR_X),a
  ld b,5
ClearChar:
  ld a,32
  ROM_CALL(M_CHARPUT)
  djnz ClearChar

  ld a,(TEMP3)           ; If card was at top
  cp 1              ; there won't be any cards above
  JUMP_Z(DoneChecking)   ; to check

;Now we have to check if card above can be cleared
  ld hl,(MINDEX)
  dec hl
  dec hl
  ld a,(hl)
  cp 13
  jr nz,CheckOnlyRight
  ld de,22
  add a,0
  sbc hl,de
  ld a,(hl)
  cp 13
  jr z,CheckOnlyRight
 
  sub 14
  ld (hl),a
  ld (TEMP2),hl
  ld bc,(TEMP)      ;Show new card out card on screen
  dec b
  ld e,6
  ld d,0
  CALL_(Mult)
  ld a,l
  ld (CURSOR_Y),a
  pop bc
  ld a,c
  sub 4
  ld (CURSOR_X),a
  push bc
  
  ld hl,(TEMP2)
  ld a,(hl)
  ld hl,(PROGRAM_ADDR)
  ld de,Nums
  add hl,de
  ld e,a
  ld d,0
  add hl,de
  ld a,(hl)
  ROM_CALL(M_CHARPUT)
CheckOnlyRight:
  ld hl,(MINDEX)
  inc hl
  inc hl
  ld a,(hl)
  cp 13
  jr nz,DoneChecking
  ld de,24
  add a,0
  sbc hl,de
  ld a,(hl)
  cp 13
  jr z,DoneChecking

  sub 14
  ld (hl),a
  ld (TEMP2),hl
  ld bc,(TEMP)      ;Show new card out card on screen
  dec b
  ld e,6
  ld d,0
  CALL_(Mult)
  ld a,l
  ld (CURSOR_Y),a
  pop bc
  ld a,c
  add a,5
  ld (CURSOR_X),a
  push bc
  
  ld hl,(TEMP2)
  ld a,(hl)
  ld hl,(PROGRAM_ADDR)
  ld de,Nums
  add hl,de
  ld e,a
  ld d,0
  add hl,de
  ld a,(hl)
  ROM_CALL(M_CHARPUT)
DoneChecking:  
  pop bc
  JUMP_(DisplayStats)

  
  


; Ask user if they want to cancel this game
; Will lose $5 for every card left on the board
; returns a=1 if cancel game is requested
; returns a=0 if cancel game is not requested
CancelGame:
  push bc
  push de
  push hl
  ld a,(CL)
  cp $0
  jr nz, NotZero
  ld a,1
  CALL_(ClearStatus)
  pop hl
  pop de
  pop bc
  ret
NotZero:
  CALL_(ClearStatus)
  ld hl,(PROGRAM_ADDR)
  ld de,AreSure
  add hl,de
  ld a,51
  ld (CURSOR_Y),a
  ld a,10
  ld (CURSOR_X),a
  ROM_CALL(D_ZM_STR)
  ld a,58
  ld (CURSOR_Y),a
  ld a,10
  ld (CURSOR_X),a
  ROM_CALL(D_ZM_STR)
  ld a,(CL)
  ld b,a
  ld e,5
  ld d,0
  CALL_(Mult)
  ld (TEMP),hl
  ld de,TEMPSTR
  CALL_(CONV_HL_STR)
  ld hl,TEMPSTR
  ld b,6
  ROM_CALL(D_LM_STR)
KeyLoop6:
  CALL GET_KEY
  cp 52
  jr nz, KeyLoop61
  ld a,0
  CALL_(ClearStatus)
  pop hl
  pop de
  pop bc
  ret
KeyLoop61:
  cp 53
  jr z, Sure_Yes
  jr KeyLoop6
Sure_Yes:
  ex de,hl
  CALL_(GetMoney)
  ld de,(TEMP)
  add a,0
  sbc hl,de
  CALL_(PutMoney)
  ld a,1
  CALL_(ClearStatus)
  pop hl
  pop de
  pop bc
  ret

;; This function clears the status area
ClearStatus:
  push bc
  push af
  push hl
  ld a,7
  ld (CURSOR_ROW),a
  ld a,0
  ld (CURSOR_COL),a
  ld b,3
  ld c,7
ClrLoop:

  ld hl,(PROGRAM_ADDR)
  ld de,Blank
  add hl,de
  ROM_CALL(D_ZT_STR)
  dec c
  ld a,c
  ld (CURSOR_ROW),a
  ld a,0
  ld (CURSOR_COL),a
  djnz ClrLoop
  pop hl
  pop af
  pop bc
  ret


;; This Generates a random number 0-51 in hl
;; It works by the following pseudocode
;; (SEED*57) % 9303 -> SEED
;; It then does SEED % 52 -> hl
;; This is built only for the shuffling routine, you
;; could change the last modulus routine for any number you
;; like, but I'm not sure that it would be very random
;; for large numbers.

;; (I took the C library's rand() routine and greatly simplified the
;; the numbers involved, all of them had a lot of prime number factors
;; and I just reduced them according to a simple pattern until I found
;; one that was semi-random)
		   
Random:
  
;; Save all registers
  push af
  push bc
  push de
  ;; I'm going to try and use someone else's random number generator for this
  ;; part, We'll see if it works.
#ifndef OLDRAND
  CALL_( rand)
  srl a
  ld l,a
  ld h,0
  jr Mod
#endif
#ifdef OLDRAND
  ld de,(SEED)
  ld b,57
  CALL_(Mult)
  ld bc,$dba8
  ld de,$2457
RandModLoop:
  add hl,bc
  scf
  ccf
  add hl,de
  jr c, RandDone
  sbc hl,de
  jr RandModLoop
RandDone:
  ld (SEED),hl

#endif
Mod:
  ld bc,$ffcc
  ld de,$34
RandModLoop2:
  add hl,bc
  scf
  ccf
  add hl,de
  jr c, RandDone2
  sbc hl,de
  jr RandModLoop2
RandDone2:
;; Restore all registers
  pop de
  pop bc
  pop af
  ret

;;This routine makes hl point to the correct value for (CINDEX)
;; It indexs through Nums to allow easier acces to the printable
;; value for each card.
GetCard:
  push bc
  push de
  ld a,(CINDEX)
  ld c,a
  ld b,0
  ld hl,CARDS
  add hl,bc
  ld a,(hl)
  ld hl,(PROGRAM_ADDR)
  ld de,Nums
  add hl,de
  ld c,a
  ld b,0
  add hl,bc
  pop de
  pop bc
  ret

;; This Routine Converts HL to a string at the location de
CONV_HL_STR:

  push af
  push bc
  push hl
  ex de,hl
  ld a,'+'
  rl d
  jr nc,BefConvLoop
  ld a,'-'
BefConvLoop:
  ld (hl),a
  scf
  ccf
  rr d
  ex de,hl
  cp '-'
  jr nz, SkipAdjust
  push hl
  pop bc
  ld hl,32768
  sbc hl,bc
SkipAdjust:  
  inc de
  inc de
  inc de
  inc de
  inc de
  ld b,5
  
ConvLoop:
  call UNPACK_HL
  add a,'0'
  ld (de),a
  dec de
  djnz ConvLoop
  pop hl
  pop bc
  pop af
  ret

;; This puts hl in the Money Variable
PutMoney
  push bc
  push de
  push hl
  pop bc
  ld hl,(PROGRAM_ADDR)
  ld de,MONEY
  add hl,de
  ld (hl),b
  inc hl
  ld (hl),c
  pop de
  pop bc
  ret

;; This gets the amount of money and puts it in hl
GetMoney:
  push bc
  push de
  ld hl,(PROGRAM_ADDR)
  ld de,MONEY
  add hl,de
  ld b,(hl)
  inc hl
  ld c,(hl)
  push bc
  pop hl
  pop de
  pop bc
  ret

;; this multiplies b * de -> hl
;; just make sure it won't overflow hl
;; It works just by 0->hl 
;; then hl+de->hl, for b number oftimes

Mult:
  push af
  ld a,0
  cp b
  jr z,EqualsZero
  push bc
  ld hl,0
MultLoop:
  add hl,de
  djnz MultLoop
  pop bc
  pop af
  ret
EqualsZero:
  pop af
  ld hl,0
  ret 

rand:
    ld    a,(randvar)  ;;must be defined in text area
    ld    b,a
    ld    a,0
    add   a,b
    sla   b
    sla   b
    add   a,b
    sla   b
    sla   b
    add   a,b
    inc   a
    ld    (randvar),a
    srl   a
    ret
;;end rand


TPos:   .db 3,11,19,25,27,33,35,41,43,47,49,51,55,57,59,63,65,67
	   .db 69,71,73,75,77,79,81,83,85,87,89,91
Title1: .db "Josh",39,"s TriMounts"
	   .db "   (c) 1996 Josh Pieper",0
Title2: .db "Enter to Start!",0
Title3: .db "info: mail me at",0
Title4: .db "ppieper@nemonet.com",0
Title5: .db "2nd to reset stats",0
Msg1:   .db "Shuffling deck!",0
Nums:   .db "A23456789TJQK +++++++++++++"
Nums2:  .db "#"
Scrn1:  .db "Money:$",0
Scrn2:  .db "Pile:",0
Scrn3:  .db "Ent ",45," new card    "
	   .db "2nd ",45," new game",0
Scrn4:  .db "Streak:",0
Scrn5:  .db "High Strk:",0
AreSure: .db "Are you sure? (Y-F1/N-F2)",0
	    .db "You will lose:$",0
Blank:   .db "                    ",0
HIGHSTRK: .db 0
MONEY:    .dw 0
.END
