diff --git a/README.md b/README.md index 26638cc..62e2d45 100644 --- a/README.md +++ b/README.md @@ -273,12 +273,13 @@ namely: * [A simple 10-character echo test](examples/echo.mu8a) (requires both I/O port 1 and port 2 support), * [Bulls and Cows game](examples/moo.mu8a), -* [Lunar Lander game](examples/lunar.mu8a). +* [Lunar Lander game](examples/lunar.mu8a), +* [NumberJack](examples/numjack.mu8a) port of a Blackjack game, utilizing some + advanced techniques (see the comments in the beginning on how to play it). You can assemble them using any of the reference assemblers provided within the repository, or even by hand (by numbering lines, resolving the labels and -replacing mnemonics with corresponding opcodes). The "Hellorld!" example only -works on the platforms supporting I/O port 1 (character output). +replacing mnemonics with corresponding opcodes). If you just want to test an implementation, assembled MU8 machine code files (in the plaintext format) are stored in the `examples/assembled` subdirectory. diff --git a/examples/assembled/numjack.mu8 b/examples/assembled/numjack.mu8 new file mode 100644 index 0000000..3769fd4 --- /dev/null +++ b/examples/assembled/numjack.mu8 @@ -0,0 +1,128 @@ +1 5 1 0 50 +2 5 10 0 51 +3 5 100 0 52 +4 5 13 0 53 +5 5 16 0 54 +6 5 21 0 55 +7 5 12 0 56 +8 5 1000 0 60 +9 5 2 0 57 +10 5 36 0 58 +11 5 101 0 80 +12 5 110 0 81 +13 1 6 0 40 +14 15 50 53 1 +15 9 1 51 2 +16 10 2 0 2 +17 8 50 2 2 +18 7 0 1 2 +19 1 13 0 99 +20 5 0 0 2 +21 5 0 0 8 +22 10 1 51 4 +23 9 1 51 1 +24 10 1 0 1 +25 1 1 4 27 +26 7 51 50 4 +27 7 4 50 2 +28 8 4 50 5 +29 1 5 5 31 +30 7 50 50 8 +31 8 1 50 5 +32 1 1 5 22 +33 6 1 2 3 +34 8 8 50 5 +35 1 5 5 39 +36 8 2 56 5 +37 1 3 5 39 +38 7 51 50 3 +39 1 13 0 99 +40 3 58 58 1 +41 3 60 60 0 +42 1 0 60 128 +43 4 61 61 0 +44 8 60 61 60 +45 1 2 60 128 +46 6 0 48 99 +47 1 6 0 14 +48 7 52 51 2 +49 6 1 2 62 +50 6 0 52 99 +51 1 6 0 14 +52 7 2 50 62 +53 6 1 62 1 +54 6 0 56 99 +55 1 6 0 20 +56 8 3 55 5 +57 1 0 5 40 +58 6 0 60 99 +59 1 6 0 14 +60 7 52 51 2 +61 6 1 2 63 +62 6 0 64 99 +63 1 6 0 14 +64 7 2 50 63 +65 9 62 51 5 +66 10 5 0 5 +67 3 5 5 0 +68 5 0 0 64 +69 5 0 0 9 +70 3 63 63 0 +71 4 6 6 0 +72 1 0 6 87 +73 8 6 50 5 +74 1 0 5 82 +75 8 5 50 5 +76 1 0 5 78 +77 1 6 0 89 +78 1 5 64 89 +79 8 60 61 60 +80 7 61 50 61 +81 5 1 0 9 +82 6 0 84 99 +83 1 6 0 14 +84 7 2 51 63 +85 7 50 50 64 +86 1 6 0 89 +87 5 1 0 9 +88 7 50 50 64 +89 6 1 63 1 +90 6 0 92 99 +91 1 6 0 20 +92 8 2 55 5 +93 1 4 5 96 +94 3 63 63 0 +95 1 6 0 40 +96 1 0 9 70 +97 6 1 3 72 +98 5 0 0 9 +99 6 1 62 1 +100 6 0 102 99 +101 1 6 0 20 +102 8 2 55 5 +103 8 3 54 6 +104 1 1 5 107 +105 1 1 6 107 +106 1 6 0 108 +107 1 6 0 112 +108 6 0 110 99 +109 1 6 0 14 +110 7 2 51 62 +111 1 0 9 99 +112 3 62 63 0 +113 8 63 80 1 +114 1 0 1 123 +115 8 63 81 1 +116 1 0 1 123 +117 8 3 72 1 +118 1 0 1 126 +119 1 2 1 125 +120 8 2 55 1 +121 1 1 1 125 +122 1 6 0 40 +123 9 61 57 1 +124 7 1 50 60 +125 7 61 50 60 +126 7 61 50 60 +127 1 6 0 40 +128 0 0 0 0 diff --git a/examples/numjack.mu8a b/examples/numjack.mu8a new file mode 100644 index 0000000..b9fa833 --- /dev/null +++ b/examples/numjack.mu8a @@ -0,0 +1,155 @@ +; NumberJack: a number-only blackjack game in MU8A for the mu808 VM +; How to play: +; * you start with a $1000 balance +; * on each round, enter your bet +; (the game will quit if the bet is above your balance) +; * the first card of the dealer's hand will be shown +; (hands are shown with a leading 1 that you must ignore) +; (card values are: 1 is ace, 2 to 9 are "as is", 0 is 10 to K) +; * on the first turn, select 0 (stand), 1 (hit) or 2 (double) +; * on each next turn, select 0 (stand) or 1 (hit) +; * as a result of the round, the dealer's final hand will be shown +; first and then yours +; * the dealer must draw on 16 and stand on any 17 +; * player's blackjack pays 3 to 2 +; Created by Luxferre in 2025, released into public domain + +; constants section +set 1 0 50 ; set constant 1 to loc 50 +set 10 0 51 ; set constant 10 to loc 51 +set 100 0 52 ; set constant 100 to loc 52 +set 13 0 53 ; upper RNG limit to loc 53 +set 16 0 54 ; dealer draw limit to loc 54 +set 21 0 55 ; 21 score limit to loc 55 +set 12 0 56 ; 12 score limit to loc 56 +set 1000 0 60 ; set $1000 initial balance +set 2 0 57 ; set constant 2 to loc 57 +set 36 0 58 ; set constant 36 to loc 58 +set 101 0 80 ; set the first blackjack condition to loc 80 +set 110 0 81 ; set the second blackjack condition to loc 81 +juc :main ; jump to the main code + +; subroutines section (return address is in loc 99) +; random card pulling procedure (result is in loc 2, from 0 to 9) +:gcard rnd 50 53 1 ; set loc 1 to random integer between 1 and 13 +div 1 51 2 ; divide it by 10 into loc 2 +mdf 2 0 2 ; get the floor part of the result into the same loc 2 +sub 50 2 2 ; subtract it from 1 +fma 0 1 2 ; multiply by the random choice itself, result is in loc 2 +iuc 99 ; jump to the return address in loc 99 + +; score evaluation procedure +; param is in loc 1, resulting scores are in locs 2 and 3 +:score set 0 0 2 ; set score1 to 0 +set 0 0 8 ; set aces to 0 +:slp mdf 1 51 4 ; get the current digit n of val +div 1 51 1 ; divide val by 10 +mdf 1 0 1 ; get the floor part into the same loc +jgt 4 :scn ; skip if n > 0 +fma 51 50 4 ; add 10 to n +:scn fma 4 50 2 ; add n to score1 at loc 2 +sub 4 50 5 ; subtract 1 from n to loc 5 +jne 5 :sc2 ; skip if n != 1 +fma 50 50 8 ; increment aces count at loc 8 +:sc2 sub 1 50 5 ; subtract 1 from val to loc 5 +jgt 5 :slp ; go to loop start if val > 1 +cpy 1 2 3 ; assign score2 (at loc 3) = score1 (at loc 2) +sub 8 50 5 ; subtract 1 from aces (loc 8) +jne 5 :send ; go to procedure end if aces != 1 +sub 2 56 5 ; subtract 12 from the score1 value +jge 5 :send ; go to procedure end if score1 >= 12 +fma 51 50 3 ; add 10 to score2 if there was exactly one non-busting ace +:send iuc 99 ; jump to the return address in loc 99 + +; main code section +; variables: +; 60 - balance, 61 - bet, 62 - dealer's hand, 63 - player's hand, 64 - round +:main out 58 58 1 ; output the dollar sign (if supported) +out 60 60 0 ; output the current balance +jeq 60 :end ; game over if it's zero +inp 61 61 0 ; input your bet +sub 60 61 60 ; subtract the bet from the balance +jlt 60 :end ; game over if over balance +cpy 0 :r1 99 ; set the return address +juc :gcard ; call the card generation procedure +:r1 fma 52 51 2 ; loc 2 <- 100 + 10 * card in loc 2 +cpy 1 2 62 ; copy the result into loc 62 +cpy 0 :r2 99 ; set the return address +juc :gcard ; call the card generation procedure again (in loc 2) +:r2 fma 2 50 62 ; add this result into loc 62 (dealer's hand) +cpy 1 62 1 ; copy the dealer's hand as the proc parameter +cpy 0 :r3 99 ; set the return address +juc :score ; call the scoring procedure +:r3 sub 3 55 5 ; we only have to compare score 2 to 21 +jeq 5 :main ; start the loop over if we have dealer's blackjack +cpy 0 :r4 99 ; set the return address +juc :gcard ; call the card generation procedure +:r4 fma 52 51 2 ; loc 2 <- 100 + 10 * card in loc 2 +cpy 1 2 63 ; copy the result into loc 63 (player's hand) +cpy 0 :r5 99 ; set the return address +juc :gcard ; call the card generation procedure again (in loc 2) +:r5 fma 2 50 63 ; add this result into loc 63 (player's hand) +div 62 51 5 ; get the start of the dealer's hand by dividing it by 10... +mdf 5 0 5 ; and getting the floor part +out 5 5 0 ; display the start of the dealer's hand +set 0 0 64 ; set the round index to 0 +set 0 0 9 ; set the stand flag to 0 +:rnl out 63 63 0 ; start of the inner player loop, display the player's hand +inp 6 6 0 ; input the action into loc 6 +jeq 6 :std ; jump to stand action if 0 +sub 6 50 5 ; check if 1 +jeq 5 :hit ; jump to hit action if 1 +sub 5 50 5 ; check if 2 +jeq 5 :dbl ; jump to double action if 2 +juc :skp ; jump to skip otherwise +:dbl jne 64 :skp ; skip double if not the first round +sub 60 61 60 ; subtract more balance +fma 61 50 61 ; double the bet +set 1 0 9 ; set the stand flag, proceed to the hit section next +:hit cpy 0 :r6 99 ; set the return address +juc :gcard ; call the card generation procedure +:r6 fma 2 51 63 ; loc 63 <- loc 63 * 10 + card in loc 2 +fma 50 50 64 ; increment the round index +juc :skp ; jump to skip afterwards +:std set 1 0 9 ; set the stand flag +fma 50 50 64 ; increment the round index +:skp cpy 1 63 1 ; copy the current player's hand to loc 1 as a parameter +cpy 0 :r7 99 ; set the return address +juc :score ; call the scoring procedure +:r7 sub 2 55 5 ; we only have to compare score 1 to 21 +jle 5 :nob ; reloop to beginning if the player is bust +out 63 63 0 ; output the player's hand +juc :main ; reloop +:nob jeq 9 :rnl ; repeat the inner player loop if the stand flag is 0 +cpy 1 3 72 ; copy the player score 2 to loc 72 +set 0 0 9 ; set the stand flag to 0 +:rnd cpy 1 62 1 ; start the inner dealer's loop, set the scoring procedure param +cpy 0 :r9 99 ; set the return address +juc :score ; call the scoring procedure +:r9 sub 2 55 5 ; we need to compare score 1 to 21 => loc 5 +sub 3 54 6 ; we need to compare score 2 to 16 => loc 6 +jgt 5 :s3 ; go to stand if score1 > 21 +jgt 6 :s3 ; go to stand if score2 > 16 +juc :s4 ; skip if neither +:s3 juc :s5 ; jump outside to stand +:s4 cpy 0 :r8 99 ; set the return address +juc :gcard ; call the card generation procedure +:r8 fma 2 51 62 ; loc 62 <- loc 62 * 10 + card in loc 2 +jeq 9 :rnd ; repeat the inner dealer loop if the stand flag is 0 +:s5 out 62 63 0 ; output both hands (dealer's first) +sub 63 80 1 ; compare player hand with 101 +jeq 1 :bjk ; jump to the blackjack condition if equal +sub 63 81 1 ; compare player hand with 110 +jeq 1 :bjk ; jump to the blackjack condition if equal +sub 3 72 1 ; compare dealer score 2 and player score 2 +jeq 1 :push ; push condition +jlt 1 :win ; player win condition +sub 2 55 1 ; compare dealer score 1 with 21 +jgt 1 :win ; player win condition +juc :main ; reloop to beginning +:bjk div 61 57 1 ; halve the bet +fma 1 50 60 ; add half the bet (for blackjack calc) +:win fma 61 50 60 ; add bet first time +:push fma 61 50 60 ; add bet second time +juc :main ; reloop to beginning +:end nop 0 0 0 ; just a nop label for the program end