Card Game revised (one sprite to download)

Necessary spritesheet: https://opengameart.org/content/playing-cards-0

I’ve tried to code this decently well. I’m not totally happy: The Field.touched(touch) and Field.move functions took me over an hour… but it went pretty well. It looks a lot better than the first time i did this, I actually commented a lot of everything, it was done in less than 3 hours, and it will fit in your scratchpad :slight_smile:

Although, I forgot to program the score hahaha

Oh! Another thing that i forgot to put in is instructions. Basically, you select a card, and then you move it to the left one or three indexes. And if suit or rank matches, that makes a valid move! sorry for not animating it, but I suppose I can work on that. I just got so excited and posted this as soon as it was playable

-- Created by Tommy
-- Second version of this game (first version is messier code, and took me two weeks. This took two and a half hours to make)
-- Note that this requires you to download a single spritesheet for the cards to play the game. i do not rememer where i got it from originally (\\_?_/) shrugs
-- I named it sheet, but you can rename it in the Field.draw() function, at line 63
displayMode( FULLSCREEN )
function setup()
    Deck.setup()
    Field.deal()
    print(Field[1], "suit "..Field[1]/4, "rank ".. Field[1]/13)
end

function draw()
    background(40,40,50,1)
    Deck.draw()
    Field.draw()
end

function touched(touch)
    Deck.touched(touch)
    Field.touched(touch)
end
-- Timezone GMT-10 Hawaii Time, Date format follows MM/DD/YYYY
-- Started 4:34 PM 09-02-2017 Saturday
-- Last Modified 7:16 PM 09-02-2017
Deck = {}   --52 length array, holds cards in deck
Field = {}  --16 length array, holds cards in play
Score = {0,0,0,0,0} --ten thousands, thousands, hundreds, tens, ones places
local numberOfRanks = 13
local numberOfSuits = 4
--[[ ^Cards are represented by number values of 1-52. Rank and suit are determined with modulus 13 and modulus 4, if you change the spritesheet, make sure to change these variables. 
]]--
local width, height = 80,112    -- Card Width and Card Height. Note they use a 5:7 ratio, in real cards this would be 2.5" x 3.5"
-- used in Deck.draw, Deck.touched, Field.draw, Field.touched

--[[ If your device screen is too small or you simply don't like the way cards are draw, change the lookup table for positioning cards, has two rows. Feel free to modify 
]]--
local position = {
    vec2(50,668), vec2(150,668), vec2(250,668), vec2(350,668), 
    vec2(450,668), vec2(550,668), vec2(650,668), vec2(750,668), 
    vec2(50,338), vec2(150,338), vec2(250,338), vec2(350,338), 
    vec2(450,338), vec2(550,338), vec2(650,338), vec2(750,338) }

--positions the deck of cards
local deckposition = vec2(WIDTH-100, 168)


function Deck.setup()   --called in setup()
    for i = 1, 52 do
        Deck[i] = i
    end
    local j
    for i = 52, 2, -1 do
        j = math.random(52)
        Deck[i], Deck[j] = Deck[j], Deck[i]
    end
end

function Field.draw()   --called in draw()
    local mesh=mesh()
    for i,v in ipairs(Field) do --index determines position
        local rank = (1/numberOfRanks)*(v % numberOfRanks)
        local suit = (1/numberOfSuits)*(v % numberOfSuits)
        mesh.texture = "Project:sheet"
        mesh:addRect(position[i].x, position[i].y, width, height)
        mesh:setRectTex(i, rank, suit, 1/numberOfRanks, 1/numberOfSuits)
    end
    mesh:draw()
end

function Field.deal()   --called in Deck.touched(touch)
    if #Field < 16 and #Deck > 0 then
        Deck[#Deck], Field[#Field + 1] = nil, Deck[#Deck]
    end
end

function Deck.draw()    --called in draw()
    local mesh = mesh()
    mesh:addRect(deckposition.x, deckposition.y, 80, 112)
    if #Deck > 0 then
        mesh:setColors(0,168,0)
    else
        mesh:setColors(0)
    end
    mesh:draw()
end

function Deck.touched(touch)    --called in touched(touch)
    if touch.x > deckposition.x - width*.5 and touch.x < deckposition.x + width*.5
    and touch.y > deckposition.y - height*.5 and touch.y < deckposition.y + height*.5
    and touch.state == ENDED then
        Field.deal()    -- is this bad practice to handle field.deal inside deck.touched?
    end
end

--[[ Field.move is the game rules, index1 must be 1 or 3 higher than ind2, and either ranks or suits must be equal. During a move, the card originally at index 2 is elimnated from the game
all other cards will move up
Here's a gamemode idea: Rank up, where cards change ranks based on your moves
]]--
function Field.move(index1, index2) --called in Field.touched(touch)
    print "Starting move"
    if index1 - index2 ~= 3 and index1 - index2 ~= 1 then
        print "Illegal Move: difference between indexes must be equal to 1 or 3"
    else
        local rank1, suit1 = Field[index1]%numberOfRanks, Field[index1]%numberOfSuits
        local rank2, suit2 = Field[index2]%numberOfRanks, Field[index2]%numberOfSuits
        if rank1 ~= rank2 and suit1 ~= suit2 then
            print "Illegal Move. Either the card suits or the card ranks must match."
        else
            Field[index2] = Field[index1]
            table.remove(Field, index1)
        end
    end
end
    
function Field.touched(touch)   --called in touched(touch)
    -- This function took an hour. Once I threw out this code and started over, it was done in ten minutes
    for i,v in ipairs(position) do
        if touch.x > v.x - width*.5 and touch.x < v.x + width*.5
        and touch.y > v.y - height*.5 and touch.y < v.y + height*.5 then
            if touch.state == BEGAN then
                index1 = i
                break
            elseif touch.state == ENDED then
                index2 = i
                if index1 and index2 and index1 ~= index2 then
                    Field.move(index1, index2)
                end
                print(index1, index2)
                index1, index2 = nil, nil
                break
            end
        end
    end
end

Could someone give me sdvice on making an instructions ofr this game? I’ve never had to do that before.

Can you post the entire Sprite sheet as an image instead of a .zip file?

Downloading, extracting, and importing an image from a .zip file is non-trivial for most of us, and involves multiple apps.

On the other hand, an image on a webpage can be imported into the camera roll and then into a Codea project without ever leaving the Codea app.

@UberGoober

https://pasteboard.co/GJjkoJk.png

Thanks!

I didn’t realize what game this was!

This is a solitaire game my uncle taught me and that, in my experience, very few people know. One of the reasons he liked it is that it’s a solitaire game you can play without taking up much space–you can actually play the whole game while just holding all the drawn cards in one hand.

Anyway, a nice blast from the past to see this. Nice job. And yeah, you should animate it!

https://github.com/ThomasofHilo/solitare/tree/master/tabs

my ipad was refusing to charge, so i recoded this from scratch on my dads ipad, without the spritesheet, thinking my ipad would die forever (spoiler: it didnt, dirty battery port)

there’s no working installer, so its not easy to download and run…I can’t figure out how to make a working installer. apparently, its not as simple as just taking the soda installer and putting my url :pensive: if you have any knowledge on making a raw downloadable folder in ios, would be nice.

@UberGoober

also: [quote]On the other hand, an image on a webpage can be imported into the camera roll and then into a Codea project without ever leaving the Codea app.[/quote]

are you browsing from inside Codea? lol

@xThomas, yes I’m browsing from inside Codea. Why the lol?

found it funny, I didnt think of doing that.

anyway… Hmm, did you look at the second version (without sprite)? It has implemented dx and dy, but I did it without the sprite so cards are just colors and numbers

I found and used the sprite, hence my comment above.

I tried the game, all I see is blank white rectangles(16). I loaded the “sheet”, but still nothing…I changed line #63 (Documents:sheet)… Two rows of 8 rectangles only come on the screen, with one green rectangle in the lower right corner(the deck)…Please Help ! I’m only interested in card games, and the fourm has very little in this area…

@kendog400 Did you put it in quotes? Try readImage(‘Documents:sheet’)

I have modified this code to not need a spritesheet, and also to have deltaX and deltaY.

-- Created by Tommy
-- Second version of this game (first version is messier code, and took me two weeks. This took two and a half hours to make)
-- This version does not use a spritesheet
function setup()
    Deck.setup(52)
    Field.deal()
    print(Field[1], "suit "..Field[1]/4, "rank ".. Field[1]/13)
end

function draw()
    background(40,40,50,1)
    Deck.draw()
    Field.draw()
end

function touched(touch)
    Deck.touched(touch)
    Field.touched(touch)
end
-- Timezone GMT-10 Hawaii Time, Date format follows MM/DD/YYYY
-- Started 4:34 PM 09-02-2017 Saturday
-- Last Modified 8:11 PM 09-26-2017
Deck = {}   --52 length array, holds cards in deck
Field = {}  --16 length array, holds cards in play
Score = {0,0,0,0,0} --ten thousands, thousands, hundreds, tens, ones places
local numberOfRanks = 13
local numberOfSuits = 4
--[[ ^Cards are represented by number values of 1-52. Rank and suit are determined with modulus 13 and modulus 4, if you change the spritesheet, make sure to change these variables. 
]]--
local width, height = WIDTH/8, WIDTH/8*1.4    -- Card Width and Card Height. Note they use a 5:7 ratio, in real cards this would be 2.5" x 3.5"
-- used in Deck.draw, Deck.touched, Field.draw, Field.touched

--[[ If your device screen is too small or you simply don't like the way cards are draw, change the lookup table for positioning cards, has two rows. Feel free to modify 
]]--
local position = {}
do local x,y
    for a = 1,0,-1 do
        y = height*a
        for b = 0,7 do
            x = width*b
            table.insert(position, vec2(x,y))
        end
    end
end
local deltaX, deltaY = 0,0
--positions the deck of cards
local deckposition = vec2(0, height*2)


function Deck.setup(n)   --called in setup()
    for i = 1, n do
        Deck[i] = i
    end
    local j
    for i = n, 2, -1 do
        j = math.random(n)
        Deck[i], Deck[j] = Deck[j], Deck[i]
    end
end
--[[ There are four suitcolors, with the fifth index being the deck color

]]
local suitColors = {
    color(168,168,0)      , 
    color(0,168,0)      , 
    color(0,0,168)      , 
    color(0,168,168)    , 
    color(168,0,0)              }
function Field.draw()   --called in draw()
    for i,v in ipairs(Field) do --index determines position
        if index1 ~= i then
            fill(suitColors[v%4+1])
            RoundedRectangle(position[i].x, position[i].y, width, height, 10)
            fill(0)
            fontSize(64)
            text(v%13, position[i].x + width*.5, position[i].y + height*.5)
        end
    end
    if index1 then
        local r = Field[index1]%4+1
        fill(suitColors[r])
        RoundedRectangle(position[index1].x + deltaX, position[index1].y + deltaY, width, height, 10)
        fill(100,200)
        RoundedRectangle(position[index1].x + deltaX, position[index1].y + deltaY, width, height, 10)
        fill(0)
        fontSize(64)
        text(Field[index1]%13, position[index1].x + width*.5 + deltaX, position[index1].y + height*.5 + deltaY)
    end
end

function Field.deal()   --called in Deck.touched(touch)
    if #Field < 16 and #Deck > 0 then
        Deck[#Deck], Field[#Field + 1] = nil, Deck[#Deck]
    end
end

function Deck.draw()    --called in draw()
    fill(suitColors[5])
    RoundedRectangle(deckposition.x, deckposition.y, width, height, 10)
    fill(0)
    fontSize(32)
    text('#'..#Deck, deckposition.x + width*.5, deckposition.y + height*.5)
end

function Deck.touched(touch)    --called in touched(touch)
    if touch.x > deckposition.x and touch.x < deckposition.x + width
    and touch.y > deckposition.y and touch.y < deckposition.y + height
    and touch.state == ENDED then
        Field.deal()    -- is this bad practice to handle field.deal inside deck.touched?
    end
end

--[[ Field.move is the game rules, index1 must be 1 or 3 higher than ind2, and either ranks or suits must be equal. During a move, the card originally at index 2 is elimnated from the game
all other cards will move up
Here's a gamemode idea: Rank up, where cards change ranks based on your moves
]]--
function Field.move(index1, index2) --called in Field.touched(touch)
    print "Starting move"
    if index1 - index2 ~= 3 and index1 - index2 ~= 1 then
        print "Illegal Move: difference between indexes must be equal to 1 or 3"
    else
        local rank1, suit1 = Field[index1]%numberOfRanks, Field[index1]%numberOfSuits
        local rank2, suit2 = Field[index2]%numberOfRanks, Field[index2]%numberOfSuits
        if rank1 ~= rank2 and suit1 ~= suit2 then
            print "Illegal Move. Either the card suits or the card ranks must match."
        else
            Field[index2] = Field[index1]
            table.remove(Field, index1)
        end
    end
end

function Field.touched(touch)   --called in touched(touch)
    for i,v in ipairs(position) do
        if touch.x > v.x and touch.x < v.x + width
        and touch.y > v.y and touch.y < v.y + height then
            if touch.state == BEGAN then
                index1 = i
                break
            elseif touch.state == ENDED then
                index2 = i
                if index1 and index2 and index1 ~= index2 then
                    Field.move(index1, index2)
                end
                print(index1, index2)
                index1, index2 = nil, nil
                break
            end
        end
    end
    --added deltaXY on Sep 26
    if touch.state == MOVING and index1 then
        deltaX = deltaX + touch.deltaX
        deltaY = deltaY + touch.deltaY
    elseif touch.state == ENDED then
        deltaX, deltaY = 0,0
    end
end

--[[
This is an auxilliary function for drawing a rectangle with possibly
curved cornders.  The first four parameters specify the rectangle.
The fifth is the radius of the corner rounding.  The optional sixth is
a way for specifying which corners should be rounded by passing a
number between 0 and 15.  The first bit corresponds to the lower-left
corner and it procedes clockwise from there.
--]]

local __RRects = {}



function RoundedRectangle(x,y,w,h,s,c,a)
    c = c or 0
    w = w or 0
    h = h or 0
    if w < 0 then
        x = x + w
        w = -w
    end
    if h < 0 then
        y = y + h
        h = -h
    end
    w = math.max(w,2*s)
    h = math.max(h,2*s)
    a = a or 0
    pushMatrix()
    translate(x,y)
    rotate(a)
    local label = table.concat({w,h,s,c},",")
    if __RRects[label] then
        __RRects[label]:setColors(fill())
        __RRects[label]:draw()
    else
    local rr = mesh()
    local v = {}
    local ce = vec2(w/2,h/2)
    local n = 4
    local o,dx,dy
    for j = 1,4 do
        dx = -1 + 2*(j%2)
        dy = -1 + 2*(math.floor(j/2)%2)
        o = ce + vec2(dx * (w/2 - s), dy * (h/2 - s))
        if math.floor(c/2^(j-1))%2 == 0 then
    for i = 1,n do
        table.insert(v,o)
        table.insert(v,o + vec2(dx * s * math.cos((i-1) * math.pi/(2*n)), dy * s * math.sin((i-1) * math.pi/(2*n))))
        table.insert(v,o + vec2(dx * s * math.cos(i * math.pi/(2*n)), dy * s * math.sin(i * math.pi/(2*n))))
    end
    else
        table.insert(v,o)
        table.insert(v,o + vec2(dx * s,0))
        table.insert(v,o + vec2(dx * s,dy * s))
        table.insert(v,o)
        table.insert(v,o + vec2(0,dy * s))
        table.insert(v,o + vec2(dx * s,dy * s))
    end
    end
    rr.vertices = v
    rr:addRect(ce.x,ce.y,w,h-2*s)
    rr:addRect(ce.x,ce.y + (h-s)/2,w-2*s,s)
    rr:addRect(ce.x,ce.y - (h-s)/2,w-2*s,s)
    rr:setColors(fill())
    rr:draw()
    __RRects[label] = rr
    end
    popMatrix()
end

I dont think i put the quotes in, I’ll try that…

I beleive I found the name of this game. It’s Accordion Solitaire :slight_smile:

Made some changes to original code incorporating some features of suggested code, used a separate mesh for the deck and added some scoring

-- Card file is read in the Field.draw() function, at line 84

displayMode( FULLSCREEN )
function setup()
    highscore = readLocalData("highscore", 0) -- default to 0 if no highscore set  
    gameState = "Started"  
    Deck.setup()
    Field.deal()
--    print(Field[1], "suit "..Field[1]/4, "rank ".. Field[1]/13)
    DisplayText = "Start: "
end

function draw()
    background(40,40,50,1)
    Deck.draw()
    Field.draw()
    if gameState == "GAMEOVER" then
        if Score > highscore then
             saveLocalData( "highscore", Score )
             -- congratulate player
            DisplayText = "Congratulations a new high score!"
        end
    end
    text("HighScore= "..math.tointeger(highscore), WIDTH/2,25)
end

function touched(touch)
    Deck.touched(touch)
    Field.touched(touch)
    if #Field == 1 and #Deck == 0 then
        Score = Score + 100
        DisplayText = "Congratulations you have Won! with a score of "..Score
        gameState = GAMEOVER
    elseif #Field >> 1 and #Deck == 0 then
        DisplayText = "Keep trying you have "..#Field.." Cards left, and a Score of: "..Score   
        gameState = "GAMEOVER"
    end
end

Deck = {}   --52 length array, holds cards in deck
Field = {}  --16 length array, holds cards in play
Score = 0 -- each card remove has a face and suit value added to score
local numberOfRanks = 13 -- Ace through to King
local numberOfSuits = 4
-- ^Cards are represented by number values of 1-52. 

local width, height = 90,126   -- was 80,112
-- Card Width and Card Height. Note they use a 5:7 ratio, in real cards this would be 2.5" x 3.5"
-- used in Deck.draw, Deck.touched, Field.draw, Field.touched

--[[ If your device screen is too small or you simply don't like the way cards are drawn, change the lookup table for positioning cards, has two rows. Feel free to modify 
]]--
local position = {
    vec2(50,600), vec2(150,600), vec2(250,600), vec2(350,600), 
    vec2(450,600), vec2(550,600), vec2(650,600), vec2(750,600), 
    vec2(50,450), vec2(150,450), vec2(250,450), vec2(350,450), 
    vec2(450,450), vec2(550,450), vec2(650,450), vec2(750,450) }

--positions the deck of cards

local deckposition = vec2(WIDTH-100, 168)

function Deck.setup()   --called in setup()
    for i = 1, 52 do
        Deck[i] = i
    end
    local j
    for i = 52, 2, -1 do
        j = math.random(52)
        Deck[i], Deck[j] = Deck[j], Deck[i]
    end
end

function Field.draw()   --called in draw()
    textAlign(CENTER)
    fill(246, 246, 246, 255)
    fontSize(32)
    text(DisplayText, WIDTH/2, HEIGHT-50)
    local mesh=mesh()
    for i,v in ipairs(Field) do --index determines position
        local rank = (1/numberOfRanks)*(v % numberOfRanks)
        local suit = (1/numberOfSuits)*(v % numberOfSuits)
        mesh.texture = "Documents:sheet"
        mesh:addRect(position[i].x, position[i].y, width, height)
        mesh:setRectTex(i, rank, suit, 1/numberOfRanks, 1/numberOfSuits)
    end
    mesh:draw()
end

function Field.deal()   --called in Deck.touched(touch)
    if #Field < 16 and #Deck > 0 then
        Deck[#Deck], Field[#Field + 1] = nil, Deck[#Deck]
    end
end

function Deck.draw()    --called in draw()
    local mesh = mesh()
        mesh.texture = "Documents:CardBack"
        mesh:addRect(deckposition.x, deckposition.y, width, height)
    mesh:draw()
    -- number of cards remaining in deck
    fill(241, 240, 240, 255)
    fontSize(32) 
    text('#'..#Deck, deckposition.x, deckposition.y)
end


function Deck.touched(touch)    --called in touched(touch)
    if touch.x > deckposition.x - width*.5 and touch.x < deckposition.x + width*.5
    and touch.y > deckposition.y - height*.5 and touch.y < deckposition.y + height*.5
    and touch.state == ENDED then
        Field.deal()    -- is this bad practice to handle field.deal inside deck.touched?
    end
end

--[[ Field.move defines the game rules, index1 must be 1 or 3 higher than index2, and either ranks or suits must be equal. During a move, the card originally at index 2 is elimnated from the game, and is scored for both rank and suit, all other cards will move up
]]--
function Field.move(index1, index2) --called in Field.touched(touch)
    DisplayText = "Starting Move"
    if index1 - index2 ~= 3 and index1 - index2 ~= 1 then
        DisplayText = "Illegal Move: difference between indexes must be equal to 1 or 3"
    else
        local rank1, suit1 = Field[index1]%numberOfRanks, Field[index1]%numberOfSuits
        local rank2, suit2 = Field[index2]%numberOfRanks, Field[index2]%numberOfSuits
        print("Rank:"..rank2.."  Suit:"..suit2)
        if rank1 ~= rank2 and suit1 ~= suit2 then
            DisplayText = "Invalid Move. Either the card suits or the card rank must match."  
        else
            Field[index2] = Field[index1]
            -- suit2 order is clubs diamonds hearts spades A to king
            -- suits and ranks count from 0
            if suit2 == 0 then -- Clubs
                Score = Score + 10
            elseif suit2 == 1 then -- Diamonds
                Score = Score + 30
            elseif suit2 == 2 then -- Hearts
                Score = Score + 40
            elseif suit2 == 3 then -- Spades
                Score = Score + 20
            end
            Score = Score + rank2 + 1            
            table.remove(Field, index1)
            DisplayText = " Score = "..Score
        end
    end
end

function Field.touched(touch)   --called in touched(touch)
    for i,v in ipairs(position) do
        if touch.x > v.x - width*.5 and touch.x < v.x + width*.5
        and touch.y > v.y - height*.5 and touch.y < v.y + height*.5 then
            if touch.state == BEGAN then
                index1 = i
                break
            elseif touch.state == ENDED then
                index2 = i
                if index1 and index2 and index1 ~= index2 then
                    Field.move(index1, index2)
                end
                index1, index2 = nil, nil
                break
            end
        end
    end
end

@colincooper when you post code, leave three ‘~’ before and after it so that it displays nicely

I added the three ~~~

Somehow I can’t seem to get the sprite sheet to load…
PGM says : Card file is read in the Field.draw()…(png file)
–*******************************
mesh.texture = “Documents:sheet” – The sprite sheet
–*******************************

I tried to make changes but I can’t seem to get the sprite sheet to load…
Is this the correct way ?..
img=readImage(asset.documents.Deck)

I know PGM was written long ago and their may have been changes and updates to codea since then…But looks like a gud PGM, and I would like to get it to work…
I also like the idea of putting in lots of comments, and making it easier to break the PGM down…Any help is greatly appreicated…

You should be able to tap in the () of img=readImage() and select what you want to read. It will put the correct name format in the ().

Original PGM doesn’t have a a readImage ( ), so I placed one in the setup fx…
pic 01…I got the results of pic4

In the feild draw & Deck draw fx’s, pics 3 & 4, shows changes I made…
I see ::mesh.texture = “Documents:CardBack”, so I put a
mesh.texture = img…and I got the whole sprite sheet not a card…pic4
I took some freeze frames to show…

I uploaded 2 sprite sheets…if needed…

I found the game interesting and thought I’d try writing another version. The difference with this game is the first card selected replaces the second card selected by tapping the first card, then tapping the second. Another difference is you can go 1 or 3 cards to the left or right. The original only let you go to the left. By going either way, you get a little more strategy on your selection. I also added a score. The game starts with the sum of all the cards, 1-13 for each suit. Ace is 1 and King is 13. As you replace a card, that card value is subtracted from the score. Try to get a low score. I was able to get to 7. The lowest score is 1, but you might never get there. Best played in landscape orientation. To select a card, tap on it. If you make a mistake, tap the same card again.

PS. I was able to get down to a score of 3. The cards, Ace of spades and 2 of hearts were left. If it would have been a 2 of spades, I could have gotten a 1.

Code removed, see updated version below.