🏠 Home page > 🌖 LÖVE tutorials
A tutorial for Lua and LÖVE 11
The dealer and player are dealt two cards each. The dealer's first card is hidden from the player.
The player can hit (i.e. take another card) or stand (i.e. stop taking cards).
If the total value of the player's hand goes over 21, then they have gone bust.
Face cards (king, queen and jack) have a value of 10, and aces have a value of 11 unless this would make the total value of the hand go above 21, in which case they have a value of 1.
After the player has stood or gone bust, the dealer takes cards until the total of their hand is 17 or over.
The round is then over, and the hand with the highest total (if the total is 21 or under) wins the round.
Left click | Click on hit or stand button |
Each card is represented by a table containing a string representing its suit, and a number representing its rank. Jacks, queens and kings are represented by the numbers 11, 12 and 13.
The deck is represented by a table which initially contains one of every card.
The player's and dealer's hands are represented by tables, and cards are removed from the deck at random positions and inserted into their hands when they take cards.
The cards are assembled from the following images, with the images colored or flipped horizontally or vertically as necessary:
Each card is represented by a table containing a string representing its suit, and a number representing its rank. Jacks, queens and kings are represented by the numbers 11, 12 and 13.
A table for the deck is created containing one of every card.
function love.load() deck = {} for suitIndex, suit in ipairs({'club', 'diamond', 'heart', 'spade'}) do for rank = 1, 13 do table.insert(deck, {suit = suit, rank = rank}) -- Temporary print('suit: '..suit..', rank: '..rank) end end -- Temporary print('Total number of cards in deck: '..#deck) end
suit: club, rank: 1 suit: club, rank: 2 suit: club, rank: 3 suit: club, rank: 4 suit: club, rank: 5 suit: club, rank: 6 suit: club, rank: 7 suit: club, rank: 8 suit: club, rank: 9 suit: club, rank: 10 suit: club, rank: 11 suit: club, rank: 12 suit: club, rank: 13 suit: diamond, rank: 1 suit: diamond, rank: 2 suit: diamond, rank: 3 suit: diamond, rank: 4 suit: diamond, rank: 5 suit: diamond, rank: 6 suit: diamond, rank: 7 suit: diamond, rank: 8 suit: diamond, rank: 9 suit: diamond, rank: 10 suit: diamond, rank: 11 suit: diamond, rank: 12 suit: diamond, rank: 13 suit: heart, rank: 1 suit: heart, rank: 2 suit: heart, rank: 3 suit: heart, rank: 4 suit: heart, rank: 5 suit: heart, rank: 6 suit: heart, rank: 7 suit: heart, rank: 8 suit: heart, rank: 9 suit: heart, rank: 10 suit: heart, rank: 11 suit: heart, rank: 12 suit: heart, rank: 13 suit: spade, rank: 1 suit: spade, rank: 2 suit: spade, rank: 3 suit: spade, rank: 4 suit: spade, rank: 5 suit: spade, rank: 6 suit: spade, rank: 7 suit: spade, rank: 8 suit: spade, rank: 9 suit: spade, rank: 10 suit: spade, rank: 11 suit: spade, rank: 12 suit: spade, rank: 13 Total number of cards in deck: 52
A table for the player's hand is created.
A card is removed from the deck table at a random index between 1 and the number of cards in the deck. This card is inserted into the player's hand.
This happens again for the player's second card.
function love.load() -- etc. playerHand = {} table.insert(playerHand, table.remove(deck, love.math.random(#deck))) table.insert(playerHand, table.remove(deck, love.math.random(#deck))) -- Temporary print('Player hand:') for cardIndex, card in ipairs(playerHand) do print('suit: '..card.suit..', rank: '..card.rank) end print('Total number of cards in deck: '..#deck) end
Player hand: suit: heart, rank: 1 suit: spade, rank: 12 Total number of cards in deck: 50
For now, information about the state of the game will be displayed as text.
A table is created for the output strings, and is concatenated and drawn to the screen.
function love.draw() local output = {} table.insert(output, 'Player hand:') for cardIndex, card in ipairs(playerHand) do table.insert(output, 'suit: '..card.suit..', rank: '..card.rank) end love.graphics.print(table.concat(output, '\n'), 15, 15) end
A table is created for the dealer's hand and two random cards from the deck are inserted into it.
function love.load() -- etc. playerHand = {} table.insert(playerHand, table.remove(deck, love.math.random(#deck))) table.insert(playerHand, table.remove(deck, love.math.random(#deck))) dealerHand = {} table.insert(dealerHand, table.remove(deck, love.math.random(#deck))) table.insert(dealerHand, table.remove(deck, love.math.random(#deck))) end function love.draw() local output = {} table.insert(output, 'Player hand:') for cardIndex, card in ipairs(playerHand) do table.insert(output, 'suit: '..card.suit..', rank: '..card.rank) end table.insert(output, '') table.insert(output, 'Dealer hand:') for cardIndex, card in ipairs(dealerHand) do table.insert(output, 'suit: '..card.suit..', rank: '..card.rank) end love.graphics.print(table.concat(output, '\n'), 15, 15) end
The only difference between the code for inserting a card into the player's hand and inserting a card into the dealer's hand is the hand to insert the card into, so a function is made with the hand as a parameter.
function love.load() -- etc. local function takeCard(hand) table.insert(hand, table.remove(deck, love.math.random(#deck))) end playerHand = {} takeCard(playerHand) takeCard(playerHand) dealerHand = {} takeCard(dealerHand) takeCard(dealerHand) end
For now, the keyboard is used for input instead of on-screen buttons.
When the h key is pressed, the player takes a card from the deck.
Since takeCard is used in love.keypressed it is made global.
function love.load() -- etc. -- "local" removed function takeCard(hand) -- etc. end function love.keypressed(key) if key == 'h' then takeCard(playerHand) end end
For each hand, the rank of each card is added together to get the total value of the hand.
Currently this does not account for face cards having a value of 10 or for the value of aces sometimes being 11.
function love.draw() local function getTotal(hand) local total = 0 for cardIndex, card in ipairs(hand) do total = total + card.rank end return total end -- etc. table.insert(output, 'Total: '..getTotal(playerHand)) -- etc. table.insert(output, 'Total: '..getTotal(dealerHand)) -- etc. end
If a card's rank is higher than 10 (i.e. 11, 12 or 13), then it is a face card and its value is 10.
function love.draw() local function getTotal(hand) local total = 0 for cardIndex, card in ipairs(hand) do if card.rank > 10 then total = total + 10 else total = total + card.rank end end return total end -- etc. end
Aces have a value of 11 instead of 1 unless the value of the hand would go over 21.
First, the values of all of the cards in the hand are added together, counting aces as 1 instead of 11.
Then, if the hand has an ace and the total value of the hand is 11 or less, 10 is added to the total (10 is added instead of 11 because 1 has already been added to it). If the total value of the hand was 12 (or more), then an ace counting as 11 would make the value of the hand 22 (or more).
function love.draw() -- etc. local function getTotal(hand) local total = 0 local hasAce = false for cardIndex, card in ipairs(hand) do if card.rank > 10 then total = total + 10 else total = total + card.rank end if card.rank == 1 then hasAce = true end end if hasAce and total <= 11 then total = total + 10 end return total end -- etc. end
When the s key is pressed, the player stands and the round is over.
The player can hit only when the round is not over.
function love.load() -- etc. roundOver = false end function love.keypressed(key) if key == 'h' and not roundOver then takeCard(playerHand) elseif key == 's' then roundOver = true end end
When the round is over, the total of the player's hand is compared to the total of the dealer's hand and the winner is displayed.
Currently this does not account for busts (i.e. hands with a value of over 21).
function love.draw() -- etc. if roundOver then table.insert(output, '') if getTotal(playerHand) > getTotal(dealerHand) then table.insert(output, 'Player wins') elseif getTotal(dealerHand) > getTotal(playerHand) then table.insert(output, 'Dealer wins') else table.insert(output, 'Draw') end end -- etc. end
The player or dealer wins if they haven't gone bust, and...
function love.draw() -- etc. if roundOver then table.insert(output, '') if getTotal(playerHand) <= 21 and ( getTotal(dealerHand) > 21 or getTotal(playerHand) > getTotal(dealerHand) ) then table.insert(output, 'Player wins') elseif getTotal(dealerHand) <= 21 and ( getTotal(playerHand) > 21 or getTotal(dealerHand) > getTotal(playerHand) ) then table.insert(output, 'Dealer wins') else table.insert(output, 'Draw') end end -- etc. end
The only differences in the code determining if a hand has won is the hand in question and the opponent's hand, so a function is made.
function love.draw() -- etc. if roundOver then table.insert(output, '') local function hasHandWon(thisHand, otherHand) return getTotal(thisHand) <= 21 and ( getTotal(otherHand) > 21 or getTotal(thisHand) > getTotal(otherHand) ) end if hasHandWon(playerHand, dealerHand) then table.insert(output, 'Player wins') elseif hasHandWon(dealerHand, playerHand) then table.insert(output, 'Dealer wins') else table.insert(output, 'Draw') end end -- etc. end
When a key is pressed and the round is over, love.load is called (for now) to start another game.
function love.keypressed(key) if not roundOver then if key == 'h' then takeCard(playerHand) elseif key == 's' then roundOver = true end else love.load() end end
If the player has gone bust or the value of their hand is already 21, the round is automatically over.
Since this requires getting the total value of a hand, getTotal is moved into love.load.
function love.load() -- etc. -- Moved and "local" removed function getTotal(hand) -- etc. end end function love.keypressed(key) if not roundOver then if key == 'h' then takeCard(playerHand) if getTotal(playerHand) >= 21 then roundOver = true end elseif key == 's' then roundOver = true end else love.load() end end
If the player has stood, gone bust or has 21, the dealer takes cards while the value of their hand is less than 17.
function love.keypressed(key) if not roundOver then if key == 'h' then takeCard(playerHand) if getTotal(playerHand) >= 21 then roundOver = true end elseif key == 's' then roundOver = true end if roundOver then while getTotal(dealerHand) < 17 do takeCard(dealerHand) end end else love.load() end end
Until the round is over, the dealer's first card (i.e. the first item of the dealer's hand table) is hidden.
function love.draw() -- etc. table.insert(output, 'Dealer hand:') for cardIndex, card in ipairs(dealerHand) do if not roundOver and cardIndex == 1 then table.insert(output, '(Card hidden)') else table.insert(output, 'suit: '..card.suit..', rank: '..card.rank) end end -- etc. end
Until the round is over, the total of the dealer's hand is hidden.
function love.draw() -- etc. if roundOver then table.insert(output, 'Total: '..getTotal(dealerHand)) else table.insert(output, 'Total: ?') end -- etc. end
All of the images used for drawing cards are loaded into a table.
You can access the image files used in this tutorial by downloading the .love file linked to at the top of this page, then unzipping it by renaming the extension from .love to .zip, or by using 7-Zip and right clicking on the .love file and extracting it.
function love.load() images = {} for nameIndex, name in ipairs({ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 'pip_heart', 'pip_diamond', 'pip_club', 'pip_spade', 'mini_heart', 'mini_diamond', 'mini_club', 'mini_spade', 'card', 'card_face_down', 'face_jack', 'face_queen', 'face_king', }) do images[name] = love.graphics.newImage('images/'..name..'.png') end -- etc. end
The line of code which displays the text is removed.
The cards in the player's hand are looped through and (for now) a blank card is drawn for each one.
The background color is set to white.
function love.load() love.graphics.setBackgroundColor(1, 1, 1) -- etc. end function love.draw() -- etc. -- Removed: love.graphics.print(table.concat(output, '\n'), 15, 15) for cardIndex, card in ipairs(playerHand) do love.graphics.draw(images.card, (cardIndex - 1) * 60) end end
For now, the suit and rank of each card is drawn as text.
function love.draw() -- etc. for cardIndex, card in ipairs(playerHand) do love.graphics.setColor(1, 1, 1) love.graphics.draw(images.card, (cardIndex - 1) * 60, 0) love.graphics.setColor(0, 0, 0) love.graphics.print(card.suit, (cardIndex - 1) * 60, 0) love.graphics.print(card.rank, (cardIndex - 1) * 60, 15) end end
The code for drawing a card is made into a function, and test hands are created.
function love.draw() -- etc. local function drawCard(card, x, y) love.graphics.setColor(1, 1, 1) love.graphics.draw(images.card, x, y) love.graphics.setColor(0, 0, 0) love.graphics.print(card.suit, x, y) love.graphics.print(card.rank, x, y + 15) end local testHand1 = { {suit = 'club', rank = 1}, {suit = 'diamond', rank = 2}, {suit = 'heart', rank = 3}, {suit = 'spade', rank = 4}, {suit = 'club', rank = 5}, {suit = 'diamond', rank = 6}, {suit = 'heart', rank = 7}, } for cardIndex, card in ipairs(testHand1) do drawCard(card, (cardIndex - 1) * 60, 0) end local testHand2 = { {suit = 'spade', rank = 8}, {suit = 'club', rank = 9}, {suit = 'diamond', rank = 10}, {suit = 'heart', rank = 11}, {suit = 'spade', rank = 12}, {suit = 'club', rank = 13}, } for cardIndex, card in ipairs(testHand2) do drawCard(card, (cardIndex - 1) * 60, 80) end end
The card's rank is drawn by indexing the image table with the card's rank, which corresponds to the appropriate image file.
function love.draw() -- etc. local function drawCard(card, x, y) love.graphics.setColor(1, 1, 1) love.graphics.draw(images.card, x, y) love.graphics.setColor(0, 0, 0) love.graphics.draw(images[card.rank], x + 3, y + 4) end end
The card's rank is drawn at the bottom right corner minus the same offset that was added to the number drawn at the top.
The image is flipped both horizontally and vertically.
To calculate the image's X and Y positions, the card's width and height are needed (and they will also be used when drawing the suit and pips), and the offset from the corner is reused from drawing the top number, so variables are made for them.
function love.draw() -- etc. local function drawCard(card, x, y) love.graphics.setColor(1, 1, 1) love.graphics.draw(images.card, x, y) love.graphics.setColor(0, 0, 0) local cardWidth = 53 local cardHeight = 73 local numberOffsetX = 3 local numberOffsetY = 4 love.graphics.draw( images[card.rank], x + numberOffsetX, y + numberOffsetY ) love.graphics.draw( images[card.rank], x + cardWidth - numberOffsetX, y + cardHeight - numberOffsetY, 0, -1 ) end -- etc. end
The suit is drawn similarly to the rank.
function love.draw() -- etc. local function drawCard(card, x, y) -- etc. local suitOffsetX = 3 local suitOffsetY = 14 local suitImage = images['mini_'..card.suit] love.graphics.draw( suitImage, x + suitOffsetX, y + suitOffsetY ) love.graphics.draw( suitImage, x + cardWidth - suitOffsetX, y + cardHeight - suitOffsetY, 0, -1 ) end -- etc. end
The only differences between drawing the rank and suit are the image and the offset, so a function is made with these as parameters.
function love.draw() -- etc. local function drawCard(card, x, y) -- etc. local cardWidth = 53 local cardHeight = 73 local function drawCorner(image, offsetX, offsetY) love.graphics.draw( image, x + offsetX, y + offsetY ) love.graphics.draw( image, x + cardWidth - offsetX, y + cardHeight - offsetY, 0, -1 ) end drawCorner(images[card.rank], 3, 4) drawCorner(images['mini_'..card.suit], 3, 14) end -- etc. end
If the card's suit is diamonds or hearts, then the color of the rank and suit is red, otherwise for clubs and spades it is black.
function love.draw() -- etc. local function drawCard(card, x, y) love.graphics.setColor(1, 1, 1) love.graphics.draw(images.card, x, y) -- Removed: love.graphics.setColor(0, 0, 0) if card.suit == 'heart' or card.suit == 'diamond' then love.graphics.setColor(.89, .06, .39) else love.graphics.setColor(.2, .2, .2) end -- etc. end end
If the card's rank is 11, 12 or 13, then the image for the jack, queen or king is drawn.
function love.draw() -- etc. local function drawCard(card, x, y) -- etc. if card.rank > 10 then local faceImage if card.rank == 11 then faceImage = images.face_jack elseif card.rank == 12 then faceImage = images.face_queen elseif card.rank == 13 then faceImage = images.face_king end love.graphics.setColor(1, 1, 1) love.graphics.draw(faceImage, x + 12, y + 11) end end -- etc. end
A pip is drawn in the middle of the card if it is an ace.
function love.draw() -- etc. local function drawCard(card, x, y) -- etc. if card.rank > 10 then -- etc. else local pipImage = images['pip_'..card.suit] if card.rank == 1 then love.graphics.draw(pipImage, x + 21, y + 31) end end end local testHand = { {suit = 'club', rank = 1}, {suit = 'diamond', rank = 1}, {suit = 'heart', rank = 1}, {suit = 'spade', rank = 1}, } for cardIndex, card in ipairs(testHand) do drawCard(card, (cardIndex - 1) * 60, 0) end end
For a rank of 2, two pips are drawn.
The first pip is in the middle on the X axis and at the top plus an offset of 7 on the Y axis.
The second pip is also in the middle on the X axis, and is mirrored on the Y axis by being offset by 7 from the bottom and drawn flipped both horizontally and vertically, similarly to the rank and suit.
function love.draw() -- etc. local function drawCard(card, x, y) -- etc. if card.rank > 10 then -- etc. else local pipImage = images['pip_'..card.suit] local pipWidth = 11 if card.rank == 1 then love.graphics.draw( pipImage, x + 21, y + 31 ) elseif card.rank == 2 then love.graphics.draw( pipImage, x + 21, y + 7 ) love.graphics.draw( pipImage, x + 21 + pipWidth, y + cardHeight - 7, 0, -1 ) end end end local testHand = { {suit = 'club', rank = 2}, {suit = 'diamond', rank = 2}, {suit = 'heart', rank = 2}, {suit = 'spade', rank = 2}, } -- etc. end
Positions are reused, so they are made into variables.
function love.draw() -- etc. local pipImage = images['pip_'..card.suit] local pipWidth = 11 local xMid = 21 local yTop = 7 if card.rank == 1 then love.graphics.draw( pipImage, x + xMid, y + 31 ) elseif card.rank == 2 then love.graphics.draw( pipImage, x + xMid, y + yTop ) love.graphics.draw( pipImage, x + xMid + pipWidth, y + cardHeight - yTop, 0, -1 ) end -- etc. end
Drawing a 3 is the same as drawing both an ace and a 2.
The middle of the card on the Y axis is reused, so it is made into a variable.
function love.draw() -- etc. local function drawCard(card, x, y) -- etc. if card.rank > 10 then -- etc. else local pipImage = images['pip_'..card.suit] local pipWidth = 11 local xMid = 21 local yTop = 7 local yMid = 31 if card.rank == 1 then love.graphics.draw( pipImage, x + xMid, y + yMid ) elseif card.rank == 2 then love.graphics.draw( pipImage, x + xMid, y + yTop ) love.graphics.draw( pipImage, x + xMid + pipWidth, y + cardHeight - yTop, 0, -1 ) elseif card.rank == 3 then love.graphics.draw( pipImage, x + xMid, y + yMid ) love.graphics.draw( pipImage, x + xMid, y + yTop ) love.graphics.draw( pipImage, x + xMid + pipWidth, y + cardHeight - yTop, 0, -1 ) end end end local testHand = { {suit = 'club', rank = 3}, {suit = 'diamond', rank = 3}, {suit = 'heart', rank = 3}, {suit = 'spade', rank = 3}, } -- etc. end
The pips for rank 4 are in the same position on the Y axis as the pips for rank 2, with a different X offset and mirrored on the X axis.
function love.draw() -- etc. local function drawCard(card, x, y) -- etc. if card.rank > 10 then -- etc. else local pipImage = images['pip_'..card.suit] local pipWidth = 11 local xLeft = 11 local xMid = 21 local yTop = 7 local yMid = 31 if card.rank == 1 then love.graphics.draw( pipImage, x + xMid, y + yMid ) elseif card.rank == 2 then love.graphics.draw( pipImage, x + xMid, y + yTop ) love.graphics.draw( pipImage, x + xMid + pipWidth, y + cardHeight - yTop, 0, -1 ) elseif card.rank == 3 then love.graphics.draw( pipImage, x + xMid, y + yMid ) love.graphics.draw( pipImage, x + xMid, y + yTop ) love.graphics.draw( pipImage, x + xMid + pipWidth, y + cardHeight - yTop, 0, -1 ) elseif card.rank == 4 then love.graphics.draw( pipImage, x + xLeft, y + yTop ) love.graphics.draw( pipImage, x + cardWidth - xLeft - pipWidth, y + yTop ) love.graphics.draw( pipImage, x + xLeft + pipWidth, y + cardHeight - yTop, 0, -1 ) love.graphics.draw( pipImage, x + cardWidth - xLeft, y + cardHeight - yTop, 0, -1 ) end end end local testHand = { {suit = 'club', rank = 4}, {suit = 'diamond', rank = 4}, {suit = 'heart', rank = 4}, {suit = 'spade', rank = 4}, } -- etc. end
Pips have an X and Y offset, and can be mirrored on the X and Y axes.
A function is made with the offsets and whether to mirror on the X or Y axes as parameters.
function love.draw() -- etc. local function drawCard(card, x, y) -- etc. if card.rank > 10 then -- etc. else local function drawPip(offsetX, offsetY, mirrorX, mirrorY) local pipImage = images['pip_'..card.suit] local pipWidth = 11 love.graphics.draw( pipImage, x + offsetX, y + offsetY ) if mirrorX then love.graphics.draw( pipImage, x + cardWidth - offsetX - pipWidth, y + offsetY ) end if mirrorY then love.graphics.draw( pipImage, x + offsetX + pipWidth, y + cardHeight - offsetY, 0, -1 ) end if mirrorX and mirrorY then love.graphics.draw( pipImage, x + cardWidth - offsetX, y + cardHeight - offsetY, 0, -1 ) end end local xLeft = 11 local xMid = 21 local yTop = 7 local yMid = 31 if card.rank == 1 then drawPip(xMid, yMid) elseif card.rank == 2 then drawPip(xMid, yTop, false, true) elseif card.rank == 3 then drawPip(xMid, yTop, false, true) drawPip(xMid, yMid) elseif card.rank == 4 then drawPip(xLeft, yTop, true, true) end end end local testHand = { {suit = 'club', rank = 1}, {suit = 'diamond', rank = 2}, {suit = 'heart', rank = 3}, {suit = 'spade', rank = 4}, } -- etc. end
The rest of the ranks are drawn, with two new variables made for repeated Y offsets.
function love.draw() -- etc. local function drawCard(card, x, y) -- etc. if card.rank > 10 then -- etc. else -- etc. local xLeft = 11 local xMid = 21 local yTop = 7 local yThird = 19 local yQtr = 23 local yMid = 31 if card.rank == 1 then drawPip(xMid, yMid) elseif card.rank == 2 then drawPip(xMid, yTop, false, true) elseif card.rank == 3 then drawPip(xMid, yTop, false, true) drawPip(xMid, yMid) elseif card.rank == 4 then drawPip(xLeft, yTop, true, true) elseif card.rank == 5 then drawPip(xLeft, yTop, true, true) drawPip(xMid, yMid) elseif card.rank == 6 then drawPip(xLeft, yTop, true, true) drawPip(xLeft, yMid, true) elseif card.rank == 7 then drawPip(xLeft, yTop, true, true) drawPip(xLeft, yMid, true) drawPip(xMid, yThird) elseif card.rank == 8 then drawPip(xLeft, yTop, true, true) drawPip(xLeft, yMid, true) drawPip(xMid, yThird, false, true) elseif card.rank == 9 then drawPip(xLeft, yTop, true, true) drawPip(xLeft, yQtr, true, true) drawPip(xMid, yMid) elseif card.rank == 10 then drawPip(xLeft, yTop, true, true) drawPip(xLeft, yQtr, true, true) drawPip(xMid, 16, false, true) end end end local testHand1 = { {suit = 'club', rank = 1}, {suit = 'diamond', rank = 2}, {suit = 'heart', rank = 3}, {suit = 'spade', rank = 4}, {suit = 'club', rank = 5}, } for cardIndex, card in ipairs(testHand1) do drawCard(card, (cardIndex - 1) * 60, 0) end local testHand2 = { {suit = 'diamond', rank = 6}, {suit = 'heart', rank = 7}, {suit = 'spade', rank = 8}, {suit = 'club', rank = 9}, {suit = 'diamond', rank = 10}, } for cardIndex, card in ipairs(testHand2) do drawCard(card, (cardIndex - 1) * 60, 80) end end
The player's and dealer's hands are drawn.
They use the same space between the cards, and the same offset on the X axis, so variables are made for them.
function love.draw() -- etc. local cardSpacing = 60 local marginX = 10 for cardIndex, card in ipairs(dealerHand) do drawCard(card, ((cardIndex - 1) * cardSpacing) + marginX, 30) end for cardIndex, card in ipairs(playerHand) do drawCard(card, ((cardIndex - 1) * cardSpacing) + marginX, 140) end end
Until the round is over, the dealer's first card is hidden. Instead of drawing a blank card, the face down card image is used.
The Y position of the face down card is reused from drawing the dealer's other cards, so it is made into a variable.
function love.draw() -- etc. for cardIndex, card in ipairs(dealerHand) do local dealerMarginY = 30 if not roundOver and cardIndex == 1 then love.graphics.setColor(1, 1, 1) love.graphics.draw(images.card_face_down, marginX, dealerMarginY) else drawCard(card, ((cardIndex - 1) * cardSpacing) + marginX, dealerMarginY) end end for cardIndex, card in ipairs(playerHand) do drawCard(card, ((cardIndex - 1) * cardSpacing) + marginX, 140) end end
The total of each hand are drawn.
The dealer's total is drawn only when the round is over.
function love.draw() -- etc. love.graphics.setColor(0, 0, 0) if roundOver then love.graphics.print('Total: '..getTotal(dealerHand), marginX, 10) else love.graphics.print('Total: ?', marginX, 10) end love.graphics.print('Total: '..getTotal(playerHand), marginX, 120) end
When the round is over, the result of the game is drawn.
function love.draw() -- etc. if roundOver then local function hasHandWon(thisHand, otherHand) return getTotal(thisHand) <= 21 and ( getTotal(otherHand) > 21 or getTotal(thisHand) > getTotal(otherHand) ) end local function drawWinner(message) love.graphics.print(message, marginX, 268) end if hasHandWon(playerHand, dealerHand) then drawWinner('Player wins') elseif hasHandWon(dealerHand, playerHand) then drawWinner('Dealer wins') else drawWinner('Draw') end end end
The code relating to text output is removed.
function love.draw() -- Removed: -- local output = {} -- table.insert(output, 'Player hand:') -- for cardIndex, card in ipairs(playerHand) do -- table.insert(output, 'suit: '..card.suit..', rank: '..card.rank) -- end -- table.insert(output, 'Total: '..getTotal(playerHand)) -- table.insert(output, '') -- table.insert(output, 'Dealer hand:') -- for cardIndex, card in ipairs(dealerHand) do -- if not roundOver and cardIndex == 1 then -- table.insert(output, '(Card hidden)') -- else -- table.insert(output, 'suit: '..card.suit..', rank: '..card.rank) -- end -- end -- if roundOver then -- table.insert(output, 'Total: '..getTotal(dealerHand)) -- else -- table.insert(output, 'Total: ?') -- end -- if roundOver then -- table.insert(output, '') -- local function hasHandWon(thisHand, otherHand) -- return getTotal(thisHand) <= 21 -- and ( -- getTotal(otherHand) > 21 -- or getTotal(thisHand) > getTotal(otherHand) -- ) -- end -- if hasHandWon(playerHand, dealerHand) then -- table.insert(output, 'Player wins') -- elseif hasHandWon(dealerHand, playerHand) then -- table.insert(output, 'Dealer wins') -- else -- table.insert(output, 'Draw') -- end -- end -- etc. end
The hit and stand buttons are drawn as rectangles with text on top.
function love.draw() -- etc. love.graphics.setColor(1, .5, .2) love.graphics.rectangle('fill', 10, 230, 53, 25) love.graphics.setColor(1, 1, 1) love.graphics.print('Hit!', 26, 236) love.graphics.setColor(1, .5, .2) love.graphics.rectangle('fill', 70, 230, 53, 25) love.graphics.setColor(1, 1, 1) love.graphics.print('Stand', 78, 236) end
The "play again" button is drawn occupying the same position as the other buttons, since it will only be visible when the round is over.
function love.draw() -- etc. --[[ love.graphics.setColor(1, .5, .2) love.graphics.rectangle('fill', 10, 230, 53, 25) love.graphics.setColor(1, 1, 1) love.graphics.print('Hit!', 16, 236) love.graphics.setColor(1, .5, .2) love.graphics.rectangle('fill', 70, 230, 53, 25) love.graphics.setColor(1, 1, 1) love.graphics.print('Stand', 78, 236) --]] love.graphics.setColor(1, .5, .2) love.graphics.rectangle('fill', 10, 230, 113, 25) love.graphics.setColor(1, 1, 1) love.graphics.print('Play again', 34, 236) end
The X and Y positions of the button text is based on the X and Y positions of the buttons plus an offset.
Each button's X position is used twice, so they are made into variables.
The same Y position is used by all buttons, so it is made into a variable.
function love.draw() -- etc. local buttonY = 230 local buttonHitX = 10 love.graphics.setColor(1, .5, .2) love.graphics.rectangle('fill', buttonHitX, buttonY, 53, 25) love.graphics.setColor(1, 1, 1) love.graphics.print('Hit!', buttonHitX + 16, buttonY + 6) local buttonStandX = 70 love.graphics.setColor(1, .5, .2) love.graphics.rectangle('fill', buttonStandX, buttonY, 53, 25) love.graphics.setColor(1, 1, 1) love.graphics.print('Stand', buttonStandX + 8, buttonY + 6) --[[ local buttonPlayAgainX = 10 love.graphics.setColor(1, .5, .2) love.graphics.rectangle('fill', buttonPlayAgainX, buttonY, 113, 25) love.graphics.setColor(1, 1, 1) love.graphics.print('Play again', buttonPlayAgainX + 24, buttonY + 6) --]] end
The only difference between the code for drawing each button is the button's text, X position, width, and text offset on the X axis. A function is made with these as parameters.
function love.draw() -- etc. local function drawButton(text, buttonX, buttonWidth, textOffsetX) local buttonY = 230 love.graphics.setColor(1, .5, .2) love.graphics.rectangle('fill', buttonX, buttonY, buttonWidth, 25) love.graphics.setColor(1, 1, 1) love.graphics.print(text, buttonX + textOffsetX, buttonY + 6) end drawButton('Hit!', 10, 53, 16) drawButton('Stand', 70, 53, 8) -- drawButton('Play again', 10, 113, 24) end
The color of the rectangle changes when the mouse cursor is over it.
The cursor is over the button if...
The button height is reused from drawing the button, so a variable is made.
function love.draw() -- etc. local function drawButton(text, buttonX, buttonWidth, textOffsetX) local buttonY = 230 local buttonHeight = 25 if love.mouse.getX() >= buttonX and love.mouse.getX() < buttonX + buttonWidth and love.mouse.getY() >= buttonY and love.mouse.getY() < buttonY + buttonHeight then love.graphics.setColor(1, .8, .3) else love.graphics.setColor(1, .5, .2) end love.graphics.rectangle('fill', buttonX, buttonY, buttonWidth, buttonHeight) love.graphics.setColor(1, 1, 1) love.graphics.print(text, buttonX + textOffsetX, buttonY + 6) end -- etc. end
If a mouse button is released and the mouse is over a button, then the button has been clicked, and (for now) the name of the button is printed.
Checking if the mouse is over a button is reused from drawing the button, so it is made into a function and moved into love.load.
function love.load() -- etc. function isMouseInButton(buttonX, buttonWidth) local buttonY = 230 local buttonHeight = 25 return love.mouse.getX() >= buttonX and love.mouse.getX() < buttonX + buttonWidth and love.mouse.getY() >= buttonY and love.mouse.getY() < buttonY + buttonHeight end end function love.mousereleased() if isMouseInButton(10, 53) then print('Hit!') elseif isMouseInButton(70, 53) then print('Stand') --elseif isMouseInButton(10, 113) then -- print('Play again') end end function love.draw() -- etc. local function drawButton(text, buttonX, buttonWidth, textOffsetX) local buttonY = 230 local buttonHeight = 25 if isMouseInButton(buttonX, buttonWidth) then love.graphics.setColor(1, .8, .3) else love.graphics.setColor(1, .5, .2) end -- etc. end -- etc. end
So that the information for each button is stored in one place, a table is created for each button.
function love.load() -- etc. local buttonY = 230 local buttonHeight = 25 local textOffsetY = 6 buttonHit = { x = 10, y = buttonY, width = 53, height = buttonHeight, text = 'Hit!', textOffsetX = 16, textOffsetY = textOffsetY, } buttonStand = { x = 70, y = buttonY, width = 53, height = buttonHeight, text = 'Stand', textOffsetX = 8, textOffsetY = textOffsetY, } buttonPlayAgain = { x = 10, y = buttonY, width = 113, height = buttonHeight, text = 'Play again', textOffsetX = 24, textOffsetY = textOffsetY, } function isMouseInButton(button) -- Removed: local buttonY = 230 -- Removed: local buttonHeight = 25 return love.mouse.getX() >= button.x and love.mouse.getX() < button.x + button.width and love.mouse.getY() >= button.y and love.mouse.getY() < button.y + button.height end end function love.mousereleased() if isMouseInButton(buttonHit) then print('Hit!') elseif isMouseInButton(buttonStand) then print('Stand') --elseif isMouseInButton(buttonPlayAgain) then -- print('Play again') end end function love.draw() -- etc. local function drawButton(button) -- Removed: local buttonY = 230 -- Removed: local buttonHeight = 25 if isMouseInButton(button) then love.graphics.setColor(1, .8, .3) else love.graphics.setColor(1, .5, .2) end love.graphics.rectangle('fill', button.x, button.y, button.width, button.height) love.graphics.setColor(1, 1, 1) love.graphics.print(button.text, button.x + button.textOffsetX, button.y + button.textOffsetY) end drawButton(buttonHit) drawButton(buttonStand) -- drawButton(buttonPlayAgain) end
The hit and stand buttons are shown while the round is in progress, and the play again button is shown when the round is over.
function love.draw() -- etc. if not roundOver then drawButton(buttonHit) drawButton(buttonStand) else drawButton(buttonPlayAgain) end end
The code from love.keypressed is moved to love.mousepressed, and instead of using the keys it uses the on-screen buttons.
function love.mousereleased() if not roundOver then if isMouseInButton(buttonHit) then takeCard(playerHand) if getTotal(playerHand) >= 21 then roundOver = true end elseif isMouseInButton(buttonStand) then roundOver = true end if roundOver then while getTotal(dealerHand) < 17 do takeCard(dealerHand) end end elseif isMouseInButton(buttonPlayAgain) then love.load() end end
Only some variables need to be reset when the play again button is clicked, so a function is made.
function love.load() -- etc. function reset() deck = {} for suitIndex, suit in ipairs({'club', 'diamond', 'heart', 'spade'}) do for rank = 1, 13 do table.insert(deck, {suit = suit, rank = rank}) end end playerHand = {} takeCard(playerHand) takeCard(playerHand) dealerHand = {} takeCard(dealerHand) takeCard(dealerHand) roundOver = false end reset() end function love.mousereleased() -- etc. elseif isMouseInButton(buttonPlayAgain) then reset() end end