Smart Contract

Gomoku

A.a10dde51240c1ec7.Gomoku

Valid From

85,820,000

Deployed

3d ago
Feb 25, 2026, 04:33:25 AM UTC

Dependents

1 imports
1import MatchContract from 0xa10dde51240c1ec7
2import FungibleToken from 0xf233dcee88fe0abe
3import FlowToken from 0x1654653399040a61
4
5import GomokuIdentity from 0xa10dde51240c1ec7
6import GomokuResult from 0xa10dde51240c1ec7
7import GomokuType from 0xa10dde51240c1ec7
8
9pub contract Gomoku {
10
11    // Paths
12    pub let AdminStoragePath: StoragePath
13
14    pub let CollectionStoragePath: StoragePath
15    pub let CollectionPublicPath: PublicPath
16
17    // Bets
18    access(account) let hostOpeningBetMap: @{ UInt32: FlowToken.Vault }
19    access(account) let challengerOpeningBetMap: @{ UInt32: FlowToken.Vault }
20
21    access(account) let hostRaisedBetMap: @{ UInt32: FlowToken.Vault }
22    access(account) let challengerRaisedBetMap: @{ UInt32: FlowToken.Vault }
23
24    // Bets value when finalize
25    access(account) let hostFinalizedOpeningBetMap: { UInt32: UFix64 }
26    access(account) let challengerFinalizedOpeningBetMap: { UInt32: UFix64 }
27
28    access(account) let hostFinalizedRaisedBetMap: { UInt32: UFix64 }
29    access(account) let challengerFinalizedRaisedBetMap: { UInt32: UFix64 }
30
31    // Events
32    // Event be emitted when the composition is created
33    pub event HostOpeningBet(balance: UFix64)
34
35    // Event be emitted when the contract is created
36    pub event CompositionMatched(
37        host: Address,
38        challenger: Address,
39        currency: String,
40        openingBet: UFix64)
41
42    pub event CompositionCreated(
43        host: Address,
44        currency: String)
45    pub event CollectionCreated()
46    pub event Withdraw(id: UInt32, from: Address?)
47    pub event Deposit(id: UInt32, to: Address?)
48    pub event Surrender(id: UInt32, from: Address)
49
50    pub event CollectionNotFound(type: Type, path: Path, address: Address)
51    pub event ResourceNotFound(id: UInt32, type: Type, address: Address)
52
53    pub event MakeMove(
54        compositionId: UInt32,
55        locationX: Int8,
56        locationY: Int8,
57        stoneColor: UInt8)
58
59    pub event RoundSwitch(
60        compositionId: UInt32,
61        previous: UInt8,
62        next: UInt8
63    )
64
65    pub resource Admin {
66
67        pub fun manualFinalizeByTimeout(index: UInt32) {
68        }
69
70        pub fun recycleBets() {
71
72            let flowReceiverReference = Gomoku.account
73                .getCapability<&FlowToken.Vault{FungibleToken.Receiver}>(/public/flowTokenReceiver)
74                .borrow() ?? panic("Could not borrow a reference to the Flow token receiver capability")
75
76            for key in Gomoku.hostOpeningBetMap.keys {
77                // let hostOpeningBet <-! Gomoku.hostOpeningBetMap[key] not working: cannot move nested resource
78                
79                // let placeholderVault: @FlowToken.Vault? <- FlowToken.createEmptyVault() as! @FlowToken.Vault
80
81                // let mapRef: &{UInt32: FlowToken.Vault} = &Gomoku.hostOpeningBetMap as &{UInt32: FlowToken.Vault}
82                // let vault = &mapRef[key] as &FlowToken.Vault?
83                // let flowBet <- vault!.withdraw(amount: vault!.balance)
84
85                // // let hostOpeningBet <- Gomoku.hostOpeningBetMap[key] <- placeholderVault
86                // flowReceiverReference.deposit(from: <- flowBet)
87                // let emptyVault <- Gomoku.hostOpeningBetMap.remove(key: key)
88                // destroy emptyVault
89                let tokenVault <- Gomoku.hostOpeningBetMap.remove(key: key)
90                if let vault <- tokenVault {
91                    flowReceiverReference.deposit(from: <- (vault as! @FungibleToken.Vault))
92                } else {
93                    destroy tokenVault
94                }
95            }
96
97            for key in Gomoku.challengerOpeningBetMap.keys {
98                // var challengerOpeningBet: @FlowToken.Vault? <- FlowToken.createEmptyVault() as! @FlowToken.Vault
99                // Gomoku.challengerOpeningBetMap[key] <-> challengerOpeningBet
100                // flowReceiverReference.deposit(from: <- (challengerOpeningBet as! @FungibleToken.Vault))
101                // let emptyVault <- Gomoku.challengerOpeningBetMap.remove(key: key)
102                // destroy emptyVault
103                let tokenVault <- Gomoku.challengerOpeningBetMap.remove(key: key)
104                if let vault <- tokenVault {
105                    flowReceiverReference.deposit(from: <- (vault as! @FungibleToken.Vault))
106                } else {
107                    destroy tokenVault
108                }
109            }
110
111            for key in Gomoku.hostRaisedBetMap.keys {
112                // var hostRaisedBet: @FlowToken.Vault? <- FlowToken.createEmptyVault() as! @FlowToken.Vault
113                // Gomoku.hostRaisedBetMap[key] <-> hostRaisedBet
114                // flowReceiverReference.deposit(from: <- (hostRaisedBet as! @FungibleToken.Vault))
115                // let emptyVault <- Gomoku.hostRaisedBetMap.remove(key: key)
116                // destroy emptyVault
117                let tokenVault <- Gomoku.hostRaisedBetMap.remove(key: key)
118                if let vault <- tokenVault {
119                    flowReceiverReference.deposit(from: <- (vault as! @FungibleToken.Vault))
120                } else {
121                    destroy tokenVault
122                }
123            }
124
125            for key in Gomoku.challengerRaisedBetMap.keys {
126                // var challengerRaisedBet: @FlowToken.Vault? <- FlowToken.createEmptyVault() as! @FlowToken.Vault
127                // Gomoku.challengerRaisedBetMap[key] <-> challengerRaisedBet
128                // flowReceiverReference.deposit(from: <- (challengerRaisedBet as! @FungibleToken.Vault))
129                // let emptyVault <- Gomoku.challengerRaisedBetMap.remove(key: key)
130                // destroy emptyVault
131                let tokenVault <- Gomoku.challengerRaisedBetMap.remove(key: key)
132                if let vault <- tokenVault {
133                    flowReceiverReference.deposit(from: <- (vault as! @FungibleToken.Vault))
134                } else {
135                    destroy tokenVault
136                }
137            }
138        }
139
140        pub fun getValue(): UFix64 {
141            var total = 0.0
142            for key in Gomoku.hostOpeningBetMap.keys {
143                total = total + (Gomoku.hostOpeningBetMap[key]?.balance ?? 0.0)
144            }
145            return total
146        }
147    }
148
149    init() {
150        self.CollectionStoragePath = /storage/gomokuCompositionCollectionV1
151        self.CollectionPublicPath = /public/gomokuCompositionCollectionV1
152
153        self.hostOpeningBetMap <- {}
154        self.challengerOpeningBetMap <- {}
155        self.hostRaisedBetMap <- {}
156        self.challengerRaisedBetMap <- {}
157
158        self.hostFinalizedOpeningBetMap = {}
159        self.challengerFinalizedOpeningBetMap = {}
160        self.hostFinalizedRaisedBetMap = {}
161        self.challengerFinalizedRaisedBetMap = {}
162
163        self.AdminStoragePath = /storage/gomokuAdminV1
164        let admin <- create Admin()
165        self.account.save(<- admin, to: self.AdminStoragePath)
166
167        if self.account.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) == nil {
168            let flowVault <- FlowToken.createEmptyVault()
169            self.account.save(<- flowVault, to: /storage/flowTokenVault)
170        }
171
172        if self.account.getCapability<&FlowToken.Vault{FungibleToken.Receiver}>(/public/flowTokenReceiver) == nil {
173            // Create a public capability to the stored Vault that only exposes
174            // the `deposit` method through the `Receiver` interface
175            self.account.link<&FlowToken.Vault{FungibleToken.Receiver}>(
176                /public/flowTokenReceiver,
177                target: /storage/flowTokenVault
178            )
179        }
180
181        if self.account.getCapability<&FlowToken.Vault{FungibleToken.Balance}>(/public/flowTokenBalance) == nil {
182            // Create a public capability to the stored Vault that only exposes
183            // the `balance` field through the `Balance` interface
184            self.account.link<&FlowToken.Vault{FungibleToken.Balance}>(
185                /public/flowTokenBalance,
186                target: /storage/flowTokenVault
187            )
188        }
189    }
190
191    pub resource Composition {
192
193        pub let id: UInt32
194
195        pub let boardSize: UInt8
196        pub let totalRound: UInt8
197        pub var currentRound: UInt8
198
199        // timeout of block height
200        pub var latestBlockHeight: UInt64
201        pub var blockHeightTimeout: UInt64
202
203        access(account) var destroyable: Bool
204
205        priv var winner: GomokuType.Role?
206
207        priv var host: Address
208        priv var challenger: Address?
209        priv var roundWinners: [GomokuType.Result]
210        priv var steps: @[[Stone]]
211        priv var locationStoneMaps: [{String: GomokuType.StoneColor}]
212
213        init(
214            id: UInt32,
215            host: Address,
216            boardSize: UInt8,
217            totalRound: UInt8
218        ) {
219            pre {
220                totalRound >= 2: "Total round should be 2 to take turns to make first move (black stone) for fairness."
221                totalRound % 2 == 0: "Total round should be event number to take turns to make first move (black stone) for fairness."
222            }
223
224            self.id = id
225            self.host = host
226            self.boardSize = boardSize
227            self.challenger = nil
228            self.totalRound = totalRound
229            self.currentRound = 0
230            self.winner = nil
231            self.roundWinners = []
232
233            self.steps <- []
234            self.locationStoneMaps = []
235            var stepIndex = 0
236            // while totalRound > stepIndex {
237            //     self.steps.append(<- [])
238            //     self.locationStoneMaps.append({})
239            //     stepIndex = stepIndex + 1
240            // }
241
242            self.latestBlockHeight = getCurrentBlock().height
243            self.blockHeightTimeout = 60 * 60 * 24 * 7
244            self.destroyable = false
245
246            // emit CompositionCreated(
247            //     host: host,
248            //     currency: Type<FlowToken>().identifier)
249        }
250
251        destroy() {
252            if self.destroyable == false {
253                panic("You can't destory this composition by yourself!")
254            }
255            destroy self.steps
256        }
257
258    }
259
260    pub resource CompositionCollection {
261
262        pub let StoragePath: StoragePath
263        pub let PublicPath: PublicPath
264
265        priv var ownedCompositionMap: @{UInt32: Gomoku.Composition}
266        priv var destroyable: Bool
267
268        init () {
269            self.ownedCompositionMap <- {}
270            self.destroyable = false
271            self.StoragePath = /storage/gomokuCollectionV1
272            self.PublicPath = /public/gomokuCollectionV1
273        }
274
275        access(account) fun withdraw(by id: UInt32): @Gomoku.Composition {
276            let token <- self.ownedCompositionMap.remove(key: id) ?? panic("missing Composition")
277            emit Withdraw(id: token.id, from: self.owner?.address)
278            if self.ownedCompositionMap.keys.length == 0 {
279                self.destroyable = true
280            }
281            return <- token
282        }
283
284        access(account) fun deposit(token: @Gomoku.Composition) {
285            let id: UInt32 = token.id
286            let oldToken <- self.ownedCompositionMap[id] <- token
287            emit Deposit(id: id, to: self.owner?.address)
288            self.destroyable = false
289            destroy oldToken
290        }
291
292        pub fun getIds(): [UInt32] {
293            return self.ownedCompositionMap.keys
294        }
295
296        pub fun getBalance(): Int {
297            return self.ownedCompositionMap.keys.length
298        }
299
300        pub fun borrow(id: UInt32): &Gomoku.Composition? {
301            return &self.ownedCompositionMap[id] as &Gomoku.Composition?
302        }
303
304        destroy() {
305            destroy self.ownedCompositionMap
306            if self.destroyable == false {
307                panic("Ha Ha! Got you! You can't destory this collection if there are Gomoku Composition!")
308            }
309        }
310    }
311
312    pub resource Stone: GomokuType.Stoning {
313        pub let color: GomokuType.StoneColor
314        pub let location: GomokuType.StoneLocation
315
316        pub init(
317            color: GomokuType.StoneColor,
318            location: GomokuType.StoneLocation
319        ) {
320            self.color = color
321            self.location = location
322        }
323
324        pub fun key(): String {
325            return self.location.key()
326        }
327
328        pub fun convertToData(): GomokuType.StoneData {
329            return GomokuType.StoneData(
330                color: self.color,
331                location: self.location
332            )
333        }
334    }
335
336    pub struct StoneData: GomokuType.StoneDataing {
337        pub let color: GomokuType.StoneColor
338        pub let location: GomokuType.StoneLocation
339
340        init(
341            color: GomokuType.StoneColor,
342            location: GomokuType.StoneLocation
343        ) {
344            self.color = color
345            self.location = location
346        }
347    }
348}