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 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 {
sub start() {
str name = "????????????????????????????????????????"
str guessstr = "??????????"
str input = "??????????"
ubyte guess
ubyte secretnumber = 0
ubyte attempts_left = 10
memory uword freadstr_arg = $22 ; argument for FREADSTR
uword testword
memory uword freadstr_arg = $22 ; argument for FREADSTR ($22/$23)
; greeting
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")
; 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() ; .. and now *100
c64.FADDH() ; add 0.5..
@ -35,30 +41,28 @@ ask_guess:
c64.STROUT("\nYou have ")
c64scr.print_byte_decimal(attempts_left)
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? ")
Y=c64scr.input_chars(guessstr)
Y=c64scr.input_chars(input)
c64.CHROUT('\n')
freadstr_arg = guessstr
freadstr_arg = input
c64.FREADSTR(Y)
A, Y = c64flt.GETADRAY()
guess=A
c64.EXTCOL=guess ; @debug
c64.BGCOL0=secretnumber ;@debug
if(guess==secretnumber) {
c64.STROUT("\nThat's my number, impressive!\n")
goto goodbye
}
c64.STROUT("\nThat is too ")
if(guess > secretnumber)
if(guess < secretnumber)
c64.STROUT("low!\n")
else
c64.STROUT("high!\n")
attempts_left--
if(attempts_left>0) goto ask_guess
; more efficient: if_nz goto ask_guess
if_nz goto ask_guess
; game over.
c64.STROUT("\nToo bad! It was: ")

View File

@ -1,12 +1,16 @@
%import c64utils
%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 {
sub start() {
str name = "????????????????????????????????????????"
str guess = "??????????"
ubyte secretnumber = rnd() % 100
str input = "??????????"
ubyte secretnumber = rnd() % 99 + 1 ; random number 1..100
c64.VMCSB |= 2 ; switch lowercase chars
c64scr.print_string("Please introduce yourself: ")
@ -17,50 +21,67 @@
for ubyte attempts_left in 10 to 1 step -1 {
c64scr.print_byte_decimal(X)
c64.CHROUT('\n')
; stackptr debugging
; c64scr.print_byte_decimal(X)
; c64.CHROUT('\n')
c64scr.print_string("\nYou have ")
c64scr.print_byte_decimal(attempts_left)
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.input_chars(guess)
ubyte guessednumber = str2ubyte(guess)
c64scr.input_chars(input)
ubyte guess = str2ubyte(input)
; debug info
c64scr.print_string(" > attempts left=")
c64scr.print_byte_decimal(attempts_left)
c64scr.print_string("\n > secretnumber=")
c64scr.print_byte_decimal(secretnumber)
c64scr.print_string("\n > guess=")
c64scr.print_string(guess)
c64scr.print_string("\n > guessednumber=")
c64scr.print_byte_decimal(guessednumber)
c64.CHROUT('\n')
c64scr.print_byte_decimal(X)
c64.CHROUT('\n')
; debug info
; c64scr.print_string(" > attempts left=")
; c64scr.print_byte_decimal(attempts_left)
; c64scr.print_string("\n > secretnumber=")
; c64scr.print_byte_decimal(secretnumber)
; c64scr.print_string("\n > input=")
; c64scr.print_string(input)
; c64scr.print_string("\n > guess=")
; c64scr.print_byte_decimal(guess)
; c64.CHROUT('\n')
; c64scr.print_byte_decimal(X) ; stackptr debugging
; c64.CHROUT('\n')
if guessednumber==secretnumber {
c64scr.print_string("\n\nYou guessed it, impressive!\n")
c64scr.print_string("Thanks for playing, ")
c64scr.print_string(name)
c64scr.print_string(".\n")
return
if guess==secretnumber {
ending(true)
return ; @todo make return ending(true) actually work as well
} else {
c64scr.print_string("\n\nThat is too ")
if guessednumber<secretnumber
if guess<secretnumber
c64scr.print_string("low!\n")
else
c64scr.print_string("high!\n")
}
}
c64scr.print_string("\nToo bad! My number was: ")
c64scr.print_byte_decimal(secretnumber)
c64scr.print_string(".\n")
return
; return 99 ;@todo error message (no return values)
; return 99,44 ;@todo error message (no return values)
; return ending(false) ; @todo fix this, actuall needs to CALL ending even though no value is returned
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.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS,
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 -> {
if(lv.heapId==null)
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_B, DataType.ARRAY_W -> {

View File

@ -41,7 +41,9 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
optimizeDataConversionAndUselessDiscards()
optimizeVariableCopying()
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)
for (blk in blocks) {

View File

@ -487,6 +487,9 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
Opcode.PUSH_REGAY_WORD -> {
" 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_REGXY_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
Opcode.POP_REGAY_WORD -> {

View File

@ -186,7 +186,7 @@ Values will usually be part of an expression or assignment statement::
12345 ; 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
'a' ; petscii value (byte) for the letter a
-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``
**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.
@ -342,7 +344,7 @@ arithmetic: ``+`` ``-`` ``*`` ``/`` ``//`` ``**`` ``%``
``+``, ``-``, ``*``, ``/`` are the familiar arithmetic operations.
``//`` 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 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: ``&`` ``|`` ``^`` ``~``

View File

@ -17,8 +17,8 @@ asmsub init_system () -> clobbers(A,X,Y) -> () {
; ---- initializes the machine to a sane starting state
; 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.
; Also a different color scheme is chosen to identify ourselves a little.
; All three registers set to 0, status flags cleared.
; Also a different color scheme is chosen to identify ourselves a little.
; Uppercase charset is activated, and all three registers set to 0, status flags cleared.
%asm {{
sei
cld

View File

@ -250,8 +250,16 @@ remainder_b .proc
.pend
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
.warn "not implemented"
.pend
remainder_w .proc
@ -742,18 +750,57 @@ func_str2byte .proc
.warn "not implemented"
.pend
func_str2ubyte .proc
;-- convert string (address on stack) to ubyte number
; @todo load address into $22/$23
; @todo length of string in A
;jsr c64.FREADSTR ; string to fac1
;jsr c64.GETADR ; fac1 to unsigned word in Y/A
inx
lda #99 ; @todo
sta ESTACK_LO,x
dex
; @todo python code for a str-to-ubyte function that doesn't use the basic rom:
;def str2ubyte(s, slen):
; hundreds_map = {
; 0: 0,
; 1: 100,
; 2: 200
; }
; digitvalue = 0
; result = 0
; 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
.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
func_str2word .proc
@ -761,11 +808,6 @@ func_str2word .proc
.warn "not implemented"
.pend
func_str2uword .proc
rts
.warn "not implemented"
.pend
func_str2float .proc
rts
.warn "not implemented"