Skip to content

fix(fomo3d): fix owner & add txlinks in render #3734

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

Draft
wants to merge 21 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions examples/gno.land/r/stefann/fomo3d/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# FOMO3D Game

FOMO3D (Fear Of Missing Out 3D) is a blockchain-based game that combines elements of a lottery and investment mechanics. Players purchase keys using GNOT tokens, where each key purchase:

- Extends the game timer
- Increases the key price by 1%
- Makes the buyer the potential winner of the jackpot
- Distributes dividends to all key holders

## Game Mechanics

- The last person to buy a key before the timer expires wins the jackpot (47% of all purchases)
- Key holders earn dividends from each purchase (28% of all purchases)
- 20% of purchases go to the next round's starting pot
- 5% goes to development fee
- Game ends when the timer expires

## How to Play

1. **Buy Keys** - Send GNOT to this realm with `BuyKeys` to purchase keys
2. **Collect Dividends** - Call `ClaimDividends` to collect your earnings
3. **Check Your Stats** - Append `:player/` followed by your address or namespace to the current URL to view your keys and dividends
4. **Start New Round** - Call `StartGame` to begin a new round (only available when game has ended)

## Game Parameters

- Minimum Key Price: 100,000 ugnot
- Time Extension: 86,400 blocks (~24 hours @ 1s blocks)
- Distribution:
- Jackpot: 47%
- Dividends: 28%
- Next Round Pot: 20%
- Development Fee: 5%
1 change: 1 addition & 0 deletions examples/gno.land/r/stefann/fomo3d/errors.gno
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ var (
ErrPlayerNotInGame = errors.New("fomo3d: player is not in the game")

// Payment errors
ErrNoCoinsSent = errors.New("fomo3d: must send ugnot to buy keys")
ErrInvalidPayment = errors.New("fomo3d: must send ugnot only")
ErrInsufficientPayment = errors.New("fomo3d: insufficient payment for key")

Expand Down
74 changes: 16 additions & 58 deletions examples/gno.land/r/stefann/fomo3d/fomo3d.gno
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,14 @@ package fomo3d

import (
"std"
"strings"

"gno.land/p/demo/avl"
"gno.land/p/demo/ownable"
"gno.land/p/demo/ufmt"

"gno.land/r/leon/hor"
"gno.land/r/sys/users"
)

// FOMO3D (Fear Of Missing Out 3D) is a blockchain-based game that combines elements
// of a lottery and investment mechanics. Players purchase keys using GNOT tokens,
// where each key purchase:
// - Extends the game timer
// - Increases the key price by 1%
// - Makes the buyer the potential winner of the jackpot
// - Distributes dividends to all key holders
//
// Game Mechanics:
// - The last person to buy a key before the timer expires wins the jackpot (47% of all purchases)
// - Key holders earn dividends from each purchase (28% of all purchases)
// - 20% of purchases go to the next round's starting pot
// - 5% goes to development fee
// - Game ends when the timer expires
//
// Inspired by the original Ethereum FOMO3D game but implemented in Gno.

const (
MIN_KEY_PRICE int64 = 100000 // minimum key price in ugnot
TIME_EXTENSION int64 = 86400 // time extension in blocks when new key is bought (~24 hours @ 1s blocks)
Expand All @@ -47,20 +28,17 @@ type PlayerInfo struct {

// GameState represents the current state of the FOMO3D game
type GameState struct { // TODO: Separate GameState and RoundState and save round history tree in GameState
StartBlock int64 // Block when the game started
EndBlock int64 // Block when the game will end
LastKeyBlock int64 // Block of last key purchase
LastBuyer std.Address // Address of last key buyer
Jackpot int64 // Current jackpot in ugnot
KeyPrice int64 // Current price of keys in ugnot
TotalKeys int64 // Total number of keys in circulation
Ended bool // Whether the game has ended
CurrentRound int64 // Current round number
NextPot int64 // Next round's starting pot
OwnerFee int64 // Accumulated owner fees
BuyKeysLink string // Link to BuyKeys function
ClaimDividendsLink string // Link to ClaimDividends function
StartGameLink string // Link to StartGame function
StartBlock int64 // Block when the game started
EndBlock int64 // Block when the game will end
LastKeyBlock int64 // Block of last key purchase
LastBuyer std.Address // Address of last key buyer
Jackpot int64 // Current jackpot in ugnot
KeyPrice int64 // Current price of keys in ugnot
TotalKeys int64 // Total number of keys in circulation
Ended bool // Whether the game has ended
CurrentRound int64 // Current round number
NextPot int64 // Next round's starting pot
OwnerFee int64 // Accumulated owner fees
}

var (
Expand All @@ -70,10 +48,10 @@ var (
)

func init() {
Ownable = ownable.New()
Ownable = ownable.NewWithAddress(std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8"))
players = avl.NewTree()
gameState.Ended = true
hor.Register("FOMO3D Game!", "")
hor.Register("FOMO3D", "A thrilling game of strategy, timing, and FOMO!")
}

// StartGame starts a new game round
Expand Down Expand Up @@ -116,6 +94,9 @@ func BuyKeys() {

// Get sent coins
sent := std.OriginSend()
if len(sent) == 0 {
panic(ErrNoCoinsSent.Error())
}
if len(sent) != 1 || sent[0].Denom != "ugnot" {
panic(ErrInvalidPayment.Error())
}
Expand Down Expand Up @@ -333,26 +314,3 @@ func GetPlayerInfo(addrOrName string) (int64, int64) {
playerInfo := info.(PlayerInfo)
return playerInfo.Keys, playerInfo.Dividends
}

// Render handles the rendering of game state
func Render(path string) string {
parts := strings.Split(path, "/")
c := len(parts)

switch {
case path == "":
return RenderHome()
case c == 2 && parts[0] == "player":
if gameState.Ended {
return ufmt.Sprintf("🔴 Game has not started yet.\n\n Call [`StartGame()`](%s) to start a new round.\n\n", gameState.StartGameLink)
}
addr := stringToAddress(parts[1])
if addr == "" || !isPlayerInGame(addr) {
return "Address not found in game. You need to buy keys first to view your stats.\n\n"
}
keys, dividends := GetPlayerInfo(parts[1])
return RenderPlayer(addr, keys, dividends)
default:
return "404: Invalid path\n\n"
}
}
62 changes: 46 additions & 16 deletions examples/gno.land/r/stefann/fomo3d/render.gno
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,34 @@ import (

"gno.land/p/demo/grc/grc721"
"gno.land/p/demo/ufmt"
"gno.land/p/moul/txlink"

"gno.land/r/sys/users"
)

// Render handles the rendering of game state
func Render(path string) string {
parts := strings.Split(path, "/")
c := len(parts)

switch {
case path == "":
return RenderHome()
case c == 2 && parts[0] == "player":
if gameState.Ended {
return ufmt.Sprintf("🔴 Game has not started yet.\n\n Call [StartGame](%s) to start a new round.\n\n", txlink.Call("StartGame"))
}
addr := stringToAddress(parts[1])
if addr == "" || !isPlayerInGame(addr) {
return ufmt.Sprintf("Address or namespace not found. You need to [Buy Keys](%s) first to view your stats.\n\n", txlink.Call("BuyKeys"))
}
keys, dividends := GetPlayerInfo(parts[1])
return RenderPlayer(addr, keys, dividends)
default:
return "404: Invalid path\n\n"
}
}

// RenderHome renders the main game state
func RenderHome() string {
var builder strings.Builder
Expand All @@ -34,18 +58,24 @@ func RenderHome() string {

// Play Game section
builder.WriteString("## How to Play\n\n")
builder.WriteString(ufmt.Sprintf("1. **Buy Keys** - Send GNOT to this realm with function [`BuyKeys()`](%s)\n", gameState.BuyKeysLink))
builder.WriteString(ufmt.Sprintf("2. **Collect Dividends** - Call [`ClaimDividends()`](%s) to collect your earnings\n", gameState.ClaimDividendsLink))
builder.WriteString("3. **Check Your Stats** - Append `:player/` followed by your address or namespace to the current URL to view your keys and dividends\n")
if gameState.Ended {
builder.WriteString(ufmt.Sprintf("4. **Start New Round** - Call [`StartGame()`](%s) to begin a new round\n", gameState.StartGameLink))
builder.WriteString(ufmt.Sprintf("1. **Start New Round** - Call [StartGame](%s) to begin a new round\n",
txlink.Call("StartGame"),
))
}
builder.WriteString(ufmt.Sprintf("1. **Buy Keys** - Send GNOT to this realm with [BuyKeys](%s)\n",
txlink.Call("BuyKeys"),
))
builder.WriteString(ufmt.Sprintf("1. **Collect Dividends** - Call [ClaimDividends](%s) to collect your earnings\n",
txlink.Call("ClaimDividends"),
))
builder.WriteString("1. **Check Your Stats** - Append `:player/` followed by your address or namespace to the current URL to view your keys and dividends\n")
builder.WriteString("\n")

// Game Status section
builder.WriteString("## Game Status\n\n")
if gameState.StartBlock == 0 {
builder.WriteString("🔴 Game has not started yet.\n\n")
builder.WriteString(ufmt.Sprintf("🔴 Game has not started yet. Call [StartGame](%s) to start a new round.\n\n", txlink.Call("StartGame")))
} else {
if gameState.Ended {
builder.WriteString("🔴 **Game Status:** Ended\n")
Expand All @@ -58,7 +88,11 @@ func RenderHome() string {
builder.WriteString(ufmt.Sprintf("💰 **Jackpot:** %d ugnot\n\n", gameState.Jackpot))
builder.WriteString(ufmt.Sprintf("🔑 **Key Price:** %d ugnot\n\n", gameState.KeyPrice))
builder.WriteString(ufmt.Sprintf("📊 **Total Keys:** %d\n\n", gameState.TotalKeys))
builder.WriteString(ufmt.Sprintf("👤 **Last Buyer:** %s\n\n", getDisplayName(gameState.LastBuyer)))
if gameState.LastBuyer == "" {
builder.WriteString(ufmt.Sprintf("👤 **Last Buyer:** No buyers yet, [Buy Keys](%s) to be the first player!\n\n", txlink.Call("BuyKeys")))
} else {
builder.WriteString(ufmt.Sprintf("👤 **Last Buyer:** %s\n\n", getDisplayName(gameState.LastBuyer)))
}
builder.WriteString(ufmt.Sprintf("🎮 **Next Round Pot:** %d ugnot\n\n", gameState.NextPot))
}

Expand Down Expand Up @@ -113,9 +147,13 @@ func RenderPlayer(addr std.Address, keys int64, dividends int64) string {
}

builder.WriteString("## Actions\n\n")
builder.WriteString(ufmt.Sprintf("* To buy more keys, send GNOT to this realm with [`BuyKeys()`](%s)\n", gameState.BuyKeysLink))
builder.WriteString(ufmt.Sprintf("* To buy more keys, send GNOT to this realm with [BuyKeys](%s)\n",
txlink.Call("BuyKeys"),
))
if dividends > 0 {
builder.WriteString("* You have unclaimed dividends! Call `ClaimDividends()` to collect them\n")
builder.WriteString(ufmt.Sprintf("* You have unclaimed dividends! Call [ClaimDividends](%s) to collect them\n",
txlink.Call("ClaimDividends"),
))
}

return builder.String()
Expand All @@ -128,11 +166,3 @@ func getDisplayName(addr std.Address) string {
}
return addr.String()
}

// UpdateFunctionLinks updates the links for game functions
func UpdateFunctionLinks(buyKeysLink string, claimDividendsLink string, startGameLink string) {
Ownable.AssertOwnedByPrevious()
gameState.BuyKeysLink = buyKeysLink
gameState.ClaimDividendsLink = claimDividendsLink
gameState.StartGameLink = startGameLink
}
Loading