Restore the Unused Memory Game - pret/pokecrystal GitHub Wiki

Crystal Version had hidden within its code an unused memory card game that wasn't quite finished. Luckily it is close enough to finished that we can fix it up and use it in our games!

Contents


1. Make the Game Playable

1.1. Make the Game Accessible

The first step is to give the developer (and eventually the player) a way to access the game. Let's add it into the Goldenrod Game Corner for now.

Edit maps/GoldenrodGameCorner.asm:

...
GoldenrodGameCornerCardFlipMachineScript:
	...
	end

+GoldenrodGameCornerMemoryGameScript:
+	reanchormap
+	special UnusedMemoryGame
+	closetext
+	end

GoldenrodGameCornerPrizeVendorIntroText:
	text "Welcome!"
...
GoldenrodGameCorner_MapEvents:
...
	def_coord_events

	def_bg_events
+	bg_event  0, 11, BGEVENT_READ, GoldenrodGameCornerMemoryGameScript
	bg_event  6,  6, BGEVENT_READ, GoldenrodGameCornerSlotsMachineScript
...

Now you can access the minigame by interacting with this plant here! Aweso- ... Oh, dear.


1.2. Make the Game Work

Well, it looks like we have more work to do. There's a mistake in the routine responsible for setting up the game.

Edit engine/games/memory_game.asm:

...
MemoryGame_SampleTilePlacement:
...
	jr nz, .loop
	ld [hl], c
-	dec b
+	dec c
	jr nz, .loop
	pop hl
	inc hl
	ret
...

Ah, that's better. The game is now playable.


1.3. Make the Game Quittable

But there's no way to exit. The player is stuck playing forever. A routine exists to ask the player if they'd like to continue, but the code for it is missing and just restarts the game instead. Let's fix that. We can make use of some of the strings from the other Game Corner games to help us.

Edit engine/games/memory_game.asm:

...
.AskPlayAgain:
-	call UnusedCursor_InterpretJoypad_AnimateCursor
+	ld hl, .SlotsPlayAgainText
+	call PrintText
+	call YesNoBox
	jr nc, .restart
	ld hl, wJumptableIndex
	set JUMPTABLE_EXIT_F, [hl]
	ret

.restart
	xor a
	ld [wJumptableIndex], a
	ret
+
+.SlotsPlayAgainText
+	text_far _SlotsPlayAgainText
+	text_end
...

And now the player can exit after they run out of tries! Technically the minigame is ready to be added to your game without causing any issues. But it looks terrible and doesn't play very well.


1.4. (OPTIONAL) Make the Game Cost Coins

You might want to charge the player Game Corner coins to play the game. We'll borrow some code from the Card Flip game to do this.

engine/games/memory_game.asm:

...
.RestartGame:
+	ld hl, .CardFlipPlayWithThreeCoinsText
+	call PrintText
+	call CardFlip_PrintCoinBalance
+	call YesNoBox
+	jr c, .NotPlaying
+	call .DeductCoins
+	jr c, .NotPlaying
	call MemoryGame_InitStrings
	ld hl, wJumptableIndex
	inc [hl]
	ret
+
+.NotPlaying:
+	ld hl, wJumptableIndex
+	set JUMPTABLE_EXIT_F, [hl]
+	ret
+
+.CardFlipPlayWithThreeCoinsText:
+	text_far _CardFlipPlayWithThreeCoinsText
+	text_end
+
+.DeductCoins:
+	ld a, [wCoins]
+	ld h, a
+	ld a, [wCoins + 1]
+	ld l, a
+	ld a, h
+	and a
+	jr nz, .deduct ; You have at least 256 coins.
+	ld a, l
+	cp 3
+	jr nc, .deduct ; You have at least 3 coins.
+	ld hl, .CardFlipNotEnoughCoinsText
+	call PrintText
+	scf
+	ret
+
+.deduct
+	ld de, -3
+	add hl, de
+	ld a, h
+	ld [wCoins], a
+	ld a, l
+	ld [wCoins + 1], a
+	ld de, SFX_TRANSACTION
+	call PlaySFX
+	call CardFlip_PrintCoinBalance
+	call WaitSFX
+	xor a
+	ret
+
+.CardFlipNotEnoughCoinsText:
+	text_far _CardFlipNotEnoughCoinsText
+	text_end

.ResetBoard
...
.finish_round
	call WaitPressAorB_BlinkCursor
-	ld hl, wJumptableIndex
-	inc [hl]
.AskPlayAgain:
-	ld hl, .SlotsPlayAgainText ; These lines are only here if you did the Make the game quittable step
-	call PrintText             ; The important thing is just to have nothing but the
-	call YesNoBox              ; lines below
-	jr nc, .restart
-	ld hl, wJumptableIndex
-	set JUMPTABLE_EXIT_F, [hl]
-	ret
-
-.restart
	xor a
	ld [wJumptableIndex], a
	ret
-
-.SlotsPlayAgainText
-	text_far _SlotsPlayAgainText
-	text_end
...

2. Cleaning Things Up

Now we have a working game, let's make it look pretty.


2.1. Untranslated Text

The most obvious thing upon opening the game is the glitched text in the top corners. The text in the top left indicates that any cards that appear next to it are matches you've made. This is pretty obvious and probably doesn't need a label. The text in the top right is a counter for how many tries you have left. There's also that awkward empty text box at the bottom. Let's fix this up.

Again edit engine/games/memory_game.asm:

...
.CheckTriesRemaining:
-	ld a, [wMemoryGameNumberTriesRemaining]
-	hlcoord 17, 0
-	add '0'
-	ld [hl], a
	ld hl, wMemoryGameNumberTriesRemaining
	ld a, [hl]
	and a
	jr nz, .next_try
...
.next_try
+	push hl
+	ld hl, CardFlipChooseACardText
+	call PrintText
+	call MemoryGame_PrintTries
+	pop hl
	dec [hl]
	...
MemoryGame_CheckMatch:
	ld hl, wMemoryGameCard1
	ld a, [hli]
	cp [hl]
	jr nz, .no_match

+	ld hl, .VictoryText
+	call PrintText
+	call MemoryGame_PrintTries
+	call WaitPressAorB_BlinkCursor
	...
.find_empty_slot
	ld a, [hli]
	...
	inc [hl]
	inc [hl]
	ld d, 0
-	hlcoord 5, 0
+	hlcoord 0, 0
	add hl, de
	call MemoryGame_PlaceCard
-	ld hl, .VictoryText
-	call PrintText
	ret

.no_match
	xor a
	...
	ld hl, MemoryGameDarnText
	call PrintText
+	call MemoryGame_PrintTries
+	call WaitPressAorB_BlinkCursor
	ret
...

MemoryGame_InitStrings:
	hlcoord 0, 0
	ld bc, SCREEN_AREA
	ld a, $1
	call ByteFill
-	hlcoord 0, 0
-	ld de, .japstr1
-	call PlaceString
-	hlcoord 15, 0
-	ld de, .japstr2
-	call PlaceString
-	ld hl, .dummy_text
-	call PrintText
	ret

-.dummy_text
-	db "@"
-.japstr1
-	db "とったもの@"
-.japstr2
-	db "あと かい@"
+CardFlipChooseACardText:
+	text_far _CardFlipChooseACardText
+	text_end
+
+MemoryGame_PrintTries:
+	hlcoord 9, 15
+	lb bc, 1, 9
+	call Textbox
+	hlcoord 10, 16
+	ld de, .tries_text
+	call PlaceString
+	hlcoord 17, 16
+	ld de, wMemoryGameNumberTriesRemaining
+	lb bc, PRINTNUM_LEADINGZEROS | 1, 2
+	call PrintNum
+	ret
+
+.tries_text:
+	db "TRIES@"

MemoryGame_Card2Coord:
...

Much better. MemoryGame_PrintTries is intended to look like the "COIN" indicator box from the card flip game.


2.2. Fixing the Cursor

Now it's time to fix that glitchy blob that's supposed to be a cursor. The problem is the memory being used for the graphic isn't graphical data, it's reading the card flip game's code and interpreting it as a graphic, so it's a glitchy mess. We'll bring back the graphic that was used for the game in the Spaceworld demo.

Save this image as gfx/battle_anims/pointer.png

Again edit engine/games/memory_game.asm:

...
MemoryGameLZ:
INCBIN "gfx/memory_game/memory_game.2bpp.lz"
+
+MemoryGameGFX:
+INCBIN "gfx/battle_anims/pointer.2bpp"

Edit engine/games/card_flip.asm:

DEF CARDFLIP_LIGHT_OFF EQU '♂' ; $ef
DEF CARDFLIP_LIGHT_ON  EQU '♀' ; $f5

-MemoryGameGFX:
-; Graphics for an unused Game Corner
-; game were meant to be here.
-
UnusedCursor_InterpretJoypad_AnimateCursor:
	ret

Now we've got a nice pretty pointer.


2.3. Player can move the cursor at the wrong times

The player moves the cursor to select a card, but there are times when the player shouldn't be able to move it (like after two cards are selected). There is only one time when the player should be able to move it, when they're selecting a card. Luckily, we can deal with this by disallowing cursor movement based on what phase the game is in, which is determined by the jumptable value. The cursor movement logic already partially handles this, but it is incomplete. Let's fix it.

engine/games/memory_game.asm:

...
MemoryGame_InterpretJoypad_AnimateCursor:
	ld a, [wJumptableIndex]
+	cp $3
+	jr c, .quit
+	cp $6
+	ret z
	cp $7
	jr nc, .quit
	call JoyTextDelay
...

This will ensure the cursor does not move if we're in a state when the player cannot select a card.


2.4. Make Text Print Instantly

The other game corner games turn off text scrolling to keep the arcade-y feeling of the games. Let's do the same here.

engine/games/memory_game.asm:

_MemoryGame:
+	ld hl, wOptions
+	set NO_TEXT_SCROLL, [hl]
	call .LoadGFXAndPals
	call DelayFrame
.loop
	call .JumptableLoop
	jr nc, .loop
+	ld hl, wOptions
+	res NO_TEXT_SCROLL, [hl]
	ret

3. Improving the Gameplay

We've taken care of the problems with untranslated strings and cleaned up the interface. Now it's time for some quality of life improvements.

Anything in this list is totally optional and it is up to you as a developer if you want to implement it.


3.1. Don't Hide Cards Until the Next Try

This is a minor complaint, but after you fail to find a match, the game hides the two cards and prints "Darn". We can make the game show the cards until the player presses "A" to proceed to the next try instead.

engine/games/memory_game.asm:

...
.no_match
+	ld hl, MemoryGameDarnText
+	call PrintText
+	call MemoryGame_PrintTries
+	call WaitPressAorB_BlinkCursor
+
	xor a
	ld [wMemoryGameLastCardPicked], a

	ld a, [wMemoryGameCard1Location]
	call MemoryGame_Card2Coord
	call MemoryGame_PlaceCard

	ld a, [wMemoryGameCard2Location]
	call MemoryGame_Card2Coord
	call MemoryGame_PlaceCard
-
-	ld hl, MemoryGameDarnText
-	call PrintText
-	call MemoryGame_PrintTries
-	call WaitPressAorB_BlinkCursor
	ret
...

Now we see a bit longer what the cards are. They will be hidden again after the player proceeds.


3.2. Don't Deduct a Try For a Match

Five tries isn't a lot, and there are 45 cards on the board. Let's give the player more time to clear some cards! We'll do this by rewarding good gameplay and not using up a try when a match is found.

engine/games/memory_game.asm:

...
MemoryGame_CheckMatch:
	ld hl, wMemoryGameCard1
	ld a, [hli]
	cp [hl]
	jr nz, .no_match

+	ld hl, wMemoryGameNumberTriesRemaining
+	inc [hl]
	ld hl, .VictoryText
	call PrintText
	...
.find_empty_slot
	ld a, [hli]
	...
	inc [hl]
-	inc [hl]
	ld d, 0
	hlcoord 0, 0
...

Since the player might now be able to make more than five matches (or in fact 10 matches), we'll make it so matched cards layer on top of each other, increasing the space at the top of the screen from 10 matches to 19. This is fine because it looks like the cards are simply lying on top of each other, with the bonus that it makes the matched cards more visually distinct from the board itself.


3.3. Add a "Game Over" Message

Let's let the player know specifically when the game has finished. Let's also remove an unnecessary "A press" required before revealing all the cards.

data/text/common_2.asm:

...
_MemoryGameDarnText::
	text "Darn…"
	done

+_MemoryGameGameOverText::
+	text "Game over!"
+	done
+
_StartMenuContestEndText::
...

engine/games/memory_game.asm:

...
.CheckTriesRemaining:
	ld hl, wMemoryGameNumberTriesRemaining
	ld a, [hl]
	and a
	jr nz, .next_try
+	ld hl, MemoryGameGameOverText
+	call PrintText
	ld a, $7
	ld [wJumptableIndex], a
	ret
...
.RevealAll:
-	ldh a, [hJoypadPressed]
-	and PAD_A
-	ret z
	xor a
	ld [wMemoryGameCounter], a
.RevelationLoop:
...
MemoryGameDarnText:
	text_far _MemoryGameDarnText
	text_end

+MemoryGameGameOverText:
+	text_far _MemoryGameGameOverText
+	text_end
+
MemoryGame_InitBoard:
...

3.4. Add Sounds

Cool, the game works now, but it's so incredibly quiet. The other Game Corner games have all kinds of neat noises and things that they make. Let's add some of our own here! These are completely independent from each other, so you can skip any of them.


3.4.1 Beginning Game, Board Set-Up

First, let's set up a sound for when the board gets constructed. We'll also take out a bit of unnecessary code while we're here (you can remove this whether you're adding the sound or not).

engine/games/memory_game.asm:

...
.ResetBoard:
-	call UnusedCursor_InterpretJoypad_AnimateCursor
-	jr nc, .proceed
-	ld hl, wJumptableIndex
-	set JUMPTABLE_EXIT_F, [hl]
-	ret
-
-.proceed
+	ld de, SFX_SLOT_MACHINE_START
+	call PlaySFX
	call MemoryGame_InitBoard
	ld hl, wJumptableIndex
	inc [hl]
...

3.4.2 Moving Cursor

Next we'll set up a little sound for moving the cursor from card to card.

engine/games/memory_game.asm:

...
MemoryGame_InterpretJoypad_AnimateCursor:
...
.pressed_left
	...
	add hl, bc
	dec [hl]
-	ret
+	jr .play_movement_sound

.pressed_right
	...
	add hl, bc
	inc [hl]
-	ret
+	jr .play_movement_sound

.pressed_up
	...
	add hl, bc
	ld a, [hl]
	sub 9
	ld [hl], a
-	ret
+	jr .play_movement_sound

.pressed_down
	...
	add hl, bc
	ld a, [hl]
	add 9
	ld [hl], a
-	ret
+	; fallthrough
+
+.play_movement_sound
+	ld de, SFX_POKEBALLS_PLACED_ON_TABLE
+	call PlaySFX
+	ret

MemoryGameLZ:
...

3.4.3 Selecting a Card

Basically a sound for "clicking" on a card.

engine/games/memory_game.asm:

...
.PickCard1:
	...
	ld [wMemoryGameCardChoice], a
+	ld de, SFX_STOP_SLOT
+	call PlaySFX
	ld hl, wJumptableIndex
	inc [hl]
	ret

.PickCard2:
	...
	ld [wMemoryGameCounter], a
+	ld de, SFX_STOP_SLOT
+	call PlaySFX
	ld hl, wJumptableIndex
	inc [hl]
.DelayPickAgain:
...

3.4.4 Match or No Match

Play a sound when a match was made, or when a match wasn't made.

engine/games/memory_game.asm:

MemoryGame_CheckMatch:
	ld hl, wMemoryGameCard1
	ld a, [hli]
	cp [hl]
	jr nz, .no_match

	ld hl, wMemoryGameNumberTriesRemaining ; These two lines will only be here if you implemented
	inc [hl]                               ; "Don't Deduct a Try For a Match"
	ld hl, .VictoryText
	call PrintText
	call MemoryGame_PrintTries
+	ld de, SFX_3RD_PLACE
+	call PlaySFX
+	call WaitSFX
	call WaitPressAorB_BlinkCursor

	ld a, [wMemoryGameCard1Location]	
...
.no_match
+	ld de, SFX_WRONG
+	call PlaySFX
	ld hl, MemoryGameDarnText
	call PrintText
...

3.4.5 Leaving the Game

The slot machines and card flip games both play a sad little tune when you quit. Let's add that here as well.

engine/games/memory_game.asm:

	and a
	ret

.quit
+	ld de, SFX_QUIT_SLOTS
+	call PlaySFX
+	call WaitSFX
	scf
	ret

.ExecuteJumptable:

4. Setting Up Actions

Now that the game is working, let's make it do something. There are several symbols that can appear on the cards, and there is a consistent amount of them that can appear on any given board. The number of any given card that appear on a board can be changed but that won't be covered here.

  1. 7
  2. 6
  3. 5
  4. 4
  5. 17
  6. 3
  7. 2
  8. 1

4.1. Take Action for a Match

Let's set up some code that will allow us to hook up some sort of reward system. We'll take out the generic "Yeah!" match text here because we'll be replacing it with more specific text later on.

engine/games/memory_game.asm:

...
MemoryGame_CheckMatch:
	ld hl, wMemoryGameCard1
	ld a, [hli]
	cp [hl]
	jr nz, .no_match

	ld hl, wMemoryGameNumberTriesRemaining  ; These lines are only here if you implemented
	inc [hl]                                ; "Don't Deduct a Try For a Match" above
-	ld hl, .VictoryText
-	call PrintText
-	call MemoryGame_PrintTries
-	ld de, SFX_3RD_PLACE
-	call PlaySFX             ; These lines are only here if
-	call WaitSFX             ; you implemented the sounds above
-	call WaitPressAorB_BlinkCursor

	ld a, [wMemoryGameCard1Location]
...
...
.find_empty_slot
	...
	add hl, de
	call MemoryGame_PlaceCard
+	call .HandleMatch
	ret

.no_match
...
	call MemoryGame_Card2Coord
	call MemoryGame_PlaceCard

	ret

+; Take action based on what was matched
+.HandleMatch
+	ld a, [wMemoryGameLastCardPicked]
+	cp 1 ; "Medkit"
+	jr nz, .not_medkit
+	; Fill in action here
+	ret
+.not_medkit
+	cp 2 ; "Candy"
+	jr nz, .not_candy
+	; Fill in action here
+	ret
+.not_candy
+	cp 3 ; "Clefairy doll"
+	jr nz, .not_pokedoll
+	; Fill in action here
+	ret
+.not_pokedoll
+	cp 4 ; "Star"
+	jr nz, .not_star
+	; Fill in action here
+	ret
+.not_star
+	cp 5 ; "Potion/Bottle"
+	jr nz, .not_potion
+	; Fill in action here
+	ret
+.not_potion
+	cp 6 ; "Pokeball"
+	jr nz, .not_pokeball
+	; Fill in action here
+	ret
+.not_pokeball
+	cp 7 ; "Superball"
+	ret nz ; The last icon only occurs once so we can't reward a match for it
+	; Fill in action here
+	ret

.VictoryText:
...

Ok, now we've got our skeleton for what we're going to do. So let's make some stuff happen! Any one of these can be used or combined for different matches. For example, I like to reward extra tries when medkits are matched, nothing for potions, and coins for everything else.


4.1.1. Option 1: Rewarding Extra Tries

Pretty simple, we just increase wMemoryGameNumberTriesRemaining and print a message, and we can play a sound as well. Or, if you're feeling really mean, you can set number of tries to 0 to give an instant game over! Just make sure to use a different string.

engine/games/memory_game.asm:

...
.HandleMatch
	ld a, [wMemoryGameLastCardPicked]
	cp 1 ; Medkit
	jr nz, .not_medkit
-	; Fill in action here
+	; Add an extra try
+	ld hl, wMemoryGameNumberTriesRemaining
+	inc [hl]
+	ld hl, .ExtraTryText
+	call PrintText
+	ld de, SFX_2ND_PLACE
+	call PlaySFX
+	call WaitSFX
+	call WaitPressAorB_BlinkCursor
	ret
.not_medkit
...

.VictoryText:
	...
	ret

+.ExtraTryText:
+	text_asm
+	push bc
+	hlcoord 2, 13
+	call MemoryGame_PlaceCard
+	ld hl, MemoryGameExtraTryText
+	pop bc
+	inc bc
+	inc bc
+	inc bc
+	ret
+
+MemoryGameExtraTryText:
+	text_far _MemoryGameExtraTryText
+	text_end

MemoryGameYeahText:
...

data/text/common_2.asm:

...
+_MemoryGameExtraTryText::
+	text " ! An extra"
+	line "try!"
+	done
...

4.1.2. Option 2: Rewarding Game Corner Coins

For this, we'll borrow some code from the Card Flip minigame. We'll use our own new string to indicate coins earned.

engine/games/memory_game.asm:

...
.not_pokeball
...
	ret

+.Payout:
+	ld a, c
+	push bc
+	ld [wStringBuffer2], a
+	ld hl, .VictoryText
+	call PrintText
+	call CardFlip_PrintCoinBalance
+	ld de, SFX_3RD_PLACE
+	call PlaySFX
+	call WaitSFX
+	pop bc
+
+.loop
+	push bc
+	call .IsCoinCaseFull
+	jr c, .full
+	call .AddCoinPlaySFX
+
+.full
+	call CardFlip_PrintCoinBalance
+	ld c, 2
+	call DelayFrames
+	pop bc
+	dec c
+	jr nz, .loop
+	call WaitPressAorB_BlinkCursor
+	ret
+
+.AddCoinPlaySFX:
+	ld a, [wCoins]
+	ld h, a
+	ld a, [wCoins + 1]
+	ld l, a
+	inc hl
+	ld a, h
+	ld [wCoins], a
+	ld a, l
+	ld [wCoins + 1], a
+	ld de, SFX_PAY_DAY
+	call PlaySFX
+	ret
+
+.IsCoinCaseFull:
+	ld a, [wCoins]
+	cp HIGH(MAX_COINS)
+	jr c, .less
+	jr z, .check_low
+	jr .more
+
+.check_low
+	ld a, [wCoins + 1]
+	cp LOW(MAX_COINS)
+	jr c, .less
+
+.more
+	scf
+	ret
+
+.less
+	and a
+	ret

.VictoryText:
	text_asm
	...

data/text/common_2.asm:

...
_MemoryGameYeahText::
-	text " , yeah!"
+	text " ! @"
+	text_decimal wStringBuffer2, 1, 2
+	text " Coin(s)!"
	done
...

Then, for any one of the ; Fill in action here above, you can insert a piece of code like this.

engine/games/memory_game.asm:

.not_pokedoll
	cp 4 ; "Star"
	jr nz, .not_star
-	; Fill in action here
-	ret
+	; Reward 3 coins
+	ld c, 3
+	jr .Payout
.not_star

4.1.3 Reward Nothing

You may want to have a case where you reward nothing, but if you implemented "Don't Deduct a Try For a Match" above then the match still won't cost a try.

For rewarding nothing, the code we already have will work fine and no additional work is necessary. But you may also want to print a message to the player.

engine/games/memory_game.asm:

...
.not_star
	cp 5 ; Bottle
	jr nz, .not_potion
-	; Fill in action here
+	ld hl, .NoPrizeText
+	call PrintText
+	ld de, SFX_BUMP
+	call PlaySFX
+	call WaitSFX
+	call WaitPressAorB_BlinkCursor
	ret
.not_potion
...
.VictoryText:
...
	ret

+.NoPrizeText:
+	text_asm
+	push bc
+	hlcoord 2, 13
+	call MemoryGame_PlaceCard
+	ld hl, MemoryGameNoPrizeText
+	pop bc
+	inc bc
+	inc bc
+	inc bc
+	ret
+
MemoryGameExtraTryText:
	text_far _MemoryGameExtraTryText
	text_end

+MemoryGameNoPrizeText:
+	text_far _MemoryGameNoPrizeText
+	text_end

data/text/common_2.asm:

...
+_MemoryGameNoPrizeText::
+	text " ! No prize…"
+	done
...

4.2. Take Immediate Action for a Card

You may want to take immediate action when a player turns over a card, before even determining if a match has been made. For example, the only appears once on the board. If you wanted to do something with it aside from wasting a try, you may do that as well.

In this example, I'm giving an instant game over. But you can do whatever you want, you can even jump to .Payout like above if you want to give coins instead.

engine/games/memory_game.asm:

...
.PickCard1:
	ld a, [wMemoryGameCardChoice]
	...
	call MemoryGame_Card2Coord
	call MemoryGame_PlaceCard
+	ld a, [wMemoryGameLastCardPicked]
+	cp 8
+	jr z, .GameOverCard
	xor a
	ld [wMemoryGameCardChoice], a
...
.PickCard2:
	ld a, [wMemoryGameCardChoice]
	...
	call MemoryGame_Card2Coord
	call MemoryGame_PlaceCard
+	ld a, [wMemoryGameLastCardPicked]
+	cp 8
+	jr z, .GameOverCard
	ld a, 30
	ld [wMemoryGameCounter], a
...
.PickAgain:
	call MemoryGame_CheckMatch
	ld a, $3
	ld [wJumptableIndex], a
	ret

+.GameOverCard:
+	ld de, SFX_WRONG
+	call PlaySFX
+	ld a, 0
+	ld [wMemoryGameNumberTriesRemaining], a
+	ld a, $7
+	ld [wJumptableIndex], a
+	ld hl, GameOverCardText
+	call PrintText
+	ret

.RevealAll:
...
.NoPrizeText:
	...
	inc bc
	inc bc
	ret
+
+GameOverCardText:
+	text_asm
+	push bc
+	hlcoord 2, 13
+	call MemoryGame_PlaceCard
+	ld hl, MemoryGameGameOverCardText
+	pop bc
+	inc bc
+	inc bc
+	inc bc
+	ret
...
MemoryGameGameOverText:
	text_far _MemoryGameGameOverText
	text_end

+MemoryGameGameOverCardText:
+	text_far _MemoryGameGameOverCardText
+	text_end
...

data/text/common_2.asm:

...
+_MemoryGameGameOverCardText::
+	text " ! Game"
+	line "over…"
+	prompt
...