madness continues

This commit is contained in:
2025-03-28 13:04:53 +00:00
parent 23e0208317
commit cabc283673
12 changed files with 156 additions and 25 deletions

View File

@@ -6,7 +6,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"synlotto-website/helpers" "synlotto-website/helpers"
"synlotto-website/services" services "synlotto-website/services/tickets"
) )
func AdminTriggersHandler(db *sql.DB) http.HandlerFunc { func AdminTriggersHandler(db *sql.DB) http.HandlerFunc {

View File

@@ -11,6 +11,7 @@ import (
"strconv" "strconv"
"synlotto-website/helpers" "synlotto-website/helpers"
"synlotto-website/models" "synlotto-website/models"
draws "synlotto-website/services/draws"
"time" "time"
"github.com/gorilla/csrf" "github.com/gorilla/csrf"
@@ -312,8 +313,13 @@ func GetMyTickets(db *sql.DB) http.HandlerFunc {
continue continue
} }
t.Ball1, t.Ball2, t.Ball3 = int(b1.Int64), int(b2.Int64), int(b3.Int64) // Build primary number + bonus fields
t.Ball4, t.Ball5, t.Ball6 = int(b4.Int64), int(b5.Int64), int(b6.Int64) t.Ball1 = int(b1.Int64)
t.Ball2 = int(b2.Int64)
t.Ball3 = int(b3.Int64)
t.Ball4 = int(b4.Int64)
t.Ball5 = int(b5.Int64)
t.Ball6 = int(b6.Int64)
t.Bonus1 = helpers.IntPtrIfValid(bo1) t.Bonus1 = helpers.IntPtrIfValid(bo1)
t.Bonus2 = helpers.IntPtrIfValid(bo2) t.Bonus2 = helpers.IntPtrIfValid(bo2)
@@ -330,9 +336,19 @@ func GetMyTickets(db *sql.DB) http.HandlerFunc {
t.IsWinner = isWinner.Bool t.IsWinner = isWinner.Bool
} }
// Build balls slices (for template use)
t.Balls = helpers.BuildBallsSlice(t) t.Balls = helpers.BuildBallsSlice(t)
t.BonusBalls = helpers.BuildBonusSlice(t) t.BonusBalls = helpers.BuildBonusSlice(t)
// 🎯 Get the actual draw info (used to show which numbers matched)
draw := draws.GetDrawResultForTicket(db, t.GameType, t.DrawDate)
t.MatchedDraw = draw
// ✅ DEBUG
log.Printf("✅ Ticket #%d", t.Id)
log.Printf("Balls: %v", t.Balls)
log.Printf("DrawResult: %+v", draw)
tickets = append(tickets, t) tickets = append(tickets, t)
} }

View File

@@ -7,6 +7,7 @@ func BuildBallsSlice(t models.Ticket) []int {
if t.GameType == "Lotto" && t.Ball6 > 0 { if t.GameType == "Lotto" && t.Ball6 > 0 {
balls = append(balls, t.Ball6) balls = append(balls, t.Ball6)
} }
return balls return balls
} }
@@ -18,5 +19,6 @@ func BuildBonusSlice(t models.Ticket) []int {
if t.Bonus2 != nil { if t.Bonus2 != nil {
bonuses = append(bonuses, *t.Bonus2) bonuses = append(bonuses, *t.Bonus2)
} }
return bonuses return bonuses
} }

16
helpers/match.go Normal file
View File

@@ -0,0 +1,16 @@
package helpers
func CountMatches(a, b []int) int {
m := make(map[int]bool)
for _, n := range b {
m[n] = true
}
match := 0
for _, n := range a {
if m[n] {
match++
}
}
return match
}

View File

@@ -69,5 +69,6 @@ func InSlice(n int, list []int) bool {
return true return true
} }
} }
return false
return true
} }

View File

@@ -0,0 +1,32 @@
package matcher
import (
"synlotto-website/helpers"
"synlotto-website/models"
)
func MatchTicketToDraw(ticket models.MatchTicket, draw models.DrawResult, rules []models.PrizeRule) models.MatchResult {
mainMatches := helpers.CountMatches(ticket.Balls, draw.Balls)
bonusMatches := helpers.CountMatches(ticket.BonusBalls, draw.BonusBalls)
prizeTier := getPrizeTier(ticket.GameType, mainMatches, bonusMatches, rules)
isWinner := prizeTier != ""
return models.MatchResult{
MatchedDrawID: draw.DrawID,
MatchedMain: mainMatches,
MatchedBonus: bonusMatches,
PrizeTier: prizeTier,
IsWinner: isWinner,
}
}
func getPrizeTier(game string, main, bonus int, rules []models.PrizeRule) string {
for _, rule := range rules {
if rule.Game == game && rule.MainMatches == main && rule.BonusMatches == bonus {
return rule.Tier
}
}
return ""
}

View File

@@ -24,4 +24,5 @@ type Ticket struct {
// Used only for display these are not stored in the DB, they mirror MatchTicket structure but are populated on read. // Used only for display these are not stored in the DB, they mirror MatchTicket structure but are populated on read.
Balls []int Balls []int
BonusBalls []int BonusBalls []int
MatchedDraw DrawResult
} }

View File

@@ -8,22 +8,31 @@ import (
func GetDrawResultForTicket(db *sql.DB, game string, drawDate string) models.DrawResult { func GetDrawResultForTicket(db *sql.DB, game string, drawDate string) models.DrawResult {
var result models.DrawResult var result models.DrawResult
if game != "Thunderball" {
log.Printf("Draw lookup for unsupported game type: %s", game)
return result
}
query := ` query := `
SELECT id, ball1, ball2, ball3, ball4, ball5, bonus1 SELECT id, ball1, ball2, ball3, ball4, ball5, thunderball
FROM results_thunderball FROM results_thunderball
WHERE draw_date = ? WHERE draw_date = ?
` `
var b1, b2, b3, b4, b5, bonus sql.NullInt64
err := db.QueryRow(query, drawDate).Scan(&result.DrawID, &b1, &b2, &b3, &b4, &b5, &bonus) var b1, b2, b3, b4, b5, tb sql.NullInt64
err := db.QueryRow(query, drawDate).Scan(&result.DrawID, &b1, &b2, &b3, &b4, &b5, &tb)
if err != nil { if err != nil {
log.Printf("No draw found for %s %s: %v", game, drawDate, err) log.Printf("No draw found for %s %s: %v", game, drawDate, err)
return result return result
} }
result.GameType = game result.GameType = game
result.DrawDate = drawDate result.DrawDate = drawDate
result.Balls = []int{int(b1.Int64), int(b2.Int64), int(b3.Int64), int(b4.Int64), int(b5.Int64)} result.Balls = []int{int(b1.Int64), int(b2.Int64), int(b3.Int64), int(b4.Int64), int(b5.Int64)}
if bonus.Valid { if tb.Valid {
result.BonusBalls = []int{int(bonus.Int64)} result.BonusBalls = []int{int(tb.Int64)}
} }
return result return result
} }

View File

@@ -7,6 +7,7 @@ import (
"synlotto-website/helpers" "synlotto-website/helpers"
"synlotto-website/models" "synlotto-website/models"
"synlotto-website/rules" "synlotto-website/rules"
draws "synlotto-website/services/draws"
) )
func RunTicketMatching(db *sql.DB, triggeredBy string) (models.MatchRunStats, error) { func RunTicketMatching(db *sql.DB, triggeredBy string) (models.MatchRunStats, error) {
@@ -24,6 +25,9 @@ func RunTicketMatching(db *sql.DB, triggeredBy string) (models.MatchRunStats, er
} }
defer rows.Close() defer rows.Close()
// Buffer results to avoid writing while iterating
var pending []models.Ticket
for rows.Next() { for rows.Next() {
var t models.Ticket var t models.Ticket
var b1, b2, b3, b4, b5, b6, bo1, bo2 sql.NullInt64 var b1, b2, b3, b4, b5, b6, bo1, bo2 sql.NullInt64
@@ -45,6 +49,10 @@ func RunTicketMatching(db *sql.DB, triggeredBy string) (models.MatchRunStats, er
t.Bonus1 = helpers.IntPtrIfValid(bo1) t.Bonus1 = helpers.IntPtrIfValid(bo1)
t.Bonus2 = helpers.IntPtrIfValid(bo2) t.Bonus2 = helpers.IntPtrIfValid(bo2)
pending = append(pending, t)
}
for _, t := range pending {
matchTicket := models.MatchTicket{ matchTicket := models.MatchTicket{
ID: t.Id, ID: t.Id,
GameType: t.GameType, GameType: t.GameType,
@@ -53,14 +61,14 @@ func RunTicketMatching(db *sql.DB, triggeredBy string) (models.MatchRunStats, er
BonusBalls: helpers.BuildBonusSlice(t), BonusBalls: helpers.BuildBonusSlice(t),
} }
draw := GetDrawResultForTicket(db, t.GameType, t.DrawDate) draw := draws.GetDrawResultForTicket(db, t.GameType, t.DrawDate)
result := handlers.MatchTicketToDraw(matchTicket, draw, rules.ThunderballPrizeRules) result := handlers.MatchTicketToDraw(matchTicket, draw, rules.ThunderballPrizeRules)
if result.MatchedDrawID == 0 { if result.MatchedDrawID == 0 {
continue continue
} }
_, err = db.Exec(` _, err := db.Exec(`
UPDATE my_tickets UPDATE my_tickets
SET matched_main = ?, matched_bonus = ?, prize_tier = ?, is_winner = ? SET matched_main = ?, matched_bonus = ?, prize_tier = ?, is_winner = ?
WHERE id = ? WHERE id = ?

View File

@@ -10,6 +10,7 @@
margin-right: 6px; margin-right: 6px;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
will-change: transform;
} }
.ball-container { .ball-container {
@@ -45,16 +46,50 @@
} }
.ball.bonus { .ball.bonus {
background-color: #d8b4fe; /* Light purple */ background-color: #d8b4fe;
} }
.ball.matched { .ball.matched {
background-color: #4ade80; /* Green */ background-color: #4ade80 !important;
animation: pulse 1s ease-in-out infinite; animation: pulse 1s ease-in-out infinite;
color: white !important;
border: 2px solid red !important;
} }
.ball.game-thunderball {
background-color: #1e3a8a; /* Dark blue */
color: white;
border: 2px solid white;
border-radius: 12px; /* Remove side rounding */
}
/* Lotto main balls Orange */
.ball.game-lotto {
background-color: #f97316; /* Bright orange */
color: white;
border: none;
border-radius: 12px;
}
/* Optional: Euromillions, Set For Life define as needed */
.ball.game-euromillions {
background-color: #2563eb; /* Royal blue */
color: white;
border-radius: 50%;
}
.ball.game-setforlife {
background-color: #6b7280; /* Slate gray */
color: white;
border-radius: 50%;
}
@keyframes pulse { @keyframes pulse {
0% { transform: scale(1); } 0% { transform: scale(1); }
50% { transform: scale(1.1); } 50% { transform: scale(1.15); }
100% { transform: scale(1); } 100% { transform: scale(1); }
} }
.pulse {
animation: pulse 0.8s ease-in-out infinite;
transform-origin: center;
}

View File

@@ -24,14 +24,16 @@
<td>{{ .DrawDate }}</td> <td>{{ .DrawDate }}</td>
<td> <td>
<div class="flex flex-wrap gap-1"> <div class="flex flex-wrap gap-1">
{{ range .Balls }} {{ range $i, $ball := .Balls }}
<div class="ball">{{ . }}</div> <div class="ball {{ if inSlice $ball $.MatchedDraw.Balls }}matched pulse{{ end }}">{{ $ball }}</div>
{{ end }} {{ end }}
</div> </div>
{{ if or (gt .MatchedMain 0) (gt .MatchedBonus 0) }}
<div style="color: red; font-size: 0.75rem; margin-top: 0.25rem;"> <div class="text-xs text-gray-500 mt-1">
MatchedMain: {{ .MatchedMain }} | MatchedBonus: {{ .MatchedBonus }} {{ .MatchedMain }} match{{ if ne .MatchedMain 1 }}es{{ end }}
{{ if gt .MatchedBonus 0 }}, {{ .MatchedBonus }} bonus{{ end }}
</div> </div>
{{ end }}
</td> </td>
<td> <td>
{{ if eq .GameType "Lotto" }} {{ if eq .GameType "Lotto" }}
@@ -39,12 +41,21 @@
{{ else if gt (intVal .Bonus2) 0 }} {{ else if gt (intVal .Bonus2) 0 }}
<div class="flex flex-wrap gap-1"> <div class="flex flex-wrap gap-1">
{{ if gt (intVal .Bonus1) 0 }} {{ if gt (intVal .Bonus1) 0 }}
<div class="ball bonus">{{ intVal .Bonus1 }}</div> {{ $b1 := intVal .Bonus1 }}
<div class="ball bonus {{ if inSlice $b1 $.MatchedDraw.BonusBalls }}matched-bonus{{ end }}">
{{ $b1 }}
</div>
{{ end }} {{ end }}
<div class="ball bonus">{{ intVal .Bonus2 }}</div> {{ $b2 := intVal .Bonus2 }}
<div class="ball bonus {{ if inSlice $b2 $.MatchedDraw.BonusBalls }}matched-bonus{{ end }}">
{{ $b2 }}
</div>
</div> </div>
{{ else if gt (intVal .Bonus1) 0 }} {{ else if gt (intVal .Bonus1) 0 }}
<div class="ball bonus">{{ intVal .Bonus1 }}</div> {{ $b := intVal .Bonus1 }}
<div class="ball bonus {{ if inSlice $b $.MatchedDraw.BonusBalls }}matched-bonus{{ end }}">
{{ $b }}
</div>
{{ else }} {{ else }}
<span style="color: lightgray;"></span> <span style="color: lightgray;"></span>
{{ end }} {{ end }}

View File

@@ -1,7 +1,7 @@
{{ define "layout" }} {{ define "layout" }}
<!DOCTYPE html> <!DOCTYPE html>
<link rel="stylesheet" href="/static/css/site.css">
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@3.3.5/dist/tailwind.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/tailwindcss@3.3.5/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/site.css">
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">