-
Notifications
You must be signed in to change notification settings - Fork 398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Adding a lottery to examples #1850
base: master
Are you sure you want to change the base?
Changes from all commits
3d20ae0
f0a477d
26a2dd2
e56ea56
f9f4993
4d095c8
793f749
303dfd4
f6b51f2
ca1398d
8780052
88e2171
0832c33
add8778
769d870
92caa54
a006915
e54628a
3b683a7
444b5ef
b0bccca
bba81f5
1cdf875
4c649fd
58e0675
7fbf161
3c2409b
5ea48cc
a4a5f38
1fb9689
4805e22
db4c8c4
63d4d90
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module gno.land/p/demo/gnolotto | ||
|
||
require ( | ||
gno.land/p/demo/testutils v0.0.0-latest | ||
gno.land/p/demo/uassert v0.0.0-latest | ||
gno.land/p/demo/ufmt v0.0.0-latest | ||
) |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,113 @@ | ||||||
package gnolotto | ||||||
|
||||||
import ( | ||||||
"std" | ||||||
"strconv" | ||||||
"strings" | ||||||
"time" | ||||||
) | ||||||
|
||||||
type Ticket struct { | ||||||
Numbers []int // Holds the selected numbers for the lottery ticket | ||||||
Owner std.Address // Address of the ticket owner | ||||||
} | ||||||
|
||||||
type Lottery struct { | ||||||
Tickets []Ticket // All tickets in the lottery | ||||||
WinningNumbers []int // Winning numbers after the draw | ||||||
DrawTime time.Time // Time of the draw | ||||||
PrizePool int64 // Total prize pool amount | ||||||
} | ||||||
|
||||||
// Intializes a new lottery instance with a specified draw time and prize pool | ||||||
func NewLottery(drawTime time.Time, prizePool int64) *Lottery { | ||||||
return &Lottery{ | ||||||
DrawTime: drawTime, | ||||||
PrizePool: prizePool, | ||||||
Tickets: make([]Ticket, 0), | ||||||
} | ||||||
} | ||||||
|
||||||
const MaxLottoNumbers = 5 | ||||||
|
||||||
// Adds a new ticket to the lottery | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you follow godoc? Start the comment with the func name. Applies for all comments |
||||||
func (l *Lottery) AddTicket(numbers []int, owner std.Address) { | ||||||
l.Tickets = append(l.Tickets, Ticket{Numbers: numbers, Owner: owner}) | ||||||
} | ||||||
|
||||||
// Conducts the draw by generating 5 pseudo-random numbers between 1 and 15 inclusive | ||||||
func (l *Lottery) Draw() { | ||||||
var blockHeight int64 = std.GetHeight() | ||||||
|
||||||
l.WinningNumbers = nil | ||||||
numbersMap := make(map[int]bool) | ||||||
|
||||||
// Add variability to the pseudo-random number generation | ||||||
var variabilityFactor int64 = 1 | ||||||
|
||||||
for len(l.WinningNumbers) < MaxLottoNumbers { | ||||||
simpleSeed := (blockHeight + variabilityFactor*251) % 233280 | ||||||
number := int(simpleSeed%15) + 1 // Ensure number is between 1 and 15 | ||||||
|
||||||
if !numbersMap[number] { | ||||||
l.WinningNumbers = append(l.WinningNumbers, number) | ||||||
numbersMap[number] = true | ||||||
} | ||||||
|
||||||
variabilityFactor += 13 // Adjusts for increased variability | ||||||
} | ||||||
} | ||||||
Comment on lines
+39
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that we have |
||||||
|
||||||
func (l *Lottery) CheckWinners() []std.Address { | ||||||
var winners []std.Address | ||||||
|
||||||
for _, ticket := range l.Tickets { | ||||||
if AreNumberMatching(ticket.Numbers, l.WinningNumbers) { | ||||||
winners = append(winners, ticket.Owner) | ||||||
} | ||||||
} | ||||||
|
||||||
return winners | ||||||
} | ||||||
|
||||||
// Distributes the prize pool equally among the winning ticket owners | ||||||
func (l *Lottery) PayWinners(winners []std.Address) { | ||||||
if len(winners) == 0 { | ||||||
return | ||||||
} | ||||||
|
||||||
var reward int64 = l.PrizePool / int64(len(winners)) | ||||||
|
||||||
banker := std.GetBanker(std.BankerTypeRealmSend) | ||||||
|
||||||
for _, owner := range winners { | ||||||
send := std.Coins{{"ugnot", reward}} | ||||||
banker.SendCoins(std.GetOrigPkgAddr(), owner, send) | ||||||
} | ||||||
|
||||||
l.PrizePool = 0 | ||||||
} | ||||||
|
||||||
func StringToIntSlice(numbersStr string) ([]int, error) { | ||||||
numbersSlice := strings.Split(numbersStr, ",") | ||||||
numbers := make([]int, len(numbersSlice)) | ||||||
for i, numStr := range numbersSlice { | ||||||
num, err := strconv.Atoi(strings.TrimSpace(numStr)) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
numbers[i] = num | ||||||
} | ||||||
|
||||||
return numbers, nil | ||||||
} | ||||||
|
||||||
func AreNumberMatching(ticketNumbers, winningNumbers []int) bool { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be a private func
Suggested change
|
||||||
for i := 0; i < MaxLottoNumbers; i++ { | ||||||
if ticketNumbers[i] != winningNumbers[i] { | ||||||
return false | ||||||
} | ||||||
} | ||||||
|
||||||
return true | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package gnolotto | ||
|
||
import ( | ||
"std" | ||
"testing" | ||
"time" | ||
|
||
"gno.land/p/demo/testutils" | ||
"gno.land/p/demo/uassert" | ||
"gno.land/p/demo/ufmt" | ||
) | ||
|
||
var ( | ||
user1 = testutils.TestAddress("user1") | ||
user2 = testutils.TestAddress("user2") | ||
) | ||
|
||
func createMockLottery() *Lottery { | ||
drawTime := time.Now().Add(24 * time.Hour) | ||
prizePool := int64(1000) | ||
return NewLottery(drawTime, prizePool) | ||
} | ||
|
||
func TestNewLottery(t *testing.T) { | ||
drawTime := time.Now().Add(24 * time.Hour) | ||
prizePool := int64(1000) | ||
lottery := NewLottery(drawTime, prizePool) | ||
|
||
uassert.True(t, lottery.DrawTime.Equal(drawTime), "Draw time should be set correctly") | ||
uassert.Equal(t, lottery.PrizePool, prizePool, "Prize pool should be set correctly") | ||
uassert.Equal(t, len(lottery.Tickets), 0, "Lottery should start with no tickets") | ||
} | ||
|
||
func TestAddTicket(t *testing.T) { | ||
lottery := createMockLottery() | ||
numbers := []int{1, 2, 3, 4, 5} | ||
lottery.AddTicket(numbers, user1) | ||
|
||
uassert.Equal(t, len(lottery.Tickets), 1, "Expected 1 ticket") | ||
uassert.True(t, AreNumberMatching(lottery.Tickets[0].Numbers, numbers), "Ticket numbers should match") | ||
uassert.Equal(t, lottery.Tickets[0].Owner, user1, "Ticket owner should match") | ||
} | ||
|
||
func TestDraw(t *testing.T) { | ||
lottery := createMockLottery() | ||
lottery.Draw() | ||
|
||
uassert.Equal(t, len(lottery.WinningNumbers), MaxLottoNumbers, "Expected 5 winning numbers") | ||
|
||
uniqueNumbers := make(map[int]bool) | ||
for _, number := range lottery.WinningNumbers { | ||
uassert.True(t, number >= 1 && number <= 15, ufmt.Sprintf("Winning number out of range: %d", number)) | ||
uassert.False(t, uniqueNumbers[number], ufmt.Sprintf("Duplicate winning number found: %d", number)) | ||
uniqueNumbers[number] = true | ||
} | ||
} | ||
|
||
func TestCheckWinners(t *testing.T) { | ||
lottery := createMockLottery() | ||
|
||
lottery.AddTicket([]int{1, 2, 3, 4, 5}, user1) | ||
lottery.AddTicket([]int{6, 7, 8, 9, 10}, user2) | ||
lottery.WinningNumbers = []int{1, 2, 3, 4, 5} | ||
|
||
winners := lottery.CheckWinners() | ||
|
||
uassert.Equal(t, len(winners), 1, "Expected 1 winner") | ||
uassert.Equal(t, winners[0], user1, "Winner should be user1") | ||
} | ||
|
||
func TestStringToIntSlice(t *testing.T) { | ||
input := "1, 2, 3, 4, 5" | ||
expected := []int{1, 2, 3, 4, 5} | ||
result, err := StringToIntSlice(input) | ||
|
||
uassert.NoError(t, err, "Unexpected error") | ||
uassert.Equal(t, len(result), len(expected), "Slice length should match") | ||
|
||
for i := range expected { | ||
uassert.Equal(t, result[i], expected[i], ufmt.Sprintf("Expected %d at index %d, got %d", expected[i], i, result[i])) | ||
} | ||
} | ||
|
||
func TestAreNumberMatching(t *testing.T) { | ||
ticketNumbers := []int{1, 2, 3, 4, 5} | ||
winningNumbers := []int{1, 2, 3, 4, 5} | ||
uassert.True(t, AreNumberMatching(ticketNumbers, winningNumbers), "Expected numbers to match") | ||
|
||
winningNumbers = []int{1, 2, 3, 4, 6} | ||
uassert.False(t, AreNumberMatching(ticketNumbers, winningNumbers), "Expected numbers to not match") | ||
} | ||
|
||
func TestPayWinners(t *testing.T) { | ||
lottery := createMockLottery() | ||
realmAddress := std.GetOrigPkgAddr() | ||
std.TestSetOrigCaller(realmAddress) | ||
|
||
winners := []std.Address{user1, user2} | ||
initialPrizePool := lottery.PrizePool | ||
expectedReward := initialPrizePool / int64(len(winners)) | ||
|
||
lottery.PayWinners(winners) | ||
|
||
uassert.Equal(t, lottery.PrizePool, int64(0), "The prize pool should be reset to 0 after distribution") | ||
|
||
coinsUser1 := std.GetBanker(std.BankerTypeRealmSend).GetCoins(user1) | ||
receivedRewardUser1 := coinsUser1.AmountOf("ugnot") | ||
uassert.Equal(t, receivedRewardUser1, expectedReward, ufmt.Sprintf("Winner %s should have received %d but received %d", user1.String(), expectedReward, receivedRewardUser1)) | ||
|
||
coinsUser2 := std.GetBanker(std.BankerTypeRealmSend).GetCoins(user2) | ||
receivedRewardUser2 := coinsUser2.AmountOf("ugnot") | ||
uassert.Equal(t, receivedRewardUser2, expectedReward, ufmt.Sprintf("Winner %s should have received %d but received %d", user2.String(), expectedReward, receivedRewardUser2)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a short godoc comment about the package to give a brief intro as to what it's about, what it can be used for?