got the number guessing example fully working on c64 asm

This commit is contained in:
Irmen de Jong
2018-12-11 00:09:37 +01:00
parent a499ac6def
commit be819ba8a7
9 changed files with 142 additions and 68 deletions

View File

@@ -2,15 +2,21 @@
%import c64lib %import c64lib
%import c64utils %import c64utils
; The classic number guessing game.
; This version uses more low-level subroutines (calls directly into the C64's ROM routines)
; and instead of a loop (with the added behind the scenes processing), uses absolute jumps.
; It's less readable I think, but produces a smaller program.
~ main { ~ main {
sub start() { sub start() {
str name = "????????????????????????????????????????" str name = "????????????????????????????????????????"
str guessstr = "??????????" str input = "??????????"
ubyte guess ubyte guess
ubyte secretnumber = 0 ubyte secretnumber = 0
ubyte attempts_left = 10 ubyte attempts_left = 10
memory uword freadstr_arg = $22 ; argument for FREADSTR memory uword freadstr_arg = $22 ; argument for FREADSTR ($22/$23)
uword testword
; greeting ; greeting
c64.VMCSB |= 2 ; switch lowercase chars c64.VMCSB |= 2 ; switch lowercase chars
@@ -23,7 +29,7 @@
c64.STROUT(".\nLet's play a number guessing game.\nI am thinking of a number from 1 to 100!You'll have to guess it!\n") c64.STROUT(".\nLet's play a number guessing game.\nI am thinking of a number from 1 to 100!You'll have to guess it!\n")
; create a secret random number from 1-100 ; create a secret random number from 1-100
c64.RND() ; fac = random number c64.RND() ; fac = random number between 0 and 1
c64.MUL10() ; fac *= 10 c64.MUL10() ; fac *= 10
c64.MUL10() ; .. and now *100 c64.MUL10() ; .. and now *100
c64.FADDH() ; add 0.5.. c64.FADDH() ; add 0.5..
@@ -35,30 +41,28 @@ ask_guess:
c64.STROUT("\nYou have ") c64.STROUT("\nYou have ")
c64scr.print_byte_decimal(attempts_left) c64scr.print_byte_decimal(attempts_left)
c64.STROUT(" guess") c64.STROUT(" guess")
if(attempts_left>0) c64.STROUT("es") if(attempts_left>1)
c64.STROUT("es")
c64.STROUT(" left.\nWhat is your next guess? ") c64.STROUT(" left.\nWhat is your next guess? ")
Y=c64scr.input_chars(guessstr) Y=c64scr.input_chars(input)
c64.CHROUT('\n') c64.CHROUT('\n')
freadstr_arg = guessstr freadstr_arg = input
c64.FREADSTR(Y) c64.FREADSTR(Y)
A, Y = c64flt.GETADRAY() A, Y = c64flt.GETADRAY()
guess=A guess=A
c64.EXTCOL=guess ; @debug
c64.BGCOL0=secretnumber ;@debug
if(guess==secretnumber) { if(guess==secretnumber) {
c64.STROUT("\nThat's my number, impressive!\n") c64.STROUT("\nThat's my number, impressive!\n")
goto goodbye goto goodbye
} }
c64.STROUT("\nThat is too ") c64.STROUT("\nThat is too ")
if(guess > secretnumber) if(guess < secretnumber)
c64.STROUT("low!\n") c64.STROUT("low!\n")
else else
c64.STROUT("high!\n") c64.STROUT("high!\n")
attempts_left-- attempts_left--
if(attempts_left>0) goto ask_guess if_nz goto ask_guess
; more efficient: if_nz goto ask_guess
; game over. ; game over.
c64.STROUT("\nToo bad! It was: ") c64.STROUT("\nToo bad! It was: ")

View File

@@ -1,12 +1,16 @@
%import c64utils %import c64utils
%import mathlib %import mathlib
; The classic number guessing game.
; This version uses mostly high level subroutine calls and loops.
; It's more readable than the low-level version, but produces a slightly larger program.
~ main { ~ main {
sub start() { sub start() {
str name = "????????????????????????????????????????" str name = "????????????????????????????????????????"
str guess = "??????????" str input = "??????????"
ubyte secretnumber = rnd() % 100 ubyte secretnumber = rnd() % 99 + 1 ; random number 1..100
c64.VMCSB |= 2 ; switch lowercase chars c64.VMCSB |= 2 ; switch lowercase chars
c64scr.print_string("Please introduce yourself: ") c64scr.print_string("Please introduce yourself: ")
@@ -17,50 +21,67 @@
for ubyte attempts_left in 10 to 1 step -1 { for ubyte attempts_left in 10 to 1 step -1 {
; stackptr debugging
c64scr.print_byte_decimal(X) ; c64scr.print_byte_decimal(X)
c64.CHROUT('\n') ; c64.CHROUT('\n')
c64scr.print_string("\nYou have ") c64scr.print_string("\nYou have ")
c64scr.print_byte_decimal(attempts_left) c64scr.print_byte_decimal(attempts_left)
c64scr.print_string(" guess") c64scr.print_string(" guess")
if attempts_left>1 c64scr.print_string("es") if attempts_left>1
c64scr.print_string("es")
c64scr.print_string(" left.\nWhat is your next guess? ") c64scr.print_string(" left.\nWhat is your next guess? ")
c64scr.input_chars(guess) c64scr.input_chars(input)
ubyte guessednumber = str2ubyte(guess) ubyte guess = str2ubyte(input)
; debug info ; debug info
c64scr.print_string(" > attempts left=") ; c64scr.print_string(" > attempts left=")
c64scr.print_byte_decimal(attempts_left) ; c64scr.print_byte_decimal(attempts_left)
c64scr.print_string("\n > secretnumber=") ; c64scr.print_string("\n > secretnumber=")
c64scr.print_byte_decimal(secretnumber) ; c64scr.print_byte_decimal(secretnumber)
c64scr.print_string("\n > guess=") ; c64scr.print_string("\n > input=")
c64scr.print_string(guess) ; c64scr.print_string(input)
c64scr.print_string("\n > guessednumber=") ; c64scr.print_string("\n > guess=")
c64scr.print_byte_decimal(guessednumber) ; c64scr.print_byte_decimal(guess)
c64.CHROUT('\n') ; c64.CHROUT('\n')
c64scr.print_byte_decimal(X) ; c64scr.print_byte_decimal(X) ; stackptr debugging
c64.CHROUT('\n') ; c64.CHROUT('\n')
if guess==secretnumber {
if guessednumber==secretnumber { ending(true)
c64scr.print_string("\n\nYou guessed it, impressive!\n") return ; @todo make return ending(true) actually work as well
c64scr.print_string("Thanks for playing, ")
c64scr.print_string(name)
c64scr.print_string(".\n")
return
} else { } else {
c64scr.print_string("\n\nThat is too ") c64scr.print_string("\n\nThat is too ")
if guessednumber<secretnumber if guess<secretnumber
c64scr.print_string("low!\n") c64scr.print_string("low!\n")
else else
c64scr.print_string("high!\n") c64scr.print_string("high!\n")
} }
} }
c64scr.print_string("\nToo bad! My number was: ") ; return 99 ;@todo error message (no return values)
c64scr.print_byte_decimal(secretnumber) ; return 99,44 ;@todo error message (no return values)
c64scr.print_string(".\n") ; return ending(false) ; @todo fix this, actuall needs to CALL ending even though no value is returned
return
ending(false)
return ; @todo make return ending(false) actually work as well
sub ending(success: ubyte) {
if success
c64scr.print_string("\n\nYou guessed it, impressive!\n")
else {
c64scr.print_string("\nToo bad! My number was: ")
c64scr.print_byte_decimal(secretnumber)
c64scr.print_string(".\n")
}
c64scr.print_string("Thanks for playing, ")
c64scr.print_string(name)
c64scr.print_string(".\n")
; return 99 ; @todo error message (no return values)
; return 99,44 ; @todo error message (no return values)
; return 99,44 ; @todo should check number of return values!!
}
} }
} }

View File

@@ -260,7 +260,7 @@ private class StatementTranslator(private val prog: IntermediateProgram,
DataType.FLOAT -> Opcode.PUSH_VAR_FLOAT DataType.FLOAT -> Opcode.PUSH_VAR_FLOAT
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS, DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS,
DataType.ARRAY_UB, DataType.ARRAY_UW, DataType.ARRAY_F, DataType.ARRAY_UB, DataType.ARRAY_UW, DataType.ARRAY_F,
DataType.ARRAY_B, DataType.ARRAY_W -> Opcode.PUSH_VAR_WORD DataType.ARRAY_B, DataType.ARRAY_W -> Opcode.PUSH_ADDR_HEAPVAR
} }
} }
@@ -565,7 +565,7 @@ private class StatementTranslator(private val prog: IntermediateProgram,
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
if(lv.heapId==null) if(lv.heapId==null)
throw CompilerException("string should have been moved into heap ${lv.position}") throw CompilerException("string should have been moved into heap ${lv.position}")
prog.instr(Opcode.PUSH_WORD, Value(lv.type, lv.heapId)) prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = "@todo-string-varname?") // XXX push address of string
} }
DataType.ARRAY_UB, DataType.ARRAY_UW, DataType.ARRAY_F, DataType.ARRAY_UB, DataType.ARRAY_UW, DataType.ARRAY_F,
DataType.ARRAY_B, DataType.ARRAY_W -> { DataType.ARRAY_B, DataType.ARRAY_W -> {

View File

@@ -41,7 +41,9 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
optimizeDataConversionAndUselessDiscards() optimizeDataConversionAndUselessDiscards()
optimizeVariableCopying() optimizeVariableCopying()
optimizeMultipleSequentialLineInstrs() optimizeMultipleSequentialLineInstrs()
// todo optimize stackvm code more // todo: optimize stackvm code more
// todo: stackvm replace rrestorex+rsavex combo by only rrestorex (note: can have label/comment inbetween)
// todo: stackvm replace call X + return (without values) combo by a jump X
// remove nops (that are not a label) // remove nops (that are not a label)
for (blk in blocks) { for (blk in blocks) {

View File

@@ -487,6 +487,9 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
Opcode.PUSH_REGAY_WORD -> { Opcode.PUSH_REGAY_WORD -> {
" sta ${ESTACK_LO.toHex()},x | tya | sta ${ESTACK_HI.toHex()},x | dex " " sta ${ESTACK_LO.toHex()},x | tya | sta ${ESTACK_HI.toHex()},x | dex "
} }
Opcode.PUSH_ADDR_HEAPVAR -> {
" lda #<${ins.callLabel} | sta ${ESTACK_LO.toHex()},x | lda #>${ins.callLabel} | sta ${ESTACK_HI.toHex()},x | dex"
}
Opcode.POP_REGAX_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself") Opcode.POP_REGAX_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
Opcode.POP_REGXY_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself") Opcode.POP_REGXY_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
Opcode.POP_REGAY_WORD -> { Opcode.POP_REGAY_WORD -> {

View File

@@ -186,7 +186,7 @@ Values will usually be part of an expression or assignment statement::
12345 ; integer number 12345 ; integer number
$aa43 ; hex integer number $aa43 ; hex integer number
%100101 ; binary integer number %100101 ; binary integer number (% is also remainder operator so be careful)
"Hi, I am a string" ; text string "Hi, I am a string" ; text string
'a' ; petscii value (byte) for the letter a 'a' ; petscii value (byte) for the letter a
-33.456e52 ; floating point number -33.456e52 ; floating point number

View File

@@ -258,6 +258,8 @@ type identifier type storage size example var declara
**hexadecimal numbers:** you can use a dollar prefix to write hexadecimal numbers: ``$20ac`` **hexadecimal numbers:** you can use a dollar prefix to write hexadecimal numbers: ``$20ac``
**binary numbers:** you can use a percent prefix to write binary numbers: ``%10010011`` **binary numbers:** you can use a percent prefix to write binary numbers: ``%10010011``
Note that ``%`` is also the remainder operator so be careful: if you want to take the remainder
of something with an operand starting with 1 or 0, you'll have to add a space in between.
**character values:** you can use a single character in quotes like this ``'a'`` for the Petscii byte value of that character. **character values:** you can use a single character in quotes like this ``'a'`` for the Petscii byte value of that character.
@@ -342,7 +344,7 @@ arithmetic: ``+`` ``-`` ``*`` ``/`` ``//`` ``**`` ``%``
``+``, ``-``, ``*``, ``/`` are the familiar arithmetic operations. ``+``, ``-``, ``*``, ``/`` are the familiar arithmetic operations.
``//`` is the floor-divide, the division resulting in a whole number rounded towards minus infinity. ``//`` is the floor-divide, the division resulting in a whole number rounded towards minus infinity.
``**`` is the power operator: ``3 ** 5`` is equal to 3*3*3*3*3 and is 243. ``**`` is the power operator: ``3 ** 5`` is equal to 3*3*3*3*3 and is 243.
``%`` is the remainder operator: ``25 % 7`` is 4. ``%`` is the remainder operator: ``25 % 7`` is 4. Be careful: without a space, %10 will be parsed as the binary number 2
bitwise arithmetic: ``&`` ``|`` ``^`` ``~`` bitwise arithmetic: ``&`` ``|`` ``^`` ``~``

View File

@@ -17,8 +17,8 @@ asmsub init_system () -> clobbers(A,X,Y) -> () {
; ---- initializes the machine to a sane starting state ; ---- initializes the machine to a sane starting state
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in, ; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set. ; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
; Also a different color scheme is chosen to identify ourselves a little. ; Also a different color scheme is chosen to identify ourselves a little.
; All three registers set to 0, status flags cleared. ; Uppercase charset is activated, and all three registers set to 0, status flags cleared.
%asm {{ %asm {{
sei sei
cld cld

View File

@@ -250,8 +250,16 @@ remainder_b .proc
.pend .pend
remainder_ub .proc remainder_ub .proc
inx
lda ESTACK_LO,x ; right operand
sta SCRATCH_ZPB1
lda ESTACK_LO+1,x ; left operand
- cmp SCRATCH_ZPB1
bcc +
sbc SCRATCH_ZPB1
jmp -
+ sta ESTACK_LO+1,x
rts rts
.warn "not implemented"
.pend .pend
remainder_w .proc remainder_w .proc
@@ -742,18 +750,57 @@ func_str2byte .proc
.warn "not implemented" .warn "not implemented"
.pend .pend
func_str2ubyte .proc ; @todo python code for a str-to-ubyte function that doesn't use the basic rom:
;-- convert string (address on stack) to ubyte number ;def str2ubyte(s, slen):
; @todo load address into $22/$23 ; hundreds_map = {
; @todo length of string in A ; 0: 0,
;jsr c64.FREADSTR ; string to fac1 ; 1: 100,
;jsr c64.GETADR ; fac1 to unsigned word in Y/A ; 2: 200
inx ; }
lda #99 ; @todo ; digitvalue = 0
sta ESTACK_LO,x ; result = 0
dex ; if slen==0:
; return digitvalue
; digitvalue = ord(s[slen-1])-48
; slen -= 1
; if slen==0:
; return digitvalue
; result = digitvalue
; digitvalue = 10 * (ord(s[slen-1])-48)
; result += digitvalue
; slen -= 1
; if slen==0:
; return result
; digitvalue = hundreds_map[ord(s[slen-1])-48]
; result += digitvalue
; return result
func_str2ubyte jmp func_str2uword
func_str2uword .proc
;-- convert string (address on stack) to uword number
lda ESTACK_LO+1,x
sta $22
lda ESTACK_HI+1,x
sta $23
jsr _strlen2233
tya
stx SCRATCH_ZPREG
jsr c64.FREADSTR ; string to fac1
jsr c64.GETADR ; fac1 to unsigned word in Y/A
ldx SCRATCH_ZPREG
sta ESTACK_HI+1,x
tya
sta ESTACK_LO+1,x
rts rts
.warn "not implemented" _strlen2233
;-- return the length of the (zero-terminated) string at $22/$23, in Y
ldy #0
- lda ($22),y
beq +
iny
bne -
+ rts
.pend .pend
func_str2word .proc func_str2word .proc
@@ -761,11 +808,6 @@ func_str2word .proc
.warn "not implemented" .warn "not implemented"
.pend .pend
func_str2uword .proc
rts
.warn "not implemented"
.pend
func_str2float .proc func_str2float .proc
rts rts
.warn "not implemented" .warn "not implemented"