Skip to content

Commit

Permalink
feat: add FOMO3D game implementation (#3344)
Browse files Browse the repository at this point in the history
# Description
This PR introduces FOMO3D, a blockchain-based game that combines lottery
and investment mechanics, implemented as a Gno realm. The game creates
an engaging economic model where players compete to be the last key
purchaser while earning dividends from subsequent purchases.

### Key Features
- Players purchase keys using GNOT tokens
- Each key purchase:
  - Extends the game timer
  - Increases key price by 1%
  - Makes buyer potential jackpot winner
  - Distributes dividends to existing key holders
- Automatic prize distribution:
  - 47% to jackpot (winner)
  - 28% as dividends to key holders
  - 20% to next round's starting pot
  - 5% as a fee to the contract owner
- Full test coverage

### Technical Implementation
- Utilizes AVL tree for player data storage
- Implements dividend distribution system
- Includes comprehensive test suite
- Features markdown-formatted render functions for game state
visualization
- Mints a unique FOMO3D NFT to the winner of each round

### How to Use
1. Start game with `StartGame()`
2. Purchase keys with `BuyKeys()`
3. Claim dividends with `ClaimDividends()`
4. View game status via render functions
5. Winner automatically receives jackpot when timer expires

### Testing
All core functionalities are covered by unit tests including:
- Full game flow
- Key purchasing mechanics
- Dividend distribution
- Game ending conditions

Inspired by the original Ethereum FOMO3D game but rebuilt for the Gno
platform.

## Note
The test checks will not pass until
[#3495](#3495) is merged.
In case this PR is not approved, I will refactor the NFT feature
accordingly.

---------

Signed-off-by: gfanton <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman <[email protected]>
Signed-off-by: Norman <[email protected]>
Co-authored-by: Nathan Toups <[email protected]>
Co-authored-by: Leon Hudak <[email protected]>
Co-authored-by: Alexis Colin <[email protected]>
Co-authored-by: gfanton <[email protected]>
Co-authored-by: Mustapha <[email protected]>
Co-authored-by: Morgan <[email protected]>
Co-authored-by: Blake <[email protected]>
Co-authored-by: n0izn0iz <[email protected]>
Co-authored-by: Norman <[email protected]>
  • Loading branch information
10 people authored Feb 3, 2025
1 parent dc0b608 commit 815cf51
Show file tree
Hide file tree
Showing 7 changed files with 1,003 additions and 0 deletions.
30 changes: 30 additions & 0 deletions examples/gno.land/r/stefann/fomo3d/errors.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package fomo3d

import "errors"

var (
// Game state errors
ErrGameInProgress = errors.New("fomo3d: game already in progress")
ErrGameNotInProgress = errors.New("fomo3d: game not in progress")
ErrGameEnded = errors.New("fomo3d: game has ended")
ErrGameTimeExpired = errors.New("fomo3d: game time expired")
ErrNoKeysPurchased = errors.New("fomo3d: no keys purchased")
ErrPlayerNotInGame = errors.New("fomo3d: player is not in the game")

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

// Dividend errors
ErrNoDividendsToClaim = errors.New("fomo3d: no dividends to claim")

// Fee errors
ErrNoFeesToClaim = errors.New("fomo3d: no owner fees to claim")

// Resolution errors
ErrInvalidAddressOrName = errors.New("fomo3d: invalid address or unregistered username")

// NFT errors
ErrUnauthorizedMint = errors.New("fomo3d: only the Fomo3D game realm can mint winner NFTs")
ErrZeroAddress = errors.New("fomo3d: zero address")
)
94 changes: 94 additions & 0 deletions examples/gno.land/r/stefann/fomo3d/events.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package fomo3d

import (
"std"

"gno.land/p/demo/ufmt"
)

// Event names
const (
// Game events
GameStartedEvent = "GameStarted"
GameEndedEvent = "GameEnded"
KeysPurchasedEvent = "KeysPurchased"

// Player events
DividendsClaimedEvent = "DividendsClaimed"

// Admin events
OwnerFeeClaimedEvent = "OwnerFeeClaimed"
)

// Event keys
const (
// Common keys
EventRoundKey = "round"
EventAmountKey = "amount"

// Game keys
EventStartBlockKey = "startBlock"
EventEndBlockKey = "endBlock"
EventStartingPotKey = "startingPot"
EventWinnerKey = "winner"
EventJackpotKey = "jackpot"

// Player keys
EventBuyerKey = "buyer"
EventNumKeysKey = "numKeys"
EventPriceKey = "price"
EventJackpotShareKey = "jackpotShare"
EventDividendShareKey = "dividendShare"
EventClaimerKey = "claimer"

// Admin keys
EventOwnerKey = "owner"
EventPreviousOwnerKey = "previousOwner"
EventNewOwnerKey = "newOwner"
)

func emitGameStarted(round, startBlock, endBlock, startingPot int64) {
std.Emit(
GameStartedEvent,
EventRoundKey, ufmt.Sprintf("%d", round),
EventStartBlockKey, ufmt.Sprintf("%d", startBlock),
EventEndBlockKey, ufmt.Sprintf("%d", endBlock),
EventStartingPotKey, ufmt.Sprintf("%d", startingPot),
)
}

func emitGameEnded(round int64, winner std.Address, jackpot int64) {
std.Emit(
GameEndedEvent,
EventRoundKey, ufmt.Sprintf("%d", round),
EventWinnerKey, winner.String(),
EventJackpotKey, ufmt.Sprintf("%d", jackpot),
)
}

func emitKeysPurchased(buyer std.Address, numKeys, price, jackpotShare, dividendShare int64) {
std.Emit(
KeysPurchasedEvent,
EventBuyerKey, buyer.String(),
EventNumKeysKey, ufmt.Sprintf("%d", numKeys),
EventPriceKey, ufmt.Sprintf("%d", price),
EventJackpotShareKey, ufmt.Sprintf("%d", jackpotShare),
EventDividendShareKey, ufmt.Sprintf("%d", dividendShare),
)
}

func emitDividendsClaimed(claimer std.Address, amount int64) {
std.Emit(
DividendsClaimedEvent,
EventClaimerKey, claimer.String(),
EventAmountKey, ufmt.Sprintf("%d", amount),
)
}

func emitOwnerFeeClaimed(owner std.Address, amount int64) {
std.Emit(
OwnerFeeClaimedEvent,
EventOwnerKey, owner.String(),
EventAmountKey, ufmt.Sprintf("%d", amount),
)
}
Loading

0 comments on commit 815cf51

Please sign in to comment.