View difference between Paste ID: qs7sASJX and QUVafYwk
SHOW: | | - or go back to the newest paste.
1
2
--# Notes
3
--[[
4
there are two Main tabs, each with a different card game
5
6
Codea will always run whichever is on the right, because it compiles from left to right and effectively writes over the left hand one. So swap the tabs around to change the game.
7
--]]
8
--# Main
9
-- 21 card game
10
11
-- Use this function to perform your initial setup
12
function setup()
13
    --b=Backup("Cards v105")
14
    img=readImage("Dropbox:Felix") --image for back of card
15
    
16
    deal21() --simple version of 21 without betting, doesn't handle things like A being 1 or 11 properly
17
 end
18
19
function touched(touch)
20
        
21
end
22
23
--===== 21 ========
24
function deal21()
25
    parameter.watch("Score")
26
    parameter.watch("bankScore")
27
    parameter.action("Hold", function() playBank() end)
28
    parameter.action("Another card", function() AddMyCard() end) 
29
    parameter.action("Play again",function() play21Again() end) 
30
    c=Card(200,img)  --big cards 
31
    t=CardTable(10,HEIGHT-10,c,false,1,2.1)
32
    p=Pack()
33
    p:shuffle()
34
    Score=0
35
    bankScore=0
36
    cards=2
37
    for i=1,2 do
38
        local crd=t:dealCard(p,i,3.2,true) --lay out cards at fractional row intervals to look nice
39
        Score=GetScore(Score,crd)
40
    end 
41
    status="PLAY"
42
end
43
44
function playBank()
45
    bankCards=2
46
    bankScore=0
47
    for i=1,2 do
48
        local crd=t:dealCard(p,i,1,true)
49
        bankScore=GetScore(bankScore,crd)
50
    end
51
    while bankScore<16 do
52
        bankScore=addBankCard()
53
    end
54
    if bankScore>=Score and bankScore<=21 then status="LOSE" else status ="WIN" end
55
end
56
57
function GetScore(score,crd)
58
    local i,j,v,s=c:SuitValue(crd.c)
59
    if v==1 then 
60
        if score<=10 then score=score+11 else score=score+1 end
61
    else
62
        score=score+math.min(10,v)
63
    end
64
    return score
65
end
66
67
function AddMyCard()
68
    cards = cards + 1
69
    local crd=t:dealCard(p,cards,3.2,true)
70
    Score=GetScore(Score,crd)
71
    if Score>21 then status="LOSE" end
72
end
73
74
function addBankCard()
75
    bankCards = bankCards + 1
76
    local crd=t:dealCard(p,bankCards,1,true)
77
    bankScore=GetScore(bankScore,crd)
78
    return bankScore
79
end
80
81
function play21Again()
82
    deal21()
83
end
84
85
-- This function gets called once every frame
86
function draw()
87
    background(69, 146, 59, 255)
88
    t:draw()
89
    if status=="LOSE" or status=="WIN" then 
90
        pushStyle()
91
        font("Copperplate")
92
        fontSize(48)
93
        fill(255)
94
        text("You "..status.."!",300,50)
95
        popStyle() 
96
    end
97
end
98
99
100
101
--# Main2
102
-- Patience
103
104
-- Use this function to perform your initial setup
105
function setup()
106
    --b=Backup("Cards v105")
107
    img=readImage("Dropbox:Felix") --image for back of card
108
    
109
    dealPatience() --deal a patience hand. Touching a card shows its value at left. Nothing else yet
110
    
111
end
112
113
function dealPatience()
114
    c=Card(120,img)  --small card to fit on screen
115
    --deal cards
116
    t=CardTable(10,HEIGHT-10,c,true,8,1)
117
    p=Pack()
118
    p:shuffle()
119
    for i=1,7 do
120
        for j=1,8-i do
121
           t:dealCard(p,j,i,j==8-i)
122
        end
123
    end
124
end
125
126
function touched(touch)
127
        local crd1,crd2=t:touched(touch)
128
        if crd1=="pack" then print("Touched pack")
129
        elseif crd1~=nil then
130
            if crd2~=nil then
131
                print("Move card(s) from col,row "..crd1.col..","..crd1.row.." to "..crd2.col)
132
                local a,b,v1,s1=c:SuitValue(crd1.c)
133
                local a,b,v2,s2=c:SuitValue(crd2.c)
134
                if c:isOppositeColor(s1,s2) and v1==v2-1 then
135
                    local result=t:moveCardColumn(crd1.col,crd1.row,crd2.col,true)
136
                    if type(result)=="string" then print(result) end
137
                else
138
                    print("Invalid move")
139
                end
140
            else
141
                print(c:SuitValue(crd1.c))
142
            end
143
        end
144
end
145
146
function draw()
147
    background(69, 146, 59, 255)
148
    t:draw()
149
end
150
151
152
--# Card
153
Card = class()
154
--[[
155
this class builds and draws the card images from scratch and provides some utilities
156
157
--]]
158
159
--class initialises by creating card design
160
--Parameters:
161
--height of card in pixels
162
--the image to be used on the back
163
--background color on the face of the card
164
--(optional) background color on the back of the card
165
-- (optional) color along the border ofthe card
166
167
--returns nothing
168
function Card:init(height,backImg,faceColor,backColor,borderColor)
169
    self.height = height
170
    self.backImg=backImg
171
    self.f=height/200 --scale font size to card size
172
    self.faceColor=faceColor or color(255)
173
    local x=100    --default border color
174
    self.borderColor=borderColor or color(x, x, x, 255)
175
    x=x*1.5 --corners are drawn with circles which appear darker than lines, so lighten colour for them
176
    self.cornerColor=borderColor or color(x,x,x, 150)
177
    self.backColor=backColor or color(154, 199, 203, 255)
178
    self:createCard()
179
    self:createSuits()
180
    self.value={"A","2","3","4","5","6","7","8","9","10","J","Q","K"}
181
    self.suit={"S","H","D","C"}
182
end
183
184
--this and the next function build just the front and back of the card itself
185
--the main problem is rounded corners
186
--this is done by drawing circles at the corners and then overlapping rectangles forthe final effect
187
function Card:createCard()
188
    self.cardFace=self:createOutline(true)
189
    self.cardBack=self:createOutline(false)
190
end
191
192
function Card:createOutline(face)
193
    --use standard 25/35 ratio
194
    self.width=math.floor(self.height*25/35+.5)
195
    local img=image(self.width,self.height)
196
    --create rounded corner on top right
197
    local corner=0.05 --distance from end of card as percent of height
198
    local c=math.floor(corner*self.height+0.5)
199
    setContext(img)
200
    pushStyle()
201
    strokeWidth(1)
202
    stroke(self.cornerColor)
203
    if face then fill(self.faceColor) else fill(self.backColor) end
204
    ellipse(self.width-c,self.height-c,c*2)
205
    ellipse(self.width-c,c,c*2)
206
    ellipse(c,self.height-c,c*2)
207
    ellipse(c,c,c*2)
208
    if face then stroke(self.faceColor) else stroke(self.backColor) end
209
    rect(0,c,self.width,self.height-c*2)
210
    rect(c,0,self.width-c*2,self.height)
211
    stroke(self.borderColor)
212
    line(0,c,0,self.height-c)
213
    line(c,0,self.width-c,0)
214
    line(self.width,c,self.width,self.height-c)
215
    line(c,self.height,self.width-c,self.height)
216
    --do picture on back 
217
    if face~=true then
218
        sprite(self.backImg,img.width/2,img.height/2,img.width*.9)
219
    end
220
    popStyle()
221
    setContext()
222
    return img
223
end
224
225
--the suit images come from emoji
226
function Card:createSuits()
227
    font("AppleColorEmoji")
228
    self.suits={unicode2UTF8(9824),unicode2UTF8(9829),unicode2UTF8(9830),unicode2UTF8(9827)}
229
end
230
231
--draws a card at x,y with value of card (1-52), face=true if face up, a=angle in degrees (default 0)
232
function Card:draw(x,y,card,face,a)
233
    pushMatrix()
234
    translate(x+self.width/2,y-self.height/2)
235
    if a==nil then a=0 end
236
    rotate(a)
237
    if face then 
238
        if card>0 then self:drawDetails(card) end
239
    else
240
        sprite(self.cardBack,0,0)
241
    end
242
    popMatrix()
243
end
244
245
--draws the pack of cards at x,y (actually just 4 cards but it looks like a pack)
246
function Card:drawPack(x,y)
247
    for i=1,4 do
248
        local s=(i-1)*3
249
        self:draw(x+s,y-s,1,false)
250
    end  
251
end
252
253
--draws the numbers and symbols on the front of the card
254
--one parameter = card value
255
function Card:drawDetails(card)
256
    sprite(self.cardFace,0,0)
257
    pushStyle()
258
    font("SourceSansPro-Regular")
259
    fontSize(24*self.f)
260
    --calculate suit and value of card
261
    card=card-1
262
    local v=card%13 
263
    local s=(card-v)/13+1
264
    v=v+1
265
    local w=self.cardFace.width
266
    local h=self.cardFace.height
267
    if s==1 or s==4 then fill(0,0, 0, 255) else fill(255,0,0,255) end   --fill is red or black
268
    
269
    --half the images on a card are upside down
270
    --so we do them in two loops, turning the card upside down between them
271
    --where the card is not exactly symmetrical, eg for odd numbers, we only draw on the first loop
272
    for i=1,2 do
273
        if i==2 then rotate(180) end --turn 180 degrees to do second loop
274
        local u=self.suits[s]
275
        text(self.value[v],-w*.4,h*.4) --text in corner of card
276
        fontSize(16*self.f)
277
        text(u,-w*.4,h*.28) --suit image
278
        fontSize(28*self.f)
279
        local ss=.13*h
280
        --now all the symbols arranged in the middle of the card
281
        if v==1 then
282
            if i==1 then text(u,0,0) end
283
        elseif v==2 then
284
            text(u,0,.3*h)
285
        elseif v==3 then
286
            if i==1 then text(u,0,0) end
287
            text(u,0,.3*h)
288
        elseif v==4 then
289
            text(u,-w*.18,.3*h)
290
            text(u,w*.18,.3*h)
291
        elseif v==5 then
292
            text(u,-w*.18,.3*h)
293
            text(u,w*.18,.3*h)
294
            if i==1 then text(u,0,0) end
295
        elseif v==6 then
296
            text(u,-w*.18,.3*h)
297
            text(u,w*.18,.3*h)
298
            text(u,-w*.18,0) 
299
        elseif v==7 then
300
            text(u,-w*.18,.3*h)
301
            text(u,w*.18,.3*h)
302
            text(u,-w*.18,0) 
303
            if i==1 then text(u,0,.15*h) end
304
        elseif v==8 then
305
            text(u,-w*.18,.3*h)
306
            text(u,w*.18,.3*h)
307
            text(u,-w*.18,0)
308
            text(u,0,.15*h)
309
        elseif v==9 then 
310
            text(u,-w*.18,.3*h)
311
            text(u,w*.18,.3*h)
312
            if i==1 then text(u,0,0) end
313
            text(u,-w*.18,.1*h)
314
            text(u,w*.18,.1*h)
315
        elseif v==10 then
316
            text(u,-w*.18,.3*h)
317
            text(u,w*.18,.3*h)
318
            text(u,-w*.18,.1*h)
319
            text(u,w*.18,.1*h)
320
            text(u,0,.2*h)
321
        else --royalty
322
            pushStyle()
323
            font("AppleColorEmoji")
324
            fill(255) 
325
            fontSize(84*self.f)
326
            if i==1 then 
327
                if v==11 then text(unicode2UTF8(128113)) 
328
                elseif v==12 then text(unicode2UTF8(128120)) 
329
                else text(unicode2UTF8(128116)) 
330
                end
331
            end
332
            popStyle()
333
        end
334
    end
335
    popStyle()
336
end
337
338
--used by Main and other classes
339
--given a value 1-52, it returns the value and suit in text and numbers, eg S,J,1,11
340
function Card:SuitValue(c)
341
    local v=(c-1)%13
342
    local s=(c-1-v)/13+1
343
    v = v + 1
344
    return self.value[v],self.suit[s],v,s
345
end
346
347
function Card:isOppositeColor(s1,s2)
348
    if s1==s2 or s1+s2==5 then return false else return true end
349
end
350
351
function unicode2UTF8(u) --needed for emoji
352
    u = math.max(0, math.floor(u)) -- A positive integer
353
    local UTF8
354
    if u < 0x80 then          -- less than  8 bits
355
        UTF8 = string.char(u)
356
    elseif u < 0x800 then     -- less than 12 bits
357
        local b2 = u % 0x40 + 0x80
358
        local b1 = math.floor(u/0x40) + 0xC0
359
        UTF8 = string.char(b1, b2)
360
    elseif u < 0x10000 then   -- less than 16 bits
361
        local b3 = u % 0x40 + 0x80
362
        local b2 = math.floor(u/0x40) % 0x40 + 0x80
363
        local b1 = math.floor(u/0x1000) + 0xE0
364
        UTF8 = string.char(b1, b2, b3)
365
    elseif u < 0x200000 then  -- less than 22 bits
366
        local b4 = u % 0x40 + 0x80
367
        local b3 = math.floor(u/0x40) % 0x40 + 0x80
368
        local b2 = math.floor(u/0x1000) % 0x40 + 0x80
369
        local b1 = math.floor(u/0x40000) + 0xF0
370
        UTF8 = string.char(b1, b2, b3, b4)
371
    elseif u < 0x800000 then -- less than 24 bits
372
        local b5 = u % 0x40 + 0x80
373
        local b4 = math.floor(u/0x40) % 0x40 + 0x80
374
        local b3 = math.floor(u/0x1000) % 0x40 + 0x80
375
        local b2 = math.floor(u/0x40000) % 0x40 + 0x80
376
        local b1 = math.floor(u/0x1000000) + 0xF8
377
        UTF8 = string.char(b1, b2, b3, b4, b5)
378
    else
379
        print("Error: Code point too large for Codea's Lua.")
380
    end
381
    return UTF8
382
end
383
384
385
--# CardTable
386
CardTable = class()
387
--[[
388
This class handles tabular card games like patience, where all the cards are in columns and rows
389
390
Most position references are in cols and rows rather than pixels, and they can be fractional
391
The class has two tables to keep track of cards on the table.
392
cards - is simply a hash table so we can scroll through all the cards quickly
393
tableMap - is a table laid out like the table itself, so tableMap[2][3] is the card in col 2, row 3
394
Currently, what is stored in these tables is a "card" which is an array with
395
self.counter=unique id (unused)
396
x,y=actual pixel position
397
col,row=column and row
398
c=value of card (1-52)
399
face=true if face showing
400
401
--]]
402
403
--x,y are top left of table
404
--c is a reference to the card class
405
--s=whether cards stack (overlay) on each other
406
--pc,ps are the location of the pack, pc=col, ps=row
407
function CardTable:init(x,y,c,s,pc,ps)
408
    self.x,self.y = x,y
409
    self.stacked=s
410
    self.card=c
411
    self.margin=4
412
    self.cards={}
413
    self.tableMap={}
414
    self.packCol,self.packRow=pc,ps
415
    self.counter=0
416
end
417
418
--deals the next card to col,row, with face=true for face up, false for face down
419
--returns the card dealt
420
function CardTable:dealCard(pack,col,row,face)
421
    local c=pack:nextCard()
422
    if c==nil then return nil end
423
    return self:addToCol(c,col,row,face) 
424
end
425
426
--add card to column, row can be set to nil if you want the next available row
427
function CardTable:addToCol(c,col,row,face)
428
    if row==nil then row=self:findEmptyRowInCol(col) end --if row omitted,find next available
429
    local x=self:colToX(col)
430
    local y=self:rowToY(row)
431
    self.counter = self.counter + 1 
432
    local crd={id=self.counter,x=x,y=y,col=col,row=row,c=c,face=face}
433
    --table.insert(self.cards,crd)
434
    table.insert(self.cards,crd)
435
    self:updateTable(crd)
436
    return crd
437
end
438
439
function CardTable:findEmptyRowInCol(col)
440
    if self.tableMap[col]==nil then return 1 end
441
    local r=1
442
    while self.tableMap[col][r]~=nil do r = r + 1   end
443
    return r
444
end
445
446
function CardTable:removeFromCol(col,row)
447
    if self.tableMap[col][row]==nil then
448
        return "Invalid column or row"
449
    end
450
    local crd=self.tableMap[col][row]
451
    for i=1,#self.cards do 
452
        if self.cards[i]==crd then table.remove(self.cards,i) break end
453
    end
454
    self.tableMap[col][row]=nil
455
    return crd
456
end
457
458
function CardTable:UpdateCard(oldC, newC)
459
    
460
end
461
462
--tableMap 
463
function CardTable:updateTable(crd,action)
464
    if self.tableMap[crd.col]==nil then self.tableMap[crd.col]={} end
465
    self.tableMap[crd.col][crd.row]={}
466
    self.tableMap[crd.col][crd.row]=crd
467
end
468
469
--helper function returns x value from a col reference
470
--dontStack=true if cards don't overlay
471
function CardTable:colToX(col,dontStack)
472
    local h
473
    if self.stacked and dontStack~=true then h=self.card.height*.25 else h=self.card.height end
474
    return self.x+(self.card.width+self.margin)*(col-1)
475
end
476
--helper function returns y value from a row reference
477
function CardTable:rowToY(row,dontStack)
478
    local h
479
    if self.stacked and dontStack~=true then h=self.card.height*.25 else h=self.card.height end
480
    return self.y-(h+self.margin)*(row-1)
481
end
482
483
--this finds which card is under x,y, very useful for touches on the screen
484
--it is actually quite difficult to do this when cards overlap, because all the cards in
485
--a column are truncated except the last one
486
--however this can be overcome by looking at which card(s) are under x,yn and keeping
487
--the card that is furthest down (lowest y value)
488
function CardTable:cardFromXY(x,y)
489
    local w,h=self.card.width,self.card.height
490
    local c
491
    for i,crd in pairs(self.cards) do
492
        if x>=crd.x and x<=crd.x+w and y<=crd.y and y>=crd.y-h then
493
            if c==nil then c=crd elseif c.row<crd.row then c=crd end
494
        end
495
    end
496
    if c==nil then
497
        local px,py=self:colToX(self.packCol,true),self:rowToY(self.packRow,true)
498
        if x>=px and x<=px+w and y<=py and y>=py-h then
499
            c="pack"
500
        end
501
    end
502
    return c
503
end
504
505
--moves a stack of cards from the bottom of one column to the bottom of another
506
--the last optional parameter makes it illegal to move a stack unless the top card is face up, ie handles 
507
function CardTable:moveCardColumn(startCol,startRow,newCol,failIfTopCardHidden)
508
    if self.tableMap[startCol][startRow]==nil then
509
        return "Invalid column or row"
510
    end
511
    if failIfTopCardHidden and self.tableMap[startCol][startRow].face==false then 
512
        return "Hidden card cannot be moved" 
513
    end
514
    --move cards one by one
515
    local r=startRow
516
    crd=self.tableMap[startCol][r]
517
    while crd~=nil do
518
        self:removeFromCol(startCol,r)
519
        self:addToCol(crd.c,newCol,nil,crd.face)
520
        r=r+1
521
        crd=self.tableMap[startCol][r]
522
    end
523
    --turn over exposed card in original col if there is one
524
    if startRow>1 then
525
        self:turnOverCard(startCol,startRow-1)
526
    end
527
end
528
529
function CardTable:turnOverCard(col,row)
530
    crd=self.tableMap[col][row]
531
    crd.face=true
532
end
533
534
function CardTable:draw()
535
    for i,card in ipairs(self.cards) do
536
        self.card:draw(card.x,card.y,card.c,card.face)
537
    end
538
    if self.packCol~=nil then
539
        local x,y=self:colToX(self.packCol,true),self:rowToY(self.packRow,true)
540
        self.card:drawPack(x,y)
541
    end
542
    if self.moving~=nil then 
543
        self.card:draw(self.moving.x,self.moving.y,self.moving.c,self.moving.face) 
544
    end
545
end
546
547
function CardTable:touched(touch)
548
    if touch.state==BEGAN then
549
        self.cardTouched=self:cardFromXY(touch.x,touch.y)  
550
    elseif touch.state==MOVING and self.cardTouched~=nil then
551
        self.moving={x=touch.x-c.width/2,y=touch.y+c.height/2,c=self.cardTouched.c,face=self.cardTouched.face}
552
    elseif touch.state==ENDED then
553
        self.moving=nil
554
        crd=self:cardFromXY(touch.x,touch.y)  
555
        if self.cardTouched==crd then 
556
            return crd
557
        else
558
            return self.cardTouched,crd
559
        end
560
    end
561
end
562
563
564
565
566
567
568
--# Pack
569
Pack = class()
570
--[[
571
Manages a pack of cards
572
Functions:
573
574
shuffle - shuffles the pack
575
576
nextCard - returns the next card in the pack
577
--]]
578
579
function Pack:init(p)
580
    self.pack={}
581
    self:shuffle()
582
end
583
584
function Pack:shuffle()
585
    self.pack={}
586
    local t={}
587
    for i=1,52 do t[i]=i end
588
    for i=1,52 do
589
        local x=math.random(1,53-i) 
590
        self.pack[i]=t[x]
591
        t[x]=53-i
592
    end
593
    return self.pack
594
end
595
596
function Pack:nextCard()
597
    if #self.pack==0 then return nil end
598
    local c=self.pack[1]
599
    table.remove(self.pack,1)
600
    return c
601
end
602
603
function Pack:cards()
604
    return #self.pack
605
end
606
607
--# Ideas
608
609
--[[
610
611
flip card over
612
remove card
613
move x layered cards
614
fan cards like poker hand
615
n packs or infinite pack, eg casino
616
manage discard pile
617
618
--]]