>rbextsrc   Richard Murray ( Monday, 23rd October 2000 2* ExtBASasm version 29th December 2003 < F5 Based upon a game I downloaded (or cover disc?) P Z d = &8000 + (128*1024) n x $+" at "+(/10) :   code% 2048   loop% = 8 10 2  P% = code%  L% = code% + 2048  [ OPT loop%  ext 1  ; Program entry  = ; R1 = memory limit, so set this to be stack pointer. E MOV R13, R1 ; *** TESTING FROM BASIC *** D STMFD R13!, {R14} ; (you must patch it out)  T ; Check FPEmulator is loaded. This checks to see if "FPEmulator" is present. [ ; Don't know if other versions of FPE call themselves "FPEmulator". A quick hack of "W ; SharedCLibrary suggests that "_kernel_fpavailable" uses a very similar way of ,; ; detecting if floating point maths is available... 6+ ; Is anybody STILL using RISC OS 2? @ MOV R0, #18 J ADR R1, fpemulator T SWI "XOS_Module" ^X BVS fpe_not_loaded ; Report an error if error returned (module not found...) h r3 ; Store current <255> character definition. | MOV R0, #10 # ADR R1, chartwofivefive  SWI "OS_Word"  ( ; Set up seed to be more random.  ADR R1, seed & SWI "OS_ReadMonotonicTime"  STR R0, [R1]  8 ; Redefine character 255 to be a rounded square.  SWI &100 + 23  SWI &100 + 255  SWI &100 + 126  SWI &100 + 255  SWI &100 + 255  SWI &100 + 255  SWI &100 + 255 & SWI &100 + 255 0 SWI &100 + 255 : SWI &100 + 126 D N .restart XB \ Program is re-entered here if user wishes to play again. b l v, ; Select 1, and switch off cursor.  SWI &100 + 22  SWI &100 + 1 " SWI "OS_RemoveCursors"   \ The main game loop. .game_loop  BL draw_screen  BL game  CMP R0, #0  BEQ game_lost  B game_won     .draw_screen , \ This sets up the screen for a game   \ Corrupts R0-R5. *$ \ Returns via stacked R14. 4 > STMFD R13!, {R14} H R( SWI &100 + 12 ; \ f, SWI &100 + 18 ; 0,2 p SWI &100 + 0 z SWI &100 + 2  T MOV R4, #0 ; Draw a three-sided border (left, top, bottom) # ADR R5, border_commands .border_loop I LDMIA R5!, {R0-R2} ; Border commands are stored as data O SWI "OS_Plot" ; to allow this to work a little faster...  ADD R4, R4, #1  CMP R4, #4  BLT border_loop  ) ; Now set up and plot the blocks.  MOV R3, #0 .block_loop H MOV R0, #63 ; Need >31, so use 63 as next range  BL random I CMP R0, #34 ; Now constrain to within 34 by ing $N GT R0, R0, #31 ; the larger numbers to be in range 0...31. .X ADD R0, R0, #4 ; Add four, to push away from the left hand border. 8> MOV R4, R0 ; Preserve X random in R4 B MOV R0, #31 L BL random V> MOV R5, R0 ; Preserve Y random in R5 ` j9 SWI &100 + 31 ; 31,,,255 t MOV R0, R4 ~ SWI "OS_WriteC"  MOV R0, R5  SWI "OS_WriteC"  SWI &100 + 255   ADD R3, R3, #1 I CMP R3, #70 ; This is the number of blocks drawn W BLT block_loop ; 50 is suggested minimum, 90 is suggested maximum  & ; Draw left-hand border blocks  MOV R0, #0 .left_block_loop 6 SWI &100 + 31 ; 31,0,,255 H SWI &100 ; Which is "TAB(0,);CHR$(255);"   SWI "OS_WriteC"  SWI &100 + 255  ADD R0, R0, #3 ( CMP R0, #32 2 BLT left_block_loop < F8 MOV R0, #15 ; Flush all buffers P MOV R1, #0 Z SWI "OS_Byte" d n@ LDMFD R13!, {PC} ; Return to game_loop code. x   .border_commands S \ A sequence of commands for the OS_Plot loop, to draw a three-sided border 2 EQUD 4 ; 1279,1023 : EQUD 1279 ; (move to top right)  EQUD 1023 / EQUD 5 ; 0,1023 9 EQUD 0 ; (draw to top left)  EQUD 1023 , EQUD 5 ; 0,0 < EQUD 0 ; (draw to bottom left)  EQUD 0 / EQUD 5 ; 1279,0 = EQUD 1279 ; (draw to bottom right)  EQUD 0 " , 6 @ .game J \ This is the game loop. T! \ Corrupts R0-R2,R5-R8. ^ \ Corrupts F0-F7. h$ \ Returns via stacked R14. r \ |" \ Returns the score in R0.  \ [ \ Once set up, all FP operations are performed in the seven available FP registers. _ \ Values are FIXed to the ARM processor for SWI calls, but there is no repeated loading [ \ and storing of data from/to memory. This might not have a tremendous impact on an K \ emulated FPA, but should help a real hardware FPA perform well...   STMFD R13!, {R14}  ) ; Set up floating point registers  ADR R0, acceleration 8 LDFS F1,[R0] ; F1 = acceleration  MOV R0, #4 H FLTS F2,R0 ; F2 = x (nb. F3 is below) (= 4) H FLTS F4,R0 ; F4 = xs (= 4) H MVFS F5,#0 ; F5 = ys (= 0) H MVFS F6,#0 ; F6 = sc (= 0) & ADR R0, gravity 0. LDFS F7,[R0] ; F7 = gr : D% ; Calculate starting position N.start_point_loop X ; Get a random offset b MOV R0, #512 l SUB R0, R0, #1 v BL random 4 ADD R8, R0, #256 ; R8 is temp. Y , ; Read that point, and +/- 8 from it  MOV R0, #4  MOV R1, R8  SWI "OS_ReadPoint" 2 MOV R5, R2 ; R5 is x,y)  ADD R1, R1, #8  SWI "OS_ReadPoint" 4 MOV R6, R2 ; R6 is x,y+8)  SUB R1, R1, #16  SWI "OS_ReadPoint" 4 MOV R7, R2 ; R7 is x,y-8)  - ; Did all points return zero (black)?   CMP R5, #0  BNE start_point_loop   CMP R6, #0 * BNE start_point_loop 4 CMP R7, #0 > BNE start_point_loop H R+ ; Get here, R8 is good for Y offset \- FLTS F3,R8 ; F3 = y f p/ MOV R0, #4 ; 0, z MOV R1, #0  MOV R2, R8  SWI "OS_Plot"  , SWI &100 + 18 ; 0,1  SWI &100 + 0  SWI &100 + 1  .game_loop 5 MOV R0, #19 ; Wait for VSync 5 SWI "OS_Byte" ; Corrupts R1,R2  . ADFS F6, F6, #1 ; sc += 1   ; Draw next bit of line 0 MOV R0, #5 ; , F FIX R1, F2 ; Takes X and Y from FP registers $ FIX R2, F3 . SWI "OS_Plot" 8 B ; Increment offsets L- ADFS F2, F2, F4 ; x=x+xs V- ADFS F3, F3, F5 ; y=y+ys ` j ; Read x+xs,y) t. ADFS F0, F2, F4 ; F0=x+xs ~, FIX R0, F0 ; Get X , FIX R1, F3 ; Get Y  SWI "OS_ReadPoint" 2 CMP R2, #3 ; Colour = 3? 3 MVFEQS F0, F4 ; Conditional: 5 SUFEQS F4, F4, F4 ; xs - xs - xs > SUFEQS F4, F4, F0 ; (which equals "xs=-xs") ? MOV R6, R2 ; It'll be needed later...  \ ; Time delay (comment out this block to run it at full speed (processor dependent)). & SWI "OS_ReadMonotonicTime" 5 ADD R1, R0, #2 ; 2 centiseconds O.time_delay_loop ; Not much, but enough... 3cs is too slow. & SWI "OS_ReadMonotonicTime"  CMP R0, R1  BLT time_delay_loop  ( ; ESC pressed? 2$ SWI "OS_ReadEscapeState"  _ SUFS F5, F5, F7 ; ys=ys+gr -add a little gravity into the mix! Blub! Blub!  \ CMN R6, #1 ; If not off-screen (told you it'd be needed again!)... 3 BNE game_loop ; ...carry on.  . ADFS F0, F2, F4 ; F0=x+xs , FIX R0, F0 ; Get X # ADR R2, onetwoeightzero  LDR R1, [R2] 0 CMP R0, R1 ; X < 1280? @ MOVLT R0, #0 ; If yes, return score = 0. 9 FIXGE R0, F6 ; Else, get score... ? LDMFD R13!, {PC} ; ...and return from game.  " , 6.abort_exit @! \ This will abort tidily. J \ T_ \ Whenever this is called, there should be TWO R14's on the stack. This will load both, ^O \ discard the more recent and use the older - thus returning correctly. h] \ It is nicer, if you are running this from a BASIC shell (ie, when testing), than to r; \ call "OS_Exit" (which will exit from BASIC too!). |V \ Gives a message, but it isn't presented as an error. This is tidy, remember?  ( SWI &100 + 12 ; 6 ADR R0, abort_message ; Print a message  SWI "OS_Write0" 6 MOV R0, #126 ; Acknowledge ESC  SWI "OS_Byte" ? BL restore_character ; Restore <255> character. _ LDMFD R13!, {R0,PC} ; Exit (should load two stored R14, junk 1st, use 2nd...).    .fpe_not_loaded J \ If FPEmulator module is not loaded, then return a (fatal) error. ( ADR R0, fpemulator_not_found " SWI "OS_GenerateError"  & 0 :M \ ******************************************************************* DJ \ , vaguely (?) in the middle so can be accessed from all code... NM \ ******************************************************************* X b.onetwoeightzero l EQUD 1280 v .abort_message , EQUS "ESCAPE pressed, exiting..."  EQUB 10  EQUB 0  .msg_press_space 0 EQUS " Press SPACE for another game "  EQUB 0  EQUB 0  .msg_bad_luck  EQUS "Bad luck..."  EQUB 0  .msg_well_done  EQUS "Well Done!"  EQUB 0 * EQUB 0 4 >.msg_score H EQUS "Score: " R EQUB 0 \ f.msg_high_score p EQUS "High score: " z EQUD 0  .msg_new_high_score " EQUS "New high score: "  EQUD 0  .msg_enter_name " EQUS "Enter your name:"  EQUD 0  .fpemulator  EQUS "FPEmulator"  EQUB 0  EQUB 0  .fpemulator_not_found  EQUD 0 $D EQUS "The FPEmulator module does not appear to be loaded" . EQUB 0 8 EQUB 0 B L.seed_pointer V EQUD seed ` j .seed t EQUD &55555555 ~ EQUD &55555555   .buffer  EQUD 0  EQUD 0  .high_score T EQUD 320 ; Straight line across is approx. 318 points...  .high_name T EQUS "Richard Murray" ; Yeah, well... I wrote it, okay? Deal with it! - EQUB 0 ;  EQUB 0 Q EQUD 0 ; 20 bytes (16 used, 1 terminator, 3 wasted)    .emanon S EQUS "" ; Default name if user doesn't enter anything. ( EQUD 0 2  \ 31 30..................23 22...............0 @> \ Sign Exponent (excess-128?) msb Fraction lsb J T ^ h .game_won rS \ If user wins (ie, score non-zero), then this will sort out "done good" vs | \ "new high score".  \  \ Corrupts R0-R1,R8. ' \ Sets R8 to score when done. = \ Does not return, calls one of two other routines.  : MOV R8, R0 ; Preserve high score  * MOV R0, #0 ; 3  MOV R1, #3  SWI "OS_SetColour"  - SWI &100 + 31 ; 14,8)  SWI &100 + 14  SWI &100 + 8 ; ADR R0, msg_well_done ; Print congratulation  SWI "OS_Write0" & 09 ADR R1, high_score ; Compare high score : LDR R0, [R1] D CMP R8, R0 N BGT new_high_score XD ; B good_score ; ...but_not_good_enough b& ; ** ENTIONAL FALL THROUGH ** l v  .good_score Q \ If user has a good score (not won, not lost), then give them a message. G \ Corrupts R0-R2,R10, and expects a value to be stored in R8. 1 \ Does not return, calls another routine.  * MOV R0, #0 ; 2  MOV R1, #2  SWI "OS_SetColour"  . SWI &100 + 31 ; 14,10)  SWI &100 + 14  SWI &100 + 10   7 ADR R0, msg_score ; Print your score  SWI "OS_Write0"   MOV R0, R8 * ADR R1, buffer 4 MOV R2, #8 >$ SWI "OS_BinaryToDecimal" H ADR R0, buffer R MOV R1, R2 \ SWI "OS_WriteN" f p* MOV R0, #0 ; 1 z MOV R1, #1  SWI "OS_SetColour"   MOV R10, #12 ! B print_high_scorer    .new_high_score T \ If user has a won, give them a message, get their name, update high score. C \ Corrupts R0-R3, and expects a value to be stored in R8. 1 \ Does not return, calls another routine.  * MOV R0, #0 ; 2  MOV R1, #2  SWI "OS_SetColour"  $. SWI &100 + 31 ; 12,10) . SWI &100 + 12 8 SWI &100 + 10 B LC ADR R0, msg_new_high_score ; Print congratulatory score V SWI "OS_Write0" ` MOV R0, R8 j ADR R1, buffer t MOV R2, #8 ~$ SWI "OS_BinaryToDecimal"  ADR R0, buffer  MOV R1, R2  SWI "OS_WriteN"  . SWI &100 + 31 ; 12,12)  SWI &100 + 12  SWI &100 + 12  5 ADR R0, msg_enter_name ; Mortier prompt  SWI "OS_Write0"  8 MOV R0, #15 ; Flush all buffers  MOV R1, #0   SWI "OS_Byte"  . SWI &100 + 31 ; 12,14) ( SWI &100 + 12 2 SWI &100 + 14 < F8 ADR R1, high_score ; Update high score P STR R8, [R1] Z d/ SWI "OS_RestoreCursors" ; Get name n ADR R0, high_name xW ADD R0, R0, #&80000000 ; Set bit 31, echo only characters entering buffer  MOV R1, #16  MOV R2, #(" ")  MOV R3, #("z")  SWI "XOS_ReadLine" 3 BCS abort_exit ; Esc pressed? " SWI "OS_RemoveCursors"  A CMP R1, #0 ; User entered a blank name?  BEQ handle_emanon  O ADR R0, high_name ; Patch in a null byte instead of newline.  ADD R0, R0, R1  MOV R1, #0  STRB R1, [R0]  .return_after_emanon "8 ADR R1, high_score ; Update high score , STR R8, [R1] 6 @.empty_keybuffer JR MOV R0, #121 ; Dispatch stacked keypresses by the mightily TS MOV R1, #16 ; technical way of simply junking them. ^ SWI "OS_Byte" h> CMP R1, #255 ; If something pressed... r7 BNE empty_keybuffer ; ...keep looping. |  B new_game    .handle_emanon L \ This handles a case of no name entered, by copying a default name.  \ Corrupts R0-R2. A \ Returns by branching to a point in the calling routine.   ADR R1, emanon  ADR R2, high_name .emanon_loop  LDRB R0, [R1], #1  STRB R0, [R2], #1  CMP R0, #0  BNE emanon_loop &# B return_after_emanon 0 : D N.game_lost XQ \ If user has a good score (not won, not lost), then give them a message. b \ Corrupts R0,R1,R10. l1 \ Does not return, calls another routine. v * MOV R0, #0 ; 1  MOV R1, #1  SWI "OS_SetColour"  - SWI &100 + 31 ; 14,8)  SWI &100 + 14  SWI &100 + 8 : ADR R0, msg_bad_luck ; Print commiseration  SWI "OS_Write0"  * MOV R0, #0 ; 2  MOV R1, #2  SWI "OS_SetColour"    MOV R10, #10 ! B print_high_scorer   * 4 >.print_high_scorer H3 \ Prints the name/score of the high scorer. R \ Corrupts R0-R2,R5 \D \ Expects R10 to be Y position for printing this stuff at. f1 \ Does not return, calls another routine. p z9 ADR R1, high_score ; Convert high score  LDR R0, [R1]  ADR R1, buffer  MOV R2, #8 $ SWI "OS_BinaryToDecimal" = MOV R5, R2 ; Preserve string length  / ; Now we count up the string lengths... ; MOV R0, #13 ; Misc. string lengths 3 ADD R0, R0, R2 ; Score length G ADR R2, high_name ; Calculate high score name length  .glsll  LDRB R1, [R2], #1  CMP R1, #0  ADDNE R0, R0, #1  BNE glsll 7 MOV R1, #40 ; Calculate offset $ SUB R1, R1, R0 .) MOV R0, R1, ASR#1 ; /2 8 B/ SWI &100 + 31 ; ,10) L SWI "OS_WriteC" V MOV R0, R10 ` SWI "OS_WriteC" j t2 ADR R0, msg_high_score ; Print title ~ SWI "OS_Write0" 2 ADR R0, buffer ; Print score  MOV R1, R5  SWI "OS_WriteN" 5 SWI &100 + 32 ; Output a space A ADR R0, high_name ; Print name of high scorer.  SWI "OS_Write0"   B new_game     .new_game N \ Does the user want to play again? SPACE if yes, anything else quits.   \ Corrupts R0-R1. 1 \ Expects stacked R14 to be exit pointer. B \ Also expects stacked keypresses to have been dealt with. ( 28 MOV R0, #15 ; Flush all buffers < MOV R1, #0 F SWI "OS_Byte" P Z* MOV R0, #0 ; 3 d MOV R1, #3 n SWI "OS_SetColour" x - SWI &100 + 31 ; 5,30)  SWI &100 + 5  SWI &100 + 30 3 ADR R0, msg_press_space ; Print prompt  SWI "OS_Write0" .await_keypress  MOV R0, #121 L MOV R1, #16 ; Skip checking for modifier keys/mouse  SWI "OS_Byte" : CMP R1, #255 ; If HING pressed... 7 BEQ await_keypress ; ...keep looping. : CMP R1, #98 ; If Space pressed... 7 BEQ restart ; ...play again... 0 MVN R0, #0 ; [ R0 = -1 \ SWI "XWimp_CommandWindow"; will cause "Press Space" message to be suppressed ] B BL restore_character ; [ Restore <255> character ] "4 LDMFD R13!, {PC} ; ...else exit. , 6 @ J.restore_character TK \ Restores the <255> character to its original state, upon an exit. ^ \ Corrupts R0-R2. h \ Returns via R14. r |# ADR R1, chartwofivefive  MOV R2, #0  SWI &100 + 23 .restore_loop  LDRB R0, [R1], #1  SWI "OS_WriteC"  ADD R2, R2, #1  CMP R2, #9  BLT restore_loop   MOV PC, R14     .random 9 \ Generates a random number and returns it in R0. X \ Expects a range in R0 upon entry, which must contain all bits in wanted range. &) \ Uses R1-R5, and preserves them. 0 \ :X \ Taken from the "random.s" example on the ARM development demonstration CD-ROM: DU \ This uses a 33-bit feedback shift register to generate a pseudo-randomly NT \ ordered sequence of numbers which repeats in a cycle of length 2^33 - 1 X \ b1 \ Corrupts R0 (returns a value in it!). l3 \ Preserves other registers used (R1-R4). v$ \ Returns via stacked R14.  " STMFD R13!, {R1-R4, R14}  ADR R4, seed_pointer  LDMIA R4, {R1, R2} 8 TST R2, R2, LSR#1 ; to bit into carry : MOVS R3, R1, RRX ; 33-bit rotate right ; ADC R2, R2, R2 ; carry into LSB of a2 0 R3, R3, R1, LSL#12 ; (involved!) : R1, R3, R3, LSR#20 ; (similarly involved!)  STMIA R4, {R1, R2} U R0, R1, R0 ; random number with range, and store the result C LDMFD R13!, {R1-R4, PC} ; in R0 ready for returning...   ]     _p$="IDEFS::Amy.$._ENTRY.Willow.Coding.Projects.Assembler.asmarea.apcsstuff.rebound.rebound" */("Save "+p$+" "+~code%+" +"+~(P%-code%)) 4("SetType "+p$+" FF8") > HHcode%!0 = &E1A00000 : Patch out R13 set with "MOV R0, R0" (NOP)... R REPEAT \ code% f IF INKEY(-3) THEN END p UNTIL 0 z