|
Wristapp Programming
Reference |
Wristapp Programming
Tutorials |
|
|
|
|
|
|
More Random Numbers and Marquis - PICK6 example
Philip Hudnott <Philip.hudnott@btinternet.com> came up with this idea
for a wristapp to pick lottery numbers. Overall, this is pretty simple wristapp
to write, but it really showed the need for a decent random number generator.
Fortunately, Alan Beale <biljir@pobox.com> provided me with a great
MWC (multiply-with-carry) algorithm. Feel free to use the random number generator
for other programs, it has some pretty good behaviour. Overall, this program
has very little changes from the 3BALL example, so getting into it should
be pretty easy.
;Name: PICK6
;Version: PICK6
;Description: A sample lottery number picker to pick 6 numbers out of a pool of 49 numbers (no duplicates allowed).
; To use it, just select it as the current app and it will pick a set of 6 numbers for you. To get another set,
; just press the next button. This is for amusement only (but if you win anything because of it, I would welcome
; anything that you send me).
;
;by John A. Toebes, VIII
;
;HelpFile: watchapp.hlp
;HelpTopic: 100
;****************************************************************************************
;* Copyright (C) 1997 John A. Toebes, VIII *
;* All Rights Reserved *
;* This program may not be distributed in any form without the permission of the author *
;* jtoebes@geocities.com *
;****************************************************************************************
; (1) Program specific constants
;
INCLUDE "WRISTAPP.I"
;
; Program specific constants
;
RAND_RANGE EQU 48 ; This is the number of items to select from (1 to RAND_RANGE+1)
CURRENT_TIC EQU $27 ; Current system clock tic (Timer)
RAND_WCL EQU $61
RAND_WCH EQU $62
RAND_WNL EQU $63
RAND_WNH EQU $64
THIS_PICK EQU $65 ; We can share this with MARQ_POS since we don't do both at the same time
MARQ_POS EQU $65
TEMPL EQU $66
TEMPH EQU $67
START EQU *
BASE_TAB EQU $FE
;
; (2) System entry point vectors
;
L0110: jmp MAIN ; The main entry point - WRIST_MAIN
L0113: bclr 1,BTNFLAGS ; Called when we are suspended for any reason - WRIST_SUSPEND
rts
L0116: jmp FLASH ; Called to handle any timers or time events - WRIST_DOTIC
L0119: bclr 1,BTNFLAGS ; Called when the COMM app starts and we have timers pending - WRIST_INCOMM
rts
L011c: rts ; Called when the COMM app loads new data - WRIST_NEWDATA
nop
nop
L011f: lda STATETAB,X ; The state table get routine - WRIST_GETSTATE
rts
L0123: jmp HANDLE_STATE0
db STATETAB-STATETAB
;
; (3) Program strings
;
S6_MARQ timex6 " +O+ "
S8_TITLE timex " PICK-6 "
MARQ_SEL
DB S6_MARQ+2-START
DB S6_MARQ+3-START
DB S6_MARQ+2-START
DB S6_MARQ+1-START
DB S6_MARQ-START
DB S6_MARQ+1-START
;
; (4) State Table
;
STATETAB:
db 0
db EVT_ENTER,TIM2_16TIC,0 ; Initial state
db EVT_RESUME,TIM_ONCE,0 ; Resume from a nested app
db EVT_DNNEXT,TIM2_16TIC,0 ; Next button
db EVT_TIMER2,TIM_ONCE,0 ; Timer
db EVT_MODE,TIM_ONCE,$FF ; Mode button
db EVT_END
PICK_VALS db 0,0,0,0,0,0,0,$FF
;
; (5) This flashes the text on the screen
;
FLASH
lda CURRENT_APP ; See which app is currently running
cmp #APP_WRIST ; Is it us?
bne L0113 ; No, so just turn off the tic timer since we don't need it
ldx #5
lda MARQ_POS
jsr INCA_WRAPX
sta MARQ_POS
tax
lda MARQ_SEL,X
jsr PUT6MID
ldx MARQ_POS
lda MARQ_SEL,X
jmp PUT6TOP
;
; (6) They want us to do it again
;
DOITAGAIN ; Tell them we are going to do it again
clr MARQ_POS
bset 1,BTNFLAGS
jsr CLEARALL
jmp BANNER
;
; (7) State Table 0 Handler
; This is called to process the state events.
; We see ENTER, RESUME, TIMER2 and NEXT events
;
HANDLE_STATE0:
bset 1,APP_FLAGS ; Indicate that we can be suspended
bclr 1,BTNFLAGS
lda BTNSTATE
cmp #EVT_DNNEXT ; Did they press the next button?
beq DOITAGAIN
cmp #EVT_ENTER ; Or did we start out
beq DOITAGAIN
cmp #EVT_RESUME
beq REFRESH
;
; (8) Select a random answer
;
SHOWIT
clra
ldx #6
CLEARIT
sta PICK_VALS-1,X
decx
bne CLEARIT
;
; We want to pick 6 random numbers. The first needs to be in the range 1 ... RAND_RANGE
; The second should be in the range 1 ... (RAND_RANGE-1)
; The third should be in the range 1 ... (RAND_RANGE-2)
; The fourth should be in the range 1 ... (RAND_RANGE-3)
; The fifth should be in the range 1 ... (RAND_RANGE-4)
; The sixth should be in the range 1 ... (RAND_RANGE-5)
;
clr THIS_PICK
ONE_MORE_PICK
REPICK
jsr RAND16
and #63
sta TEMPL
lda #RAND_RANGE
sub THIS_PICK
cmp TEMPL
blo REPICK
lda TEMPL
bsr INSERT_NUM
inc THIS_PICK
lda THIS_PICK
cmp #6
bne ONE_MORE_PICK
bra REFRESH
;
; (9) Insert a number in the list
;
INSERT_NUM
inca
ldx #(PICK_VALS-1)-BASE_TAB ; Index so that we can use the short addressing mode
TRY_NEXT
incx ; Advance to the next number
tst BASE_TAB,X ; Is it an empty slot?
bne NOT_END ; No, try some more
sta BASE_TAB,X ; Yes, just toss it in there
rts ; And return
NOT_END
cmp BASE_TAB,X ; Non-empty slot, are we less than it?
blo PUT_HERE ; Yes, so we go here
inca ; No, Greater than or equal, we need to increment one and try again
bra TRY_NEXT
PUT_HERE
sta TEMPL
lda BASE_TAB,X
sta TEMPH
lda TEMPL
sta BASE_TAB,X
lda TEMPH
incx
tsta
bne PUT_HERE
rts
;
; (10) Display the currently selected random numbers
;
REFRESH
ldx PICK_VALS
bsr GOFMTX
jsr PUTTOP12
ldx PICK_VALS+1
bsr GOFMTX
jsr PUTTOP34
ldx PICK_VALS+2
bsr GOFMTX
jsr PUTTOP56
ldx PICK_VALS+3
bsr GOFMTX
jsr PUTMID12
ldx PICK_VALS+4
bsr GOFMTX
jsr PUTMID34
ldx PICK_VALS+5
bsr GOFMTX
jsr PUTMID56
lda #ROW_MP23
sta DISP_ROW
bset COL_MP23,DISP_COL
lda #ROW_MP45
sta DISP_ROW
bset COL_MP45,DISP_COL
lda #ROW_TP23
sta DISP_ROW
bset COL_TP23,DISP_COL
lda #ROW_TP45
sta DISP_ROW
bset COL_TP45,DISP_COL
BANNER
lda #S8_TITLE-START ; And show the mode on the bottom
jmp BANNER8
GOFMTX JMP FMTX
; (11) Here is an excellent random number generator
; it comes courtesy of Alan Beale <biljir@pobox.com%gt;
; The following C code gives a good MWC (multiply-with-carry)
; generator. This type is generally superior to linear
; congruential generators. As a bonus, there is no particular advantage to using the high-order
; rather than the low-order bits.
; The algorithm was developed and analyzed by George
; Marsaglia, a very well-known scholar of random number lore.
;
; The code assumes 16 bit shorts and 32 bit longs (hardly surprising).
;
;static unsigned short wn,wc; /* random number and carry */
;
;unsigned short rand() {
; unsigned long temp;
; temp = 18000*wn + wc;
; wc = temp >> 16;
; wn = temp & 0xffff;
; return wn;
;}
;
;To seed, set wn to anything you like, and wc to anything between 0 and 17999.
;
; Translating this into assembler is
;nHnL*0x4650 + RAND_WCHcL
;
; unsigned long temp;
; temp = 18000*wn + wc;
; wc = temp >> 16;
; wn = temp & 0xffff;
; return wn;
; temp = 0x4650 * n + c
; temp = 0x4650 * nHnL + cHcL
; temp = (0x4600 + 0x50) * (nH00 + nL) + cHcL
; temp = 0x4600*nH00 + 0x4600*nL + 0x50*nH00 + 0x50*nL + cHcL
; temp = 0x46*nH*0x10000 + 0x46*nL*0x100 + 0x50*nH*0x1000 + 0x50*nL + cHcL
; We construct the 32bit result into tH tL cH cL and then swap the 16 bit values
; once we have no more need of the original numbers in the calculation
;
RAND_MULT EQU 18000 ; This is for the random number generator
RAND_MULTH EQU RAND_MULT/256
RAND_MULTL EQU RAND_MULT&255
RAND16
lda RAND_WNL ; A=nL
ldx RAND_MULTL ; X=0x50
mul ; X:A = 0x50*nL
add RAND_WCL ; A=Low(0x50nL)+cL
sta RAND_WCL ; cL=Low(0x50nL)+cL
txa ; A=High(0x50nL)
adc RAND_WCH ; A=High(0x50nL)+cH
sta RAND_WCH ; cH=High(0x50nL)+cH
clra ; A=0
sta TEMPH ; tH=0
adc #0 ; A=Carry(0x50nL)+cH
sta TEMPL ; tL=Carry(0x50nL)+cH
lda RAND_WNL ; A=nL
ldx RAND_MULTH ; X=0x46
bsr RAND_SUB ; tL:cH += 0x46*nL tH=carry(0x46*nL)
lda RAND_WNH ; A=nH
ldx RAND_MULTL ; X=0x50
bsr RAND_SUB ; tL:cH += 0x50*nH tH=carry(0x50*nH)
lda RAND_WNH ; A=nH
ldx RAND_WCL ; X=cL
stx RAND_WNL ; nL=cL
ldx RAND_WCH ; X=cH
stx RAND_WNH ; hH=cH
ldx RAND_MULTH ; X=0x46
mul ; X:A=0x46*nH
add TEMPL ; A=Low(0x46*nH)+tL
sta RAND_WCL ; nL=Low(0x46*nH)+tL
txa ; A=High(0x46*nH)
adc TEMPH ; A=High(0x46*nH)+tH
sta RAND_WCH ; nH=High(0x46*nH)+tH
rts
RAND_SUB
mul ; Compute the values
add RAND_WCH ; A=LOW(result)+cH
sta RAND_WCH ; cH=Low(result)+cH
txa ; X=High(result)
adc TEMPL ; X=High(result)+tL+Carry(low(result)+cH)
sta TEMPL ; tL=High(result)+tL+Carry(low(result)+cH)
clra ; A=0
adc TEMPH ; A=carry(High(result)+tL+Carry(low(result)+cH))+tH
sta TEMPH ; tH=carry(High(result)+tL+Carry(low(result)+cH))+tH
rts
;
; (12) This is the main initialization routine which is called when we first get the app into memory
;
MAIN:
lda #$c0 ; We want button beeps and to indicate that we have been loaded
sta WRISTAPP_FLAGS
lda CURRENT_TIC
sta RAND_WNL
sta RAND_WNH
sta RAND_WCL
and #$3f
sta RAND_WCH
rts
-
Program specific constants - We have several variables
- RAND_WCL, RAND_WCH, RAND_WNL and RAND_WNH which we use for the random number
routine. CURRENT_TIC is what is set by the system when it reads the clock
to keep the watch time up to date. We use it once to provide a seed
for the random number generator. Note that we are overlapping the use of
THIS_PICK and MARQ_POS to save one byte of low ram.
-
System entry point vectors - identical to the 3BALL
example, This one gets to be a little fun. Notice for the
WRIST_SUSPEND and
WRIST_INCOMM routines that
we don't have a JMP instruction, but instead put the actual code in line.
This saves use a couple of bytes.
-
Program strings - We are pretty fruegal here in reusing
blanks at the end of the string very liberally. Also note the S6_MARQ
string which has blanks at the start and end so that it can shuffle left
and right on the display but always have blanks visible. The MARQ_SEL
and MSG_SEL tables are simply offsets that allow us to select the message
with a simple load instruction instead of having to caluclate the offset.
-
State Table - This is pretty vanilla here except for
the fact that we have a very long time interval after the DNNEXT and ENTER
events. It is during this time that the Marquis runs. We could
make it even longer, but this seems to be a good compromise between seeing
something happen and actually getting a result in a resonable time.
-
State Table 0 Handler - Extremely simple, there are
only four events that we want to see and this is the typical test and branch
one. THe only unique thing here is that we turn off the Marquis timer
as soon as we get any event.
-
This flashes the text on the screen - This is the
cheap way to do a Marquis. Just have a string wider than the display
and change the offset from the start at which you start to display. For
this one, there are only 6 states and we select the starting offset from
the table based on our current cycle. Note that this routine is called
by the TIC timer which is enabled when they want a new random number.
Eventually the timer for the main event will run out and they will
simply stop calling us.
-
They want us to do it again - Whenever we want to
do a new random number, we just start the Marquis tic timer and set up the
display.
-
Select a random answer - This is really the meat of
this wristapp. We need to pick 6 random numbers and sort them. Fortunately,
we can take advantage of the sorting as part of our random number selection.
-
Insert a number in the list - Given a random number,
add it to the list of random numbers in sorted order. Essentially, we start
at the begining of the list and go until we either find a slot where we need
to insert the number in order or we hit the end of the list. If we hit the
end of the list, we store the number there and return. Otherwise we insert
the number at the appropriate spot. One additional thing that we do is increment
the number by 1 for each entry in the that is less than it. It makes sense,
but you need to think about why this works.
-
Display the currently selected random numbers - Given
a the 6 random numbers, we just put them on the display separated by periods.
Note the series of BSR unstructions to the GOFMTX label. Since there were
6 calls to it, we were about to reduce the 6 3-byte instructions to 6 2-byte
instructions plus one 3-byte instruction to do the call for a savings of
3 bytes.
-
Here is a random number generator - This is great
random number generator that you might want to grab for any other code that
you might write.
-
This is the main initialization routine which is called
when we first get the app into memory - Very boring stuff here, but we
do take a moment to initialize the random number seed with the current tic
count just to make it a little more variable.
|