Jump to content
IGNORED

Simon (game) in assembly language


Dexter

Recommended Posts

Very nice. Sounds great.

 

I noticed you have two memory word before you main program:

 

VSTAT DATA >8000 * VDP vsync status
NUM01 DATA 1 * 16-bit number 1
These will be executed as instructions because you program starts at SFIRST! If you want it to start at MAIN your last line should be: SLAST END MAIN
Link to comment
Share on other sites

Thanks Rasmus-M, nicely spotted.

I also have to turn off all the sprites, some of them are causing garbage on the screen.

 

Next I'm going to do the sounds and some text output routines...

 

Link to comment
Share on other sites

I'm really looking forward to the final product. I've never been a game player, but have an interesting history with the game.

 

I first encountered Simon at a dorm party at WCU in the late 70's. Someone else was playing it and I thought to myself "That looks cool!" After that person was done playing (unsuccessfully) they handed it to me. I started playing and soon had the attention of everyone in the room as I was doing well. (I was at college pursuing a BS in Music). I ended up finishing the game successfully, and was coaxed into doing it several more times. I thought nothing more of it other than it was fun. All you had to do was remember the pattern of the solfege notes so-do-mi-so. Simple! It's apparently not so simple for the musically uninclined, though. :(

 

This is the one game I can actually play. :)

 

Gazoo

  • Like 3
Link to comment
Share on other sites

Really nice progress!! I wrote an extended basic version a few years ago for one of the contests at the time, though I think we only had 30 lines of code to play with. Always enjoyed playing this game although the last time we had a group together, I vaguely remember consuming an alcohol-based penalty for missing the notes. :-o That was a dangerous challenge ;)

 

I can't wait to see your final product!

Link to comment
Share on other sites

(I was at college pursuing a BS in Music). I ended up finishing the game successfully

Indeed that’s extremely helpful! In my youth, I had music lessons in Electronic Organ, (not so popular anymore) and the game is like playing music on a “keyboard”. How helpful that is, I discovered recently. I made the SIMON game from this site: http://www.grandideastudio.com/portfolio/AVRSimon/

post-41771-0-22051700-1432312860_thumb.png

I created a PCB with the buttons arranged like on the schematic. Unfortunately, that wasn’t right, and the tones were scrambled, i.e. not from low to high like it should be. Playing the game like that was dreadful. :D That particular SIMON game also has a silent mode, that’s also rather difficult to play, although not as hard as hearing the sounds not in the right order.

 

I wrote an extended basic version a few years ago for one of the contests at the time

Yep, I found it (if you’re Tim) while researching the game on lots of different fronts. Your version is really quick for extended basic, a very fast response.

 

I vaguely remember consuming an alcohol-based penalty for missing the notes. :-o

I could program something like this: “have a beer and try again...” :pirate:

 

 

There are quite some versions and deviations, like increasing speed while progressing in the game, silent mode, blind mode, reverse mode and so on. However I’ll first focus on one variant only.

  • Like 1
Link to comment
Share on other sites

  • 2 weeks later...

Progress on SIMON is pretty slow, but that’s because I’m enjoying the view during the ride. ;-) Everything is new, so making graphics, music etc. costs a lot of investigation.

 

I’ve also been working on a much shorter development workflow with ralphb’s cross-development tools. http://atariage.com/forums/topic/233677-xdt99-new-ti-99-cross-development-tools-available/

Basically it’s now as easy as the click on a button, to create EA5 files in V9T9 format, to try them out on the emulator(s) and the real thing. It’s not working 100% every time, but that’s another story.

 

Here I have a little program that generates a list of 64 random numbers from 0-3, each stored in one word, starting at >a000. I know, wasteful, but it’s just for convenience. :) It uses some code of Matthew180's gameloop, like the random generator and the stack.

 

I’m using a pointer to the “buffer” where the random numbers are located. For the pointer, I’m using R9, but I’d rather use a regular ram location, or even a scratch pad location as a pointer. Despite this is working, I’m not satisfied with the code. Is there a more convenient way to do this?

 

Furthermore, am I using the def’s like sfirst etc. correctly?

       def  sfirst,slast,sload,main
sfirst equ  $
sload  equ  $
       b    @main

**
* workspace
wrksp  equ  >8300             * workspace

**
* random number memory map
stack  equ  >8320             * subrouting stack, grows down (8 bytes)
rand16 equ  >83c0             * 16-bit random number

       aorg >a000             * absolute origin at >a000
rndpnt equ  $                 * pointer to the random number buffer
rndbuf bss  128               * 64 words random number buffer, with values 0-3
*rndpnt data >a000             * pointer to the random number buffer


main
       li   r10,stack         * set up the stack pointer
       li   r9,rndpnt         * set up random buffer pointer

       li   r1,64             * initiate counter for 64 loops
lp     bl   @grnd             * get random number
       dec  r1                * decrease value in r1 by one
       jne  lp                * if not zero, return to loop lp

p      jmp  p                 * endless loop


**
* general workspace use:
*
* r9 random number buffer pointer
* r10 stack pointer
* r11 return address

*********************************************************************
*
* get random number from 0-3
*
grnd

       mov  r11,*r10+         * push return address onto the stack

       bl   @randno           * get new random number, stored in rand16
       srl  r5,14             * divide 16-bit value through
*                             * 16384 to get value from 0-3
       mov  r5,*r9+           * store the random number in
*                             * buffer and increase the pointer

       dect r10               * pop return address off the stack
       mov  *r10,r11
       b    *r11


*********************************************************************
*
* generates a weak pseudo random number and places it in rand16
*
* r4   - destroyed
* r5   - 16-bit random number and stored in rand16 for next round
*
randno
       li   r4,28643          * a prime number to multiply by
       mpy  @rand16,r4        * multiply by last random number
       ai   r5,31873          * add a prime number
       mov  r0,r4             * save r0
*      mov  @tick,r0          * use the vsync tick to mix it up a little
       andi r0,>000f          * check if shift count is 0
       jeq  rand01            * a 0 count means shift 16, which is a wash
       src  r5,0              * mix up the number to break odd/even pattern
rand01 mov  r5,@rand16        * save this number for next time
       mov  r4,r0             * restore r0
       b    *r11


slast  end  sfirst




  • Like 1
Link to comment
Share on other sites

It looks OK to me, but as it stands you don't really need the rndpnt equate since it's the same as rndbuf. If you want it to be a pointer stored in a memory location it should be something like "rndpnt data rndbuf". Then you could get the value of the pointer using "mov @rndpnt,r9" and increment it using "inct @rndpnt".

 

I also noticed that you have commented out the @tick part of the randno routine, but you're still shifting using whatever value r0 may contain, which is probably OK for making it a bit more random.

 

Edit: Also note that you can get a random number seed from >83C0.

  • Like 1
Link to comment
Share on other sites

it should be something like "rndpnt data rndbuf". Then you could get the value of the pointer using "mov @rndpnt,r9" and increment it using "inct @rndpnt".

That’s indeed like what I meant. I'll give it a try.

 

I also noticed that you have commented out the @tick part of the randno routine, but you're still shifting using whatever value r0 may contain, which is probably OK for making it a bit more random.

 

Edit: Also note that you can get a random number seed from >83C0.

Oh yeah, I just commented it out, because I pulled the routines out of SIMON, to isolate this random number issue. It’s going to be in there.
I think the initial seed will be from >83c0

 

 

  • Like 1
Link to comment
Share on other sites

I found in practice that the initial seed isn't always enough -- it's just a number that is steadily incremented on the master title page. Back in the day, I found that I hit a key so quickly and so predictably there that my number was frequently very close (if not the same - probably just close because back then I divided down). What helps with that, is I continue incrementing the seed on my own title page in any app I build, that helps improve the randomness.

Link to comment
Share on other sites

I found in practice that the initial seed isn't always enough -- it's just a number that is steadily incremented on the master title page. Back in the day, I found that I hit a key so quickly and so predictably there that my number was frequently very close (if not the same - probably just close because back then I divided down). What helps with that, is I continue incrementing the seed on my own title page in any app I build, that helps improve the randomness.

Good Idea, in my game, the player has to press "s" to start the game. That's the point when the seed could be taken from "tick".

 

It looks OK to me, but as it stands you don't really need the rndpnt equate since it's the same as rndbuf. If you want it to be a pointer stored in a memory location it should be something like "rndpnt data rndbuf". Then you could get the value of the pointer using "mov @rndpnt,r9" and increment it using "inct @rndpnt".

 

I also noticed that you have commented out the @tick part of the randno routine, but you're still shifting using whatever value r0 may contain, which is probably OK for making it a bit more random.

 

Edit: Also note that you can get a random number seed from >83C0.

It completely works to my satisfaction now! However, by the time I read from >83c0, it always has the same value. >8379 on the other hand, is always different. :?

 

 

In this version, I have taken the initial seed from >8378, which is >00xx.

       def  sfirst,slast,sload,main
sfirst equ  $
sload  equ  $
       b    @main

**
* workspace
wrksp  equ  >8300             * workspace
**
* random number memory map
stack  equ  >8320             * subrouting stack, grows down (8 bytes)
       aorg >b000             * absolute origin at >a000
main
       limi 0
       lwpi wrksp
       mov  @>8378,@rand16    * initialize seed by user keypress
       li   r10,stack         * set up the stack pointer
       mov  @rndpnt,r9        * set up random buffer pointer
       li   r1,64             * initiate counter for 64 loops
lp     bl   @grnd             * get random number
       dec  r1                * decrease value in r1 by one
       jne  lp                * if not zero, return to loop lp

p1     jmp  p1                * endless loop
p      jmp  p                 * endless loop


**
* general workspace use:
*
* r9 random number buffer pointer
* r10 stack pointer
* r11 return address

*********************************************************************
*
* get random number from 0-3
*
grnd

       mov  r11,*r10+         * push return address onto the stack

       bl   @randno           * get new random number, stored in rand16
       srl  r5,14             * divide 16-bit value through
*                             * 16384 to get value from 0-3

       mov  @rndpnt,r9
       mov  r5,*r9            * store the random number in

       inct @rndpnt

*                             * buffer and increase the pointer

       dect r10               * pop return address off the stack
       mov  *r10,r11
       b    *r11


*********************************************************************
*
* generates a weak pseudo random number and places it in rand16
*
* r4   - destroyed
* r5   - 16-bit random number and stored in rand16 for next round
*
randno
       li   r4,28643          * a prime number to multiply by
       mpy  @rand16,r4        * multiply by last random number
       ai   r5,31873          * add a prime number
       mov  r0,r4             * save r0
*      mov  @tick,r0          * use the vsync tick to mix it up a little
       andi r0,>000f          * check if shift count is 0
       jeq  rand01            * a 0 count means shift 16, which is a wash
       src  r5,0              * mix up the number to break odd/even pattern
rand01 mov  r5,@rand16        * save this number for next time
       mov  r4,r0             * restore r0
       b    *r11

       aorg >b0a0             * absolute origin at >a000
rndbuf bss  128               * 64 words random number buffer, with values 0-3
rndpnt data >b0a0             * pointer to the random number buffer
rand16 data >ffff             * 16-bit random number

slast  end  sfirst




Should I place those variables in line 81 and 82 in scratch pad memory, perhaps even those random numbers too (in a more compact format)?

  • Like 1
Link to comment
Share on other sites

Should I place those variables in line 81 and 82 in scratch pad memory, perhaps even those random numbers too (in a more compact format)?

 

Only if you want it to run from a cartridge without 32K. I don't think the access speed matters for a Simon game.

 

I think you will get tired of the aorg in line 79 that you have to change every time your program grows. There is no real need for it, just use "rndpnt data rndbuf" to initialize the pointer to point to the buffer (whereever it is located).

Link to comment
Share on other sites

...

Edit: Also note that you can get a random number seed from >83C0.

 

Only at power-up. After that it is used by the pseudo-random-number (PRN) generators (often the same code) in TI Basic, XB, TI Forth, the fbForths, ... to store the generated number for the next go-round. This is what guarantees the same sequence of PRNs from the same initial seed, which allows for checking programs using those PRNs.

 

======================

 

I see that you (@Dexter) are using what TI used with two differences:

  1. For some reason, TI’s formula was (seed * >6FE5 + >7AB9) SRC 5. Neither >65E5 nor >7AB9 is prime; but, they are both odd.
  2. The circular right shift is always a 5-bit shift.

Here is the routine I use for fbForth:

*** random number generator routine ***
_RNDW  LI   R0,>6FE5
       MPY  @>83C0,R0
       AI   R1,>7AB9
       SRC  R1,5
       MOV  R1,@>83C0
       B    *LINK

Also, here is the routine I use to randomize the seed when necessary:

*** RANDOMIZE ***     ( --- )
*       increments a counter until VDP interrupt detected
*        DATA SEED_N
* RNZ__N DATA 9+TERMBT*LSHFT8+'R','AN','DO','MI','ZE'+TERMBT

RNDMZ  DATA $+2
RNZ_P  MOVB @>8802,R0    get VDP status byte
       CLR  R0           discard it
       CLR  R1           clear counter
S1016A INC  R1           increment counter
       MOVB @>8802,R0    get VDP status byte
       ANDI R0,>8000     VDP interrupt?
       JEQ  S1016A          no, increment counter
       MOV  R1,@>83C0    yes, store new seed
       B    *NEXT

...lee

  • Like 2
Link to comment
Share on other sites

@Lee Stewart

I saw your reply in another thread, about random numbers: http://atariage.com/forums/topic/189117-restless-games/?p=3254385

That gave me some answers already.

 

 

I see that you (@Dexter) are using what TI used with two differences: ...

 

Thanks for the more in-depth information, so the second routine I just have to call if I want to ensure I get a new random seed, i.e. to get a new set of random numbers? And because I’m only checking the VDP’s status register, I can do that with the interrupts disabled? (limi 0)

 

While debugging it’s often more desirable to get the same set of random numbers, just to see if the program behaves like I expect it to. Then I set the seed to a fixed number.

 

 

 

I think you will get tired of the aorg in line 79 that you have to change every time your program grows. There is no real need for it, just use "rndpnt data rndbuf" to initialize the pointer to point to the buffer (whereever it is located).

You’re right, but I used it only for debugging reasons, to see the data in the debugger at a fixed place.

 

--------

 

Next I’m going to implement it in the game, to get a first working version, I hope... :)

Link to comment
Share on other sites

... Thanks for the more in-depth information, so the second routine I just have to call if I want to ensure I get a new random seed, i.e. to get a new set of random numbers?

 

Correct.

And because I’m only checking the VDP’s status register, I can do that with the interrupts disabled? (limi 0)

 

I believe so.

While debugging it’s often more desirable to get the same set of random numbers, just to see if the program behaves like I expect it to. Then I set the seed to a fixed number.

 

You will not get the same set if you use lines 71 – 74 of your post #35 because the shift is unpredictable.

 

...lee

Link to comment
Share on other sites

You’re right, but I used it only for debugging reasons, to see the data in the debugger at a fixed place.

Assemble with a listing file - you can use the listing file to see where everything got stored in memory for debugging purposes. :)

Link to comment
Share on other sites

Thanks for the more in-depth information, so the second routine I just have to call if I want to ensure I get a new random seed, i.e. to get a new set of random numbers? And because I’m only checking the VDP’s status register, I can do that with the interrupts disabled? (limi 0)

If you are reading the VDP status register directly then you really must use LIMI 0 to prevent the console ISR from running. Pulling the VDP status out from under the console ISR is a "bad thing". If you want to co-exist with the console ISR, then you have to follow the ISR's rules for using the scratchpad RAM. If you leave CPU interrupts enabled (LIMI 2) and use the console ISR, then I suggest you use the ISR's hook to update your game loop.

Link to comment
Share on other sites

Assemble with a listing file - you can use the listing file to see where everything got stored in memory for debugging purposes. :)

Good point, that’s very often the case. However, I’m using ralphb’s xdt99: TI 99 Cross-Development Tools

http://atariage.com/forums/topic/233677-xdt99-new-ti-99-cross-development-tools-available/, which does an incredibly good job I have to say. At the moment it doesn’t support outputting a listing file, but I could use WinAsm99 for that, of course.

 

If you are reading the VDP status register directly then you really must use LIMI 0 to prevent the console ISR from running. Pulling the VDP status out from under the console ISR is a "bad thing". If you want to co-exist with the console ISR, then you have to follow the ISR's rules for using the scratchpad RAM. If you leave CPU interrupts enabled (LIMI 2) and use the console ISR, then I suggest you use the ISR's hook to update your game loop.

 

Thanks, I intent to leave the interrupt off, and use your “gameloop” from the assembly thread. When reading the status register, it will be cleared, so an interrupt could be missed, right?

 

 

--------

 

Here is the fruit of my work, just a little strawberry though. :)

So far I have Graphics, Music, Keyboard (just the S-key) and the random generator.

There's NO gameplay yet, only pressing "S" will give a set of 64 random numbers.

 

System requirements:

Extended BASIC

Disk drive

32KiB memory expansion

F18A (although it works without for now)

 

The program will autostart from DSK1.

 

 

The source file is attached, so feel free to give lots of suggestions etc. I’m sure things could be done more efficiently. Music and graphics are done by me, myself and I(rene) :D Except for the character set, which is from some game on the C64.

 

myloop.a99

runme.dsk

 

Edit: Correction, not the fruit of my work alone, but very much of your invaluable help!

Edited by Dexter
  • Like 3
Link to comment
Share on other sites

If you are reading the VDP status register directly then you really must use LIMI 0 to prevent the console ISR from running. Pulling the VDP status out from under the console ISR is a "bad thing". If you want to co-exist with the console ISR, then you have to follow the ISR's rules for using the scratchpad RAM. If you leave CPU interrupts enabled (LIMI 2) and use the console ISR, then I suggest you use the ISR's hook to update your game loop.

 

It was the TI developers of TI Forth who originally wrote the RANDOMIZE routine I use in fbForth. The point of the routine is to race the console ISR for the VDP interrupt, counting how many reads it takes to catch it before the console ISR does. Certainly, one VDP interrupt will be missed. One more could be missed with the initial VDP status read, if it is set. Though possible, that is unlikely. The swiped VDP interrupt will cause a hesitation in sprite automotion and ISR-processed sound lists; but, is it a “bad thing”, otherwise?

 

...lee

Link to comment
Share on other sites

Are you going to require an F18A just in order to change the palette or are you planning to use it for something else? If it's just for the palette you could easily obtain the same effect by changing a rectangle in color table (but I'm sure you know that already).

Link to comment
Share on other sites

Are you going to require an F18A just in order to change the palette or are you planning to use it for something else? If it's just for the palette you could easily obtain the same effect by changing a rectangle in color table (but I'm sure you know that already).

It was sort of a mix actually, on one hand trying out some arbitrary functions of the F18A and unlocking them, and on the other hand changing the palette with a few values. But indeed, it’s a little bit silly to make the game dependent of the F18A, just for the altering of the palette. I’ll most likely scratch that from SIMON, so that it’s also playable on 4A’s without the F18A.

Link to comment
Share on other sites

Are you going to require an F18A just in order to change the palette or are you planning to use it for something else? If it's just for the palette you could easily obtain the same effect by changing a rectangle in color table (but I'm sure you know that already).

Well, I’m using half bitmap mode, and the used bytes for the color table are not sequentially as in normal bitmap mode, but scattered over the place. So for the digit 1, there are a few specific characters used, which have do get another value in the color table.
S123G
       MOV  R11,*R10+         * Push return address onto the stack
 
       li   r3,0              * r1 is index
ld1    movb @dig1(r3),r4      * load value from dig1+index
       ci   r4,0              * end of table?
       jeq  nld1              * yes, exit loop
       li   r0,>2000          * start of color table
       a    r4,r0             * r0 holds now the actual color table location
       li   r1,>e1            * grey pixels on black background
       bl   @vsbw             * store the byte in VRAM
       inc  r3                * increase index
       b    @ld1              * next character
nld1
       DECT R10               * Pop return address off the stack
       MOV  *R10,R11
       B    *R11
 
 
dig1   byte 145,146,160,171,172,183,209,210,0
I’m using the Indexed Memory Addressing mode, to write >e1 (grey pixels on black background), to the specific color table bytes. The screen gets pretty messed up, so I’m obviously doing something wrong.
post-41771-0-87837200-1434487373_thumb.png
Link to comment
Share on other sites

If your BL @VSBW operates on the value of R1's MSByte, you are sending >00 every time. Setting R1 to 0xe100 may be what you need i.e., "LI R1,>E100"

 

Also watch out for similar circumstances in byte-wise operations. For example, If R4 is not set to 0x0000 before your "movb @dig1(r3),r4 " the next test will always fail. Why? MOVB retains whatever is in the LSByte of the destination. Your routine may be executing many, many more times than anticipated. ;)

Link to comment
Share on other sites

You have to set 8 bytes of the color table for every character. If your @dig1 table is a list of character codes/numbers then you have to do something like this:

 

clr r4

movb @dig1(r3),r4

jeq nld1

swpb r4

sla r4,3

li r0,>2000

a r4,r0

 

And then you need to use @VSMW to write 8 bytes.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...