; Poker Night ; data model (lists/maps) + view{} table + age.* encrypted transport on LOAD { set %chan #game set %sb 5 set %bb 10 set %rankmap $map(A,14,K,13,Q,12,J,11,T,10,9,9,8,8,7,7,6,6,5,5,4,4,3,3,2,2) sidebar add poker "♠ Poker Night" poker_open } ; join/open alias poker { age.join %chan age.send %chan join $age.me $nick 1000 toast Joined the table — waiting for players } on SIGNAL:age_msg { ; $from = sender fp ; $1 = move type ; $2- = payload if ($1 == join) { poker_onjoin } elseif ($1 == start) { poker_onstart } elseif ($1 == act) { poker_onact } elseif ($1 == board) { poker_onboard } elseif ($1 == show) { poker_onshow } } alias poker_onjoin { ; $2 = fp, $3 = name, $4 = stack if ($has(%seat,$2) == false) { set %seat $list() ; ensure containers exist (idempotent-ish) } ; track roster as a map fp->name and an ordered list setat %name $2 $3 setat %stack $2 $4 push %seats $2 poker_render } ; start a hand alias poker_deal { set %handid $calc(%handid + 1) set %board $list() set %street preflop set %pot 0 set %tocall %bb ; secret deck (dealer only). seed kept private; revealed at showdown to verify. set %seed $age.rand(32) poker_builddeck ; deal 2 hole cards per seat, sealed to each player's +AGE key set %i 0 foreach %fp %seats { set %c1 $get(%deck,$calc(%i * 2)) set %c2 $get(%deck,$calc(%i * 2 + 1)) age.seal %fp %c1 %c2 set %i $calc(%i + 1) } ; community sits after all hole cards set %base $calc($len(%seats) * 2) ; post blinds (seats 0,1) and open betting poker_postblinds age.send %chan start %handid poker_render } alias poker_builddeck { set %deck $list() set %keyed $list() foreach %r $list(2,3,4,5,6,7,8,9,T,J,Q,K,A) { foreach %s $list(c,d,h,s) { ; keyed shuffle: sort cards by a PRF of (seed, card) push %keyed $age.sha(%seed%r%s)~%r%s } } set %keyed $sort(%keyed) foreach %k %keyed { set %card $get($split(%k,~),1) push %deck %card } } alias poker_postblinds { set %sbfp $get(%seats,0) set %bbfp $get(%seats,1) setat %bet %sbfp %sb setat %bet %bbfp %bb setat %stack %sbfp $calc($get(%stack,%sbfp) - %sb) setat %stack %bbfp $calc($get(%stack,%bbfp) - %bb) set %pot $calc(%sb + %bb) set %turn 2 ; UTG (simplified; heads-up handled by wrap) if ($len(%seats) < 3) { set %turn 0 } } ; our sealed hole cards arrive here on SIGNAL:age_deal { ; $data = " " setat %myhole me $data poker_render } ; betting on SIGNAL:poker_fold { age.send %chan act fold } on SIGNAL:poker_call { age.send %chan act call } on SIGNAL:poker_raise { age.send %chan act raise %betamount } on SIGNAL:poker_min { set %betamount %bb } on SIGNAL:poker_q { set %betamount $calc(%pot / 4) } on SIGNAL:poker_h { set %betamount $calc(%pot / 2) } on SIGNAL:poker_t { set %betamount $calc(%pot * 3 / 4) } on SIGNAL:poker_allin { set %betamount $get(%stack,$age.me) } alias poker_onact { ; $from acted: $2 = fold|check|call|raise ; $3 = amount set %fp $from if ($2 == fold) { setat %folded %fp 1 } elseif ($2 == call) { set %need $calc(%tocall - $get(%bet,%fp)) setat %bet %fp %tocall setat %stack %fp $calc($get(%stack,%fp) - %need) set %pot $calc(%pot + %need) } elseif ($2 == raise) { set %need $calc($3 - $get(%bet,%fp)) setat %bet %fp $3 setat %stack %fp $calc($get(%stack,%fp) - %need) set %pot $calc(%pot + %need) set %tocall $3 } poker_advance poker_render } alias poker_advance { ; next live seat; if betting closed, deal next street (dealer authoritative) set %turn $calc((%turn + 1) % $len(%seats)) ; round closes when every live player has matched %tocall (simplified: no "has acted" bit) set %open 0 foreach %fp %seats { if ($get(%folded,%fp) != 1) { if ($get(%bet,%fp) != %tocall) { set %open 1 } } } if (%open == 0) { poker_nextstreet } } alias poker_nextstreet { ; dealer reveals the next board card(s) from its deck if (%street == preflop) { set %street flop | poker_reveal 3 } elseif (%street == flop) { set %street turn | poker_reveal 1 } elseif (%street == turn) { set %street river | poker_reveal 1 } elseif (%street == river) { poker_showdown } set %tocall 0 } alias poker_reveal { ; $1 = count. dealer pulls from %deck at %base and broadcasts. set %n 0 while (%n < $1) { set %card $get(%deck,%base) push %board %card age.send %chan board %card set %base $calc(%base + 1) set %n $calc(%n + 1) } } on SIGNAL:poker_onboard { push %board $2 | poker_render } ; hand evaluation (returns a sortable score) alias handscore { set %ranks $list() set %hist $map() set %suits $map() foreach %card $1- { set %rv $get(%rankmap,$left(%card,1)) push %ranks %rv setat %hist %rv $calc($get(%hist,%rv) + 1) setat %suits $right(%card,1) $calc($get(%suits,$right(%card,1)) + 1) } set %ranks $sort(%ranks) set %top 0 set %pairs 0 foreach %k $keys(%hist) { set %c $get(%hist,%k) if (%c > %top) { set %top %c } if (%c == 2) { set %pairs $calc(%pairs + 1) } } set %flush 0 foreach %k $keys(%suits) { if ($get(%suits,%k) > 4) { set %flush 1 } } set %straight $hasstraight($join($sort($keys(%hist)))) set %cat 0 if (%top == 4) { set %cat 7 } elseif (%top == 3) { if (%pairs > 0) { set %cat 6 } else { set %cat 3 } } elseif (%flush == 1) { set %cat 5 } elseif (%straight == 1) { set %cat 4 } elseif (%pairs > 1) { set %cat 2 } elseif (%pairs == 1) { set %cat 1 } return $calc(%cat * 100 + $get(%ranks,$calc($len(%ranks) - 1))) } ; returns 1 if the sorted distinct rank-values ($1-) contain a 5-long run. alias hasstraight { set %prev 0 set %run 1 set %best 1 foreach %v $1- { if (%prev > 0) { if (%v == $calc(%prev + 1)) { set %run $calc(%run + 1) } else { set %run 1 } if (%run > %best) { set %best %run } } set %prev %v } if (%best > 4) { return 1 } return 0 } alias poker_showdown { set %best 0 set %winner foreach %fp %seats { if ($get(%folded,%fp) != 1) { ; needs each player's revealed hole (collected via SIGNAL:poker_onshow); board is public set %sc $handscore($get(%hole,%fp) $join(%board)) if (%sc > %best) { set %best %sc | set %winner %fp } } } setat %stack %winner $calc($get(%stack,%winner) + %pot) signal poker_win $get(%name,%winner) %pot poker_render } on SIGNAL:poker_onshow { setat %hole $from $2- } on SIGNAL:poker_win { if ($1 == $nick) { echo %chan *** you won %2 chips! } else { echo %chan *** $1 wins %2 } } ; the table view alias poker_render { set %turnfp $get(%seats,%turn) set %dealerfp $get(%seats,0) ; precompute per-seat display into maps foreach %fp %seats { set %bc #3a3350 if (%fp == %turnfp) { set %bc #ffd166 } setat %seatborder %fp %bc set %dbg #00000000 set %dl if (%fp == %dealerfp) { set %dl D | set %dbg #f0c020 } setat %seatdealer %fp %dl setat %seatdealerbg %fp %dbg set %st if ($get(%bet,%fp) > 0) { set %st BET $get(%bet,%fp) } if ($get(%folded,%fp) == 1) { set %st FOLD } setat %seatstatus %fp %st } set %myc $split($get(%myhole,me)) set %h1 $get(%myc,0) set %h2 $get(%myc,1) view { surface bg #241b3a pad 8 { column gap 12 pad 4 align center fill { ; seats around the oval — index 0 (us) anchored at the bottom ring radius 150 { foreach %fp %seats { column align center gap 2 { stack { surface circle width 54 height 54 bg #6c5ce7 border $get(%seatborder,%fp) { column align center { text $left($get(%name,%fp),1) bold color #ffffff textsize 20 } } surface circle width 20 height 20 bg $get(%seatdealerbg,%fp) align bottom { text $get(%seatdealer,%fp) bold color #1a1a1a textsize 11 } } text $get(%name,%fp) bold color #ffffff textsize 11 text $get(%stack,%fp) color #c8c8d8 textsize 11 surface bg #000000 pad 2 { text $get(%seatstatus,%fp) bold color #ffd166 textsize 10 } } } } ; the felt centre: pot + community board surface circle bg #2b2440 pad 14 { column align center gap 4 { text POT bold color #b9a8e0 textsize 12 text %pot bold color #ffffff textsize 24 row gap 4 { card $get(%board,0) width 38 | card $get(%board,1) width 38 | card $get(%board,2) width 38 | card $get(%board,3) width 38 | card $get(%board,4) width 38 } } } ; our hole cards row gap 6 { card %h1 width 50 | card %h2 width 50 } ; actions row gap 6 fill { button "FOLD" poker_fold weight 1 button "CALL" poker_call weight 1 button "RAISE" poker_raise weight 1 } row gap 4 fill { button "MIN" poker_min weight 1 button "1/4" poker_q weight 1 button "1/2" poker_h weight 1 button "3/4" poker_t weight 1 button "ALL IN" poker_allin weight 1.2 } } } } }