2023-07-14 14:15:58 +00:00
|
|
|
package raffle
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/rand"
|
|
|
|
"encoding/binary"
|
|
|
|
mathRand "math/rand"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
cryptRandSrc struct{}
|
|
|
|
)
|
|
|
|
|
|
|
|
var errNoCandidatesLeft = errors.New("no candidates left")
|
|
|
|
|
|
|
|
func pickWinnerFromRaffle(r raffle) (winner raffleEntry, err error) {
|
|
|
|
var maxScore float64
|
|
|
|
for _, re := range r.Entries {
|
|
|
|
if re.WasPicked {
|
|
|
|
// We skip previously picked winners and pretend they
|
|
|
|
// don't exist
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
maxScore += re.Multiplier
|
|
|
|
}
|
|
|
|
|
|
|
|
if maxScore == 0 {
|
|
|
|
return winner, errNoCandidatesLeft
|
|
|
|
}
|
|
|
|
|
|
|
|
winnerPoint := mathRand.New(cryptRandSrc{}).Float64() * maxScore //#nosec:G404 - RNG is using a secure source
|
|
|
|
|
|
|
|
for i := range r.Entries {
|
|
|
|
re := r.Entries[i]
|
|
|
|
|
|
|
|
if re.WasPicked {
|
|
|
|
// We skip previously picked winners and pretend they
|
|
|
|
// don't exist
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
winnerPoint -= re.Multiplier
|
|
|
|
if winnerPoint < 0 {
|
|
|
|
winner = re
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return winner, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cryptRandSrc) Int63() int64 {
|
|
|
|
var b [8]byte
|
2024-01-01 16:52:18 +00:00
|
|
|
if _, err := rand.Read(b[:]); err != nil {
|
|
|
|
return -1
|
|
|
|
}
|
2023-07-14 14:15:58 +00:00
|
|
|
// mask off sign bit to ensure positive number
|
|
|
|
return int64(binary.LittleEndian.Uint64(b[:]) & (1<<63 - 1))
|
|
|
|
}
|
|
|
|
|
|
|
|
// We're using a non-seedable source
|
|
|
|
func (cryptRandSrc) Seed(int64) {}
|