Samuel Preston
SE 2340 Bitmap Project
Dr. Karen Mazidi
To run this program, the follow settings must be used in MARS:
- Tools > Bitmap Display
- Must be opened and connected to MIPS
- Base address:
0x10040000 (heap data)
- Width:
512
- Height:
256
- Unit Width:
1
- Unit Height:
1
- Tools > Keyboard and Display MMIO Simulator
- Must be opened and connected to MIPS
There is only one file required to use the program, program.asm
. Once it is loaded into MARS and the settings are configured correctly, pressing assemble and run will begin the game.
Commands are inputted as characters into the keyboard section of the Keyboard and Display MMIO Simulator. Your cards will appear at the bottom of the Bitmap Display, and the dealer's will appear at the top.
This program hosts a simplified game of Blackjack.
The player has the ability to play against a dealer (the program) over multiple rounds. The program uses a virtual deck, so card counting is possible and encouraged. The program will detect when the player or dealer busts as well as who won at the end of each round. Every card will appear visually on the screen, including their suits.
There are helpful features that make this program inviting to new players. At any time, the player can see their hand's numerical value, and they do not need to worry about competing with other people. Betting is available but completely optional, and if declined, will play no part in the game.
The dealers cards will appear at the top of the screen, and yours will appear at the bottom. Your goal is to build a hand with a value greater than the dealer's but less than 21.
You determine your hand's value by totaling every card's value. The values are as follows:
- 2-10 are worth their face values (e.g. 3 is worth 3, and 5 is worth 5)
- Jacks, Queens, and Kings are all worth 10
- Aces can be worth either 1 or 11, whichever is more advantageous to the holder
You are dealt two cards at the beginning of the game, and the dealer is dealt one. If you are given an ace and a face card, you instantly win the round. Otherwise, you can draw a new card by hitting. This can be done any number of times, but if you draw a card that makes your hand's value exceed 21, you bust and lose the round. Once you are satisfied with your hand, you can stand to finish your turn.
When your turn is over, the dealer will draw a card. If their hand's value is at or below 16, they will draw again. If they go over 21, they bust and you automatically win. If they don't bust, whoever has the higher hand wins.
Betting is optional. Winning a round normally will double your bet, and winning with an initial ace and face card will 2.5x your bet. If you lose, you get nothing.
Once the cards have been dealt, you can perform actions by typing a character in the Keyboard and Display MMIO Simulator:
- h - Hit (draw another card for your hand)
- s - Stand (keep your current hand and let the dealer draw)
- v - View Value (view your hand's current value)
- i - Info (display a tutorial dialog similar to this)
- e - Exit (quit the game)
This flowchart overviews the main game loop. Specific function definitions are defined in the following section.
Every function used by the flowchart or defined in MIPS is recorded here.
All function calls formatted like function()
represent calls to the other functions defined here.
Deals a random card to the player
card = getRandomCard()
dealCard(player, card)
Deals a random card to the dealer
card = getRandomCard()
dealCard(dealer, card)
Deals a new card for either the player or dealer by displaying it on the screen. Adds the card to the individual's total and marks if they have an ace.
- Arguments:
Is dealer or player?
,card suit/rank
card_value = getCardValue(card)
if dealer:
count = read dealer card count from memory
count++
write card count back to memory
value = read dealer card total value from memory
value += card_value
write card value back to memory
if card rank is ace:
set dealer has ace flag to true
else:
count = read player card count from memory
count++
write card count back to memory
value = read player card total value from memory
value += card_value
write card value back to memory
if card rank is ace:
set player has ace flag to true
if dealer:
draw_location = top left + count * spacing
else:
draw_location = bottom left + count * spacing
renderCard(card, draw_location)
Chooses and removes a random card from the deck.
- Returns:
card suit/rank
card_count = searchDeck()
n = random int from 0 to card_count
card = searchDeck(n)
suit_bitstring = read card suit bitstring from memory
suit_bitstring -= 2 ^ card rank
write suit_bitstring back to memory
return card
Iterates through the whole deck, either counting the total cards or looking for the Nth card.
- Arguments:
-1
ornth card
- Returns:
total card count
ornth card suit/rank
total = 0
for each suit bitstring:
for each bit in suit bitstring:
if bit is 1:
total++
if total equals nth card argument:
return suit index, and bit index as rank
shift bitstring right
return total
Renders a card on the screen including the suit and rank.
- Arguments:
draw location
,card suit/rank
drawRect(draw_location, CARD_WIDTH, CARD_HEIGHT, WHITE)
if suit is spade or club:
color = black
else:
color = red
sprite_address = suit root address + suit index
draw_sprite(draw_location, sprite_address, color)
sprite_address = rank root address + rank index
draw_location = bottom right of card
draw_sprite(draw_location, sprite_address, color)
Renders a colored rectangle on screen.
- Arguments:
draw location
,width
,height
,color
for i in width:
for j in height:
store color at draw_location + i + j * screen width
Renders a sprite on the screen. Sprites are an array of words that include their dimensions and a long bitstring that will be iterated through. Every 1 in the bitstring represents a pixel that will be drawn while scanning left to right and down like a CRT.
- Arguments:
draw location
,sprite address
,color
width = read sprite width from sprite_address
height = read sprite height from sprite_address
bitstring = read sprite_address bitstring from memory
for i in width:
for j in height:
if the leftmost bit of bitstring is 1:
store color at draw_location + i + j * screen width
shift bitstring left
Returns the blackjack value of a card.
- Arguments:
card rank
- Returns:
value int
if card rank >= jack:
return 10
else:
return rank + 1
Hides the rightmost card for either the player or dealer by drawing over the card with the background color. It also decrements the individual's card count.
- Arguments:
dealer or player
if dealer:
card_count = read dealer card count from memory
card_count--
write new dealer card count to memory
draw_location = top left + card_count * card spacing
else:
card_count = read player card count from memory
card_count--
write new player card count to memory
draw_location = bottom left + card_count * card spacing
drawRect(draw_location, CARD_WIDTH, CARD_HEIGHT, BACKGROUND_COLOR)
Resets the game back to its default state to allow for another round. The deck does not get reset unless it needs to be reshuffled. Every other game data value will be set to their default values.
card_count = read player card count from memory
for each card:
popCard()
card_count = read dealer card count from memory
for each card:
popCard()
write 0 to both card counts, ace flags, and card values
total_cards = searchDeck()
if total_cards < minimum deck size:
syscall dialog to tell the user the deck is being reshuffled
for each suit bitstring:
write default suit bitstring value to memory
Decides which hand is winning and tells the user. It will automatically add 10 to their value if they have an ace since aces are already being counted as 1s.
player_value = read player value from memory
dealer_value = read dealer value from memory
player_ace = read if player has ace from memory
dealer_ace = read if dealer has ace from memory
if player has ace and player_value + 10 <= 21:
player_value += 10
if dealer has ace and dealer_value + 10 <= 21:
dealer_value += 10
if player_value > dealer_value:
bet_enabled = read if betting is enabled from memory
if bet_enabled:
bet = read bet from memory
bet *= default bet multiplier
dialog syscall to tell the user they won the bet amount
else:
dialog syscall to tell the user they won by player_value - dealer_value
else if dealer_value > player_value:
dialog syscall to tell the user they lost by dealer_value - player_value
else:
dialog syscall to tell the user they tied
Asks the user for a bet and stores it in memory.
do:
bet = syscall to prompt the user for a bet amount (float)
while bet is NaN or bet < 0
if bet == null:
bet = 0
write bet to memory
Asks the user if they want to bet during this game and stores the decision in memory.
decision = syscall to prompt the user to answer yes or no
if decision == yes:
store in memory that betting is enabled
else:
store in memory that betting is disabled
An example of an initial deal.
An example of a win for the player (no bet).
An example of typing (v) to view the value of your hand.
An example of busting.
An example of a loss for the player.
An example of what the keyboard inputs look like over a few rounds.
The sprites were created in GIMP, exported as PPM files, and parsed into the word-separated bitstrings using a python script. The source for this script is as follows:
#!/usr/bin/env python3
import sys
from textwrap import wrap
if len(sys.argv) < 2:
print("Missing file argument")
exit()
filepath = sys.argv[1]
with open(filepath) as file:
def filter_func(x):
# Ignore file type line and comments
return not (x.startswith("P") or x.startswith("#"))
lines = list(filter(filter_func, file.readlines()))
# Get sprite dimensions
width, height = map(int, lines.pop(0).split(" "))
# Remove max value line
lines.pop(0)
line_count = len(lines)
if line_count % 3 != 0:
print("The number of color values is not divisible by 3.")
exit()
if line_count != width * height * 3:
print(
f"The number of pixel values doesn't match the dimensions. Expected {width * height * 3} values, got {line_count}."
)
exit()
# Start with 1 extra word because otherwise leading 0s would be removed
bit_num = 0xFFFFFFFF
for i in range(0, line_count, 3):
color = sum(map(int, lines[i : i + 3]))
bit_num = bit_num << 1
# Draw pixel if it's black
if color == 0:
bit_num += 1
hex_string = f"{bit_num:x}"
hex_vals = wrap(hex_string, 8)
hex_vals[-1] = hex_vals[-1].ljust(8, "0")
hex_vals.pop(0) # Remove extra word
print(width, height, *map(lambda x: f"0x{x}", hex_vals), sep=",")