Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Card Game revised (one sprite to download)

edited September 2017 in Code Sharing Posts: 121

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 :)

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.

Comments

  • 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.

  • 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!

  • edited September 2017 Posts: 121

    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?

  • Posts: 121

    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..

  • edited September 2017 Posts: 121

    @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....

  • Posts: 121

    I beleive I found the name of this game. It's Accordion Solitaire :)

  • edited September 2018 Posts: 7

    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
    
  • em2em2
    Posts: 194
    @colincooper when you post code, leave three '~' before and after it so that it displays nicely
  • dave1707dave1707 Mod
    Posts: 7,606

    I added the three ~~~

Sign In or Register to comment.