Skip to content

Commit

Permalink
added giveaway prize system
Browse files Browse the repository at this point in the history
  • Loading branch information
Kesuaheli committed Dec 31, 2023
1 parent 1b990b0 commit a00a0ba
Showing 1 changed file with 245 additions and 0 deletions.
245 changes: 245 additions & 0 deletions database/giveaway.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@
package database

import (
"bytes"
"database/sql"
"encoding/json"
"fmt"
"math/rand"
"os"
"reflect"
"strings"
"time"

Expand Down Expand Up @@ -194,3 +198,244 @@ func DrawGiveawayWinner(e []GiveawayEntry) (winner GiveawayEntry, totalTickets i
})
return entries[rand.Intn(totalTickets-1)], totalTickets
}

// GiveawayPrizeType represents the type of a giveaway prize
type GiveawayPrizeType string

// GiveawayPrizeGroupSort represents the sorting type of a group of prizes
type GiveawayPrizeGroupSort string

const (
// A single giveaway prize

Check failure on line 209 in database/giveaway.go

View workflow job for this annotation

GitHub Actions / Test

comment on exported const GiveawayPrizeTypeSingle should be of the form "GiveawayPrizeTypeSingle ..."
GiveawayPrizeTypeSingle GiveawayPrizeType = "single"
// A group contains a pool of giveaway prizes

Check failure on line 211 in database/giveaway.go

View workflow job for this annotation

GitHub Actions / Test

comment on exported const GiveawayPrizeTypeGroup should be of the form "GiveawayPrizeTypeGroup ..."
GiveawayPrizeTypeGroup GiveawayPrizeType = "group"

// The pool in this group is ordered and prizes should be drawn in ascending order

Check failure on line 214 in database/giveaway.go

View workflow job for this annotation

GitHub Actions / Test

comment on exported const GiveawayPrizeGroupOrdered should be of the form "GiveawayPrizeGroupOrdered ..."
GiveawayPrizeGroupOrdered GiveawayPrizeGroupSort = "ordered"
// The pool in this group contains a set of prizes that should be drawn in a random order

Check failure on line 216 in database/giveaway.go

View workflow job for this annotation

GitHub Actions / Test

comment on exported const GiveawayPrizeGroupRandom should be of the form "GiveawayPrizeGroupRandom ..."
GiveawayPrizeGroupRandom GiveawayPrizeGroupSort = "random"
)

// giveawayPrizeInterface is a helper type to un-/marshal a giveaway prize json file
type giveawayPrizeInterface interface {
prizeType() GiveawayPrizeType
}

// GiveawayPrize represents a general giveaway prize. You can unmarshal to it as well as marshal it
// again. Various functions provide access to modify the pool of prizes.
type GiveawayPrize struct {
giveawayPrizeInterface
filename string
}

// NewGiveawayPrize reads the file and stores it in a new GiveawayPrize struct.
//
// When modifying something, make sure to call p.SaveFile() to save the changes back to the file.
func NewGiveawayPrize(filename string) (p GiveawayPrize, err error) {
if filename == "" {
return p, fmt.Errorf("argument filename cannot be empty")
}
p.filename = filename

err = p.ReadFile()
return p, err

}

// ReadFile reads the giveaway file from the configured filename and stores it in p
func (p *GiveawayPrize) ReadFile() error {
if p == nil || p.filename == "" {
return fmt.Errorf("cannot read to invalid GiveawayPrize! Make sure to use NewGiveawayPrize()")
}
data, err := os.ReadFile(p.filename)
if err != nil {
return err
}
return json.Unmarshal(data, &p)
}

// SaveFile saves p in the configured json file
func (p GiveawayPrize) SaveFile() error {
if p.filename == "" {
return fmt.Errorf("cannot save invalid GiveawayPrize! Make sure to use NewGiveawayPrize()")
}
data, err := json.MarshalIndent(p, "", " ")
if err != nil {
return err
}
return os.WriteFile(p.filename, data, 0644)
}

// HasPrizeAvailable returns whether p has at least one prize without a winner
func (p GiveawayPrize) HasPrizeAvailable() bool {
if p.giveawayPrizeInterface == nil {
return false
}

switch t := p.giveawayPrizeInterface.(type) {
case *GiveawayPrizeSingle:
return t.Winner == ""
case *GiveawayPrizeGroup:
return t.HasPrizeAvailable()
}
return false
}

// GetNextPrize returns a pointer to the next giveaway prize. You can make any changes to it but in
// in order to save it back to the file, use p.SaveFile().
//
// The next prize is determined by the next one which has an empty Winner field. If the prize
// whould be a group which sort is set to random, then one of the prizes in its pool, which has an
// empty Winner filed (or if it is also a group, then when it contains at least one prize without
// a winner), is selected.
//
// When there is no prize available ok will be false.
func (p *GiveawayPrize) GetNextPrize() (*GiveawayPrizeSingle, bool) {
if p == nil || p.giveawayPrizeInterface == nil {
return nil, false
}

switch t := p.giveawayPrizeInterface.(type) {
case *GiveawayPrizeSingle:
if t.Winner == "" {
return t, true
}
case *GiveawayPrizeGroup:
return t.GetNextPrize()
}
return nil, false
}

// UnmarshalJSON implements json.Unmarshaler
func (p *GiveawayPrize) UnmarshalJSON(data []byte) error {
var h struct {
Type GiveawayPrizeType `json:"type"`
}
err := json.Unmarshal(data, &h)
if err != nil {
return err
}

switch h.Type {
case GiveawayPrizeTypeSingle:
var t GiveawayPrizeSingle
err = json.Unmarshal(data, &t)
p.giveawayPrizeInterface = &t
return err
case GiveawayPrizeTypeGroup:
var t GiveawayPrizeGroup
err = json.Unmarshal(data, &t)
p.giveawayPrizeInterface = &t
return err
default:
return &json.UnmarshalTypeError{
Value: string(h.Type),
Type: reflect.TypeOf(h.Type),
}
}
}

// MarshalJSON implements json.Marshaler
func (p GiveawayPrize) MarshalJSON() ([]byte, error) {
if p.giveawayPrizeInterface == nil {
return []byte{}, &json.MarshalerError{
Type: reflect.TypeOf(p),
Err: fmt.Errorf("underlying prize is nil"),
}
}
b := bytes.NewBuffer([]byte{})
b.WriteByte('{')
const format string = "\"%s\":\"%s\""
b.WriteString(fmt.Sprintf(format, "type", p.prizeType()))
buf, err := json.Marshal(p.giveawayPrizeInterface)
if err != nil {
return []byte{}, err
}
b.WriteByte(',')
b.Write(buf[1:])
return b.Bytes(), nil
}

// GiveawayPrizeSingle represents a single giveaway prize. Its the lowest struct from all giveaway
// structures.
type GiveawayPrizeSingle struct {
// The name of prize
Name string `json:"name"`

// The identifier of the winner. An empty string means this prize has no winner yet and is
// available.
Winner string `json:"winner,omitempty"`
}

func (p GiveawayPrizeSingle) prizeType() GiveawayPrizeType {
return GiveawayPrizeTypeSingle
}

// GiveawayPrizeGroup represents a pool of prizes. The behavior or the order is defined by the Sort
// field.
type GiveawayPrizeGroup struct {
// The order of the pool. Defines in which order to read the prizes in the pool
Sort GiveawayPrizeGroupSort `json:"sort"`
// All prizes that belong to this group
Pool []GiveawayPrize `json:"pool"`
}

func (pg GiveawayPrizeGroup) prizeType() GiveawayPrizeType {
return GiveawayPrizeTypeGroup
}

// HasPrizeAvailable returns whether pg contains at least one prize without a winner
func (pg GiveawayPrizeGroup) HasPrizeAvailable() bool {
for _, p := range pg.Pool {
if p.HasPrizeAvailable() {
return true
}
}
return false
}

// GetNextPrize returns a pointer to the next giveaway prize. You can make any changes to it but in
// in order to save it back to the file, use p.SaveFile().
//
// The next prize is determined by the next one which has an empty Winner field. If the prize
// whould be a group which sort is set to random, then one of the prizes in its pool, which has an
// empty Winner filed (or if it is also a group, then when it contains at least one prize without
// a winner), is selected.
//
// When there is no prize available ok will be false.
func (pg *GiveawayPrizeGroup) GetNextPrize() (*GiveawayPrizeSingle, bool) {
if pg == nil || len(pg.Pool) == 0 {
return nil, false
}

switch pg.Sort {
case GiveawayPrizeGroupOrdered:
for i, p := range pg.Pool {
if s, ok := p.GetNextPrize(); ok {
pg.Pool[i] = p
return s, true
}
}
case GiveawayPrizeGroupRandom:
var available []int
for i, p := range pg.Pool {
if p.HasPrizeAvailable() {
available = append(available, i)
}
}

switch len(available) {
case 0:
case 1:
return pg.Pool[available[0]].GetNextPrize()
default:
rand.Shuffle(len(available), func(i, j int) {
available[i], available[j] = available[j], available[i]
})
i := available[rand.Intn(len(available)-1)]
return pg.Pool[i].GetNextPrize()
}
}
return nil, false
}

0 comments on commit a00a0ba

Please sign in to comment.