games.gno
21.21 Kb · 826 lines
1package kewrchess
2
3import (
4 "errors"
5 "time"
6
7 "gno.land/p/g1g7dna0gp4nec5rza4q25htj0cjgswrxefp37ep/chess"
8)
9
10// Timeout duration (seconds) for challenge proposals and turn timeouts
11var ChallengeWindowSeconds int64 = 180 // 3-minute challenge window for settlement proposals
12var TurnTimeoutSeconds int64 = 120 // 2-minute move timeout when opponent is AFK (Abandonment)
13
14// getVerifiedCaller validates the format and availability of the caller address.
15func getVerifiedCaller(caller address) (address, error) {
16 if caller == "" {
17 return "", errors.New("alamat caller tidak boleh kosong")
18 }
19 return caller, nil
20}
21
22// Stores all chess games permanently on the blockchain
23var games []*Game
24var nextGameID int
25
26// On-chain matchmaking queue (FIFO)
27var matchmakingQueue []address
28
29// init automatically runs when contract is initialized
30func init() {
31 // Initial demo game setup: White player in Game #0
32 pemberiTantangan := address("g1g7dna0gp4nec5rza4q25htj0cjgswrxefp37ep")
33 penerimaTantangan := address("g1kewrchessplayerblack777777777777777") // Simulated opponent
34
35 newGame := NewGame(nextGameID, pemberiTantangan, penerimaTantangan)
36 games = append(games, newGame)
37 nextGameID++
38}
39
40// CreateGame creates a new direct game challenge
41func CreateGame(caller address, opponent address) error {
42 vCaller, err := getVerifiedCaller(caller)
43 if err != nil {
44 return err
45 }
46
47 if vCaller == opponent {
48 return errors.New("tidak bisa bermain melawan diri sendiri")
49 }
50
51 newGame := NewGame(nextGameID, vCaller, opponent)
52 games = append(games, newGame)
53 nextGameID++
54
55 return nil
56}
57
58// JoinQueue registers a player into the on-chain matchmaking queue.
59// If the queue is empty, the player is added to the queue.
60// If another player is already waiting, both players are immediately matched into a new game.
61func JoinQueue(caller address) error {
62 vCaller, err := getVerifiedCaller(caller)
63 if err != nil {
64 return err
65 }
66
67 // Check if player is already in queue
68 for _, addr := range matchmakingQueue {
69 if addr == vCaller {
70 return errors.New("anda sudah berada di dalam antrean matchmaking")
71 }
72 }
73
74 // If queue is empty, add caller to queue
75 if len(matchmakingQueue) == 0 {
76 matchmakingQueue = append(matchmakingQueue, vCaller)
77 return nil
78 }
79
80 // If a player is waiting, take the first player (FIFO)
81 opponent := matchmakingQueue[0]
82 matchmakingQueue = matchmakingQueue[1:]
83
84 // Create new game: waiting player becomes White, caller becomes Black
85 newGame := NewGame(nextGameID, opponent, vCaller)
86 games = append(games, newGame)
87 nextGameID++
88
89 return nil
90}
91
92// LeaveQueue cancels and removes a player from the on-chain matchmaking queue
93func LeaveQueue(caller address) error {
94 vCaller, err := getVerifiedCaller(caller)
95 if err != nil {
96 return err
97 }
98
99 for i, addr := range matchmakingQueue {
100 if addr == vCaller {
101 matchmakingQueue = append(matchmakingQueue[:i], matchmakingQueue[i+1:]...)
102 return nil
103 }
104 }
105 return errors.New("anda tidak sedang berada di dalam antrean matchmaking")
106}
107
108func makeMoveInternal(caller address, gameID int, fromRow, fromCol, toRow, toCol int, promoPiece string) error {
109 vCaller, err := getVerifiedCaller(caller)
110 if err != nil {
111 return err
112 }
113
114 if gameID < 0 || gameID >= len(games) {
115 return errors.New("game tidak ditemukan")
116 }
117
118 g := games[gameID]
119
120 if g.status != "PLAYING" {
121 return errors.New("game sudah selesai")
122 }
123
124 if g.isWhiteTurn && vCaller != g.white {
125 return errors.New("bukan giliran Anda (Putih)")
126 }
127 if !g.isWhiteTurn && vCaller != g.black {
128 return errors.New("bukan giliran Anda (Hitam)")
129 }
130
131 if fromRow < 0 || fromRow > 7 || fromCol < 0 || fromCol > 7 ||
132 toRow < 0 || toRow > 7 || toCol < 0 || toCol > 7 {
133 return errors.New("koordinat di luar papan")
134 }
135
136 piece := g.board[fromRow][fromCol]
137 if piece == "." {
138 return errors.New("tidak ada bidak di posisi asal")
139 }
140
141 // Delegate move validation to package p/demo/chess
142 if !chess.IsValidMove(g.board, fromRow, fromCol, toRow, toCol, g.isWhiteTurn, g.castling, g.enPassantCol) {
143 return errors.New("langkah catur tidak valid")
144 }
145
146 isWhite := chess.IsWhitePiece(piece)
147
148 // Validate pawn promotion
149 isPromotion := (piece == "P" && toRow == 0) || (piece == "p" && toRow == 7)
150 var finalPiece string = piece
151 if isPromotion {
152 promoted, err := chess.NormalizePromotionPiece(promoPiece, isWhite)
153 if err != nil {
154 return err
155 }
156 finalPiece = promoted
157 }
158
159 isCastling := (piece == "K" || piece == "k") && chess.Abs(toCol-fromCol) == 2
160 isEnPassant := (piece == "P" || piece == "p") && toCol == g.enPassantCol && fromCol != toCol && g.board[toRow][toCol] == "."
161
162 // Verify move does not leave own king in check
163 origDest := g.board[toRow][toCol]
164 g.board[toRow][toCol] = piece
165 g.board[fromRow][fromCol] = "."
166
167 var epRow int
168 var epPiece string
169 if isEnPassant {
170 if isWhite {
171 epRow = 3
172 } else {
173 epRow = 4
174 }
175 epPiece = g.board[epRow][toCol]
176 g.board[epRow][toCol] = "."
177 }
178
179 inCheck := chess.IsKingInCheck(g.board, isWhite)
180
181 // Revert simulation
182 g.board[fromRow][fromCol] = piece
183 g.board[toRow][toCol] = origDest
184 if isEnPassant {
185 g.board[epRow][toCol] = epPiece
186 }
187
188 if inCheck {
189 return errors.New("langkah ilegal: tidak boleh membiarkan Raja Anda terkena skak")
190 }
191
192 // Execute move
193 g.board[toRow][toCol] = finalPiece
194 g.board[fromRow][fromCol] = "."
195
196 // Execute En Passant capture
197 if isEnPassant {
198 if isWhite {
199 g.board[3][toCol] = "."
200 } else {
201 g.board[4][toCol] = "."
202 }
203 }
204
205 // Move Rook during castling
206 if isCastling {
207 if isWhite {
208 if toCol == 6 { // Kingside
209 g.board[7][5] = "R"
210 g.board[7][7] = "."
211 } else if toCol == 2 { // Queenside
212 g.board[7][3] = "R"
213 g.board[7][0] = "."
214 }
215 } else {
216 if toCol == 6 { // Kingside
217 g.board[0][5] = "r"
218 g.board[0][7] = "."
219 } else if toCol == 2 { // Queenside
220 g.board[0][3] = "r"
221 g.board[0][0] = "."
222 }
223 }
224 }
225
226 // Update castling rights
227 if piece == "K" {
228 g.castling.WhiteKingMoved = true
229 } else if piece == "k" {
230 g.castling.BlackKingMoved = true
231 } else if piece == "R" {
232 if fromRow == 7 && fromCol == 0 {
233 g.castling.WhiteLeftRookMoved = true
234 } else if fromRow == 7 && fromCol == 7 {
235 g.castling.WhiteRightRookMoved = true
236 }
237 } else if piece == "r" {
238 if fromRow == 0 && fromCol == 0 {
239 g.castling.BlackLeftRookMoved = true
240 } else if fromRow == 0 && fromCol == 7 {
241 g.castling.BlackRightRookMoved = true
242 }
243 }
244
245 // Update en passant vulnerability
246 if (piece == "P" || piece == "p") && chess.Abs(toRow-fromRow) == 2 {
247 g.enPassantCol = fromCol
248 } else {
249 g.enPassantCol = -1
250 }
251
252 // Detect Checkmate or Stalemate for opponent
253 opponentIsWhite := !isWhite
254 if !chess.HasAnyLegalMoves(g.board, opponentIsWhite, g.castling, g.enPassantCol) {
255 if chess.IsKingInCheck(g.board, opponentIsWhite) {
256 if isWhite {
257 g.status = "WHITE_WON"
258 } else {
259 g.status = "BLACK_WON"
260 }
261 } else {
262 g.status = "DRAW"
263 }
264 }
265
266 g.isWhiteTurn = !g.isWhiteTurn
267 g.drawOfferedBy = ""
268 g.turnDeadline = 0
269 g.turnChallengedBy = ""
270
271 return nil
272}
273
274// MakeMove moves a piece (defaults to Queen on promotion)
275func MakeMove(caller address, gameID int, fromRow, fromCol, toRow, toCol int) error {
276 return makeMoveInternal(caller, gameID, fromRow, fromCol, toRow, toCol, "Q")
277}
278
279// MakeMovePromote moves a piece with a specified promotion piece (Q, R, B, N)
280func MakeMovePromote(caller address, gameID int, fromRow, fromCol, toRow, toCol int, promoPiece string) error {
281 return makeMoveInternal(caller, gameID, fromRow, fromCol, toRow, toCol, promoPiece)
282}
283
284// Resign resigns the game for the calling player
285func Resign(caller address, gameID int) error {
286 vCaller, err := getVerifiedCaller(caller)
287 if err != nil {
288 return err
289 }
290
291 if gameID < 0 || gameID >= len(games) {
292 return errors.New("game tidak ditemukan")
293 }
294
295 g := games[gameID]
296
297 if g.status != "PLAYING" && g.status != "PROPOSED" {
298 return errors.New("game sudah selesai")
299 }
300
301 if vCaller != g.white && vCaller != g.black {
302 return errors.New("hanya pemain dalam game ini yang dapat menyerah")
303 }
304
305 g.resignedBy = vCaller
306 if vCaller == g.white {
307 g.status = "BLACK_WON"
308 } else {
309 g.status = "WHITE_WON"
310 }
311
312 g.settledBy = vCaller
313 g.drawOfferedBy = ""
314 g.turnDeadline = 0
315 g.turnChallengedBy = ""
316 g.challengeDeadline = 0
317 return nil
318}
319
320// OfferDraw offers a draw to the opponent
321func OfferDraw(caller address, gameID int) error {
322 vCaller, err := getVerifiedCaller(caller)
323 if err != nil {
324 return err
325 }
326
327 if gameID < 0 || gameID >= len(games) {
328 return errors.New("game tidak ditemukan")
329 }
330
331 g := games[gameID]
332
333 if g.status != "PLAYING" {
334 return errors.New("game sudah selesai")
335 }
336
337 if vCaller != g.white && vCaller != g.black {
338 return errors.New("hanya pemain dalam game ini yang dapat menawarkan remis")
339 }
340
341 if g.drawOfferedBy == vCaller {
342 return errors.New("anda sudah mengajukan tawaran remis")
343 }
344
345 if g.drawOfferedBy != "" && g.drawOfferedBy != vCaller {
346 g.status = "DRAW"
347 g.drawOfferedBy = ""
348 return nil
349 }
350
351 g.drawOfferedBy = vCaller
352 return nil
353}
354
355// AcceptDraw accepts a draw offer from the opponent
356func AcceptDraw(caller address, gameID int) error {
357 vCaller, err := getVerifiedCaller(caller)
358 if err != nil {
359 return err
360 }
361
362 if gameID < 0 || gameID >= len(games) {
363 return errors.New("game tidak ditemukan")
364 }
365
366 g := games[gameID]
367
368 if g.status != "PLAYING" {
369 return errors.New("game sudah selesai")
370 }
371
372 if vCaller != g.white && vCaller != g.black {
373 return errors.New("hanya pemain dalam game ini yang dapat menerima remis")
374 }
375
376 if g.drawOfferedBy == "" {
377 return errors.New("tidak ada tawaran remis yang sedang aktif")
378 }
379
380 if g.drawOfferedBy == vCaller {
381 return errors.New("tidak bisa menerima tawaran remis yang Anda ajukan sendiri")
382 }
383
384 g.status = "DRAW"
385 g.drawOfferedBy = ""
386 return nil
387}
388
389// DeclineDraw declines a draw offer from the opponent
390func DeclineDraw(caller address, gameID int) error {
391 vCaller, err := getVerifiedCaller(caller)
392 if err != nil {
393 return err
394 }
395
396 if gameID < 0 || gameID >= len(games) {
397 return errors.New("game tidak ditemukan")
398 }
399
400 g := games[gameID]
401
402 if g.status != "PLAYING" {
403 return errors.New("game sudah selesai")
404 }
405
406 if vCaller != g.white && vCaller != g.black {
407 return errors.New("hanya pemain dalam game ini yang dapat menolak remis")
408 }
409
410 if g.drawOfferedBy == "" {
411 return errors.New("tidak ada tawaran remis yang sedang aktif")
412 }
413
414 if g.drawOfferedBy == vCaller {
415 return errors.New("tidak bisa menolak tawaran remis yang Anda ajukan sendiri")
416 }
417
418 g.drawOfferedBy = ""
419 return nil
420}
421
422// ProposeSettlement submits a victory claim for off-chain chess results.
423// Prevents fraudulent opponent moves: proposal opens a challenge period.
424// Opponent can confirm immediately (ConfirmSettlement), or dispute if cheated (DisputeSettlement).
425// If opponent does not respond, winner can finalize after ChallengeWindowSeconds expires.
426func ProposeSettlement(caller address, gameID int, movesUCI string) error {
427 vCaller, err := getVerifiedCaller(caller)
428 if err != nil {
429 return err
430 }
431
432 if gameID < 0 || gameID >= len(games) {
433 return errors.New("game tidak ditemukan")
434 }
435
436 g := games[gameID]
437
438 if vCaller != g.white && vCaller != g.black {
439 return errors.New("hanya pemain dalam game ini yang dapat mengajukan settlement")
440 }
441
442 if g.settledBy != "" {
443 return errors.New("game ini sudah diselesaikan di blockchain")
444 }
445
446 if g.status != "PLAYING" {
447 return errors.New("game ini sedang dalam status " + g.status)
448 }
449
450 // Replay chess moves from the beginning using package gno.land/p/demo/chess
451 finalBoard, _, status, err := chess.ReplayUCI(movesUCI)
452 if err != nil {
453 return errors.New("verifikasi langkah catur gagal: " + err.Error())
454 }
455
456 if status == "PLAYING" {
457 return errors.New("rangkaian langkah catur belum menghasilkan skakmat atau remis")
458 }
459
460 // Verify winner claim rights
461 if status == "WHITE_WON" && vCaller != g.white {
462 return errors.New("hanya pemain Putih yang berhak klaim kemenangan untuk White")
463 }
464 if status == "BLACK_WON" && vCaller != g.black {
465 return errors.New("hanya pemain Hitam yang berhak klaim kemenangan untuk Black")
466 }
467
468 g.board = finalBoard
469 g.proposedBy = vCaller
470 g.pendingStatus = status
471 g.challengeDeadline = time.Now().Unix() + ChallengeWindowSeconds
472 g.status = "PROPOSED"
473 g.drawOfferedBy = ""
474 g.turnDeadline = 0
475 g.turnChallengedBy = ""
476
477 return nil
478}
479
480// ConfirmSettlement allows opponent to immediately approve final result without waiting for challenge window
481func ConfirmSettlement(caller address, gameID int) error {
482 vCaller, err := getVerifiedCaller(caller)
483 if err != nil {
484 return err
485 }
486
487 if gameID < 0 || gameID >= len(games) {
488 return errors.New("game tidak ditemukan")
489 }
490
491 g := games[gameID]
492
493 if g.status != "PROPOSED" {
494 return errors.New("tidak ada proposal settlement yang sedang aktif")
495 }
496
497 if vCaller != g.white && vCaller != g.black {
498 return errors.New("hanya pemain dalam game ini yang dapat mengonfirmasi")
499 }
500
501 if vCaller == g.proposedBy {
502 return errors.New("tidak bisa mengonfirmasi proposal yang Anda ajukan sendiri")
503 }
504
505 g.status = g.pendingStatus
506 g.settledBy = g.proposedBy
507 g.challengeDeadline = 0
508 return nil
509}
510
511// FinalizeSettlement officially finalizes the game after the challenge period has passed
512func FinalizeSettlement(caller address, gameID int) error {
513 vCaller, err := getVerifiedCaller(caller)
514 if err != nil {
515 return err
516 }
517
518 if gameID < 0 || gameID >= len(games) {
519 return errors.New("game tidak ditemukan")
520 }
521
522 g := games[gameID]
523
524 if g.status != "PROPOSED" {
525 return errors.New("tidak ada proposal settlement yang sedang aktif")
526 }
527
528 if vCaller != g.white && vCaller != g.black {
529 return errors.New("hanya pemain dalam game ini yang dapat memfinalisasi")
530 }
531
532 now := time.Now().Unix()
533 if now < g.challengeDeadline {
534 return errors.New("jendela sanggahan (challenge period) belum berakhir")
535 }
536
537 g.status = g.pendingStatus
538 g.settledBy = g.proposedBy
539 g.challengeDeadline = 0
540 return nil
541}
542
543// DisputeSettlement allows opponent to dispute a fraudulent settlement proposal
544func DisputeSettlement(caller address, gameID int) error {
545 vCaller, err := getVerifiedCaller(caller)
546 if err != nil {
547 return err
548 }
549
550 if gameID < 0 || gameID >= len(games) {
551 return errors.New("game tidak ditemukan")
552 }
553
554 g := games[gameID]
555
556 if g.status != "PROPOSED" {
557 return errors.New("tidak ada proposal settlement yang sedang aktif")
558 }
559
560 if vCaller != g.white && vCaller != g.black {
561 return errors.New("hanya pemain dalam game ini yang dapat menyanggah")
562 }
563
564 if vCaller == g.proposedBy {
565 return errors.New("tidak bisa menyanggah proposal yang Anda ajukan sendiri")
566 }
567
568 // Cancel fraudulent proposal and revert game status back to PLAYING
569 g.status = "PLAYING"
570 g.proposedBy = ""
571 g.pendingStatus = ""
572 g.challengeDeadline = 0
573 return nil
574}
575
576// SettleGame proposes settlement and finalizes immediately if challenge window is 0
577func SettleGame(caller address, gameID int, movesUCI string) error {
578 err := ProposeSettlement(caller, gameID, movesUCI)
579 if err != nil {
580 return err
581 }
582 if ChallengeWindowSeconds <= 0 {
583 return FinalizeSettlement(caller, gameID)
584 }
585 return nil
586}
587
588// StartTurnTimer is initiated when opponent goes AFK or closes browser (Rage Quit) during an off-chain game.
589// Player submits current move history (currentMovesUCI).
590// Smart contract validates the board and ensures it is the OPPONENT'S TURN.
591func StartTurnTimer(caller address, gameID int, currentMovesUCI string) error {
592 vCaller, err := getVerifiedCaller(caller)
593 if err != nil {
594 return err
595 }
596
597 if gameID < 0 || gameID >= len(games) {
598 return errors.New("game tidak ditemukan")
599 }
600
601 g := games[gameID]
602
603 if g.status != "PLAYING" {
604 return errors.New("game sudah selesai")
605 }
606
607 if vCaller != g.white && vCaller != g.black {
608 return errors.New("hanya pemain dalam game ini yang dapat menuntut timer giliran")
609 }
610
611 currentBoard, nextIsWhiteTurn, status, err := chess.ReplayUCI(currentMovesUCI)
612 if err != nil {
613 return errors.New("verifikasi langkah posisi saat ini gagal: " + err.Error())
614 }
615
616 if status != "PLAYING" {
617 return errors.New("posisi catur ini sudah selesai (skakmat/remis), gunakan SettleGame")
618 }
619
620 // Validate that next turn is the opponent's turn, not the caller's
621 if vCaller == g.white && nextIsWhiteTurn {
622 return errors.New("saat ini adalah giliran Anda sendiri (Putih), lakukan langkah Anda terlebih dahulu")
623 }
624 if vCaller == g.black && !nextIsWhiteTurn {
625 return errors.New("saat ini adalah giliran Anda sendiri (Hitam), lakukan langkah Anda terlebih dahulu")
626 }
627
628 g.board = currentBoard
629 g.isWhiteTurn = nextIsWhiteTurn
630 g.turnChallengedBy = vCaller
631 g.turnDeadline = time.Now().Unix() + TurnTimeoutSeconds
632
633 return nil
634}
635
636// ClaimTimeout allows the challenger to win the game after the opponent goes AFK/rage quits
637// and turnDeadline has passed without a response move from the opponent.
638func ClaimTimeout(caller address, gameID int) error {
639 vCaller, err := getVerifiedCaller(caller)
640 if err != nil {
641 return err
642 }
643
644 if gameID < 0 || gameID >= len(games) {
645 return errors.New("game tidak ditemukan")
646 }
647
648 g := games[gameID]
649
650 if g.status != "PLAYING" {
651 return errors.New("game sudah selesai")
652 }
653
654 if g.turnDeadline == 0 {
655 return errors.New("tidak ada batas waktu giliran yang sedang berjalan")
656 }
657
658 now := time.Now().Unix()
659 if now < g.turnDeadline {
660 return errors.New("batas waktu giliran lawan belum habis")
661 }
662
663 if vCaller != g.turnChallengedBy {
664 return errors.New("hanya pemain yang menuntut timer yang dapat mengklaim kemenangan timeout")
665 }
666
667 if vCaller == g.white {
668 g.status = "WHITE_WON"
669 } else {
670 g.status = "BLACK_WON"
671 }
672
673 g.settledBy = vCaller
674 g.turnDeadline = 0
675 g.turnChallengedBy = ""
676
677 return nil
678}
679
680// GetGamesCount returns the total number of games
681func GetGamesCount() int {
682 return len(games)
683}
684
685// GetBoard returns the 8x8 board for a given game ID
686func GetBoard(gameID int) [8][8]string {
687 if gameID < 0 || gameID >= len(games) {
688 panic("game tidak ditemukan")
689 }
690 return [8][8]string(games[gameID].board)
691}
692
693// GetWhitePlayer returns the White player address for a given game ID
694func GetWhitePlayer(gameID int) address {
695 if gameID < 0 || gameID >= len(games) {
696 panic("game tidak ditemukan")
697 }
698 return games[gameID].white
699}
700
701// GetBlackPlayer returns the Black player address for a given game ID
702func GetBlackPlayer(gameID int) address {
703 if gameID < 0 || gameID >= len(games) {
704 panic("game tidak ditemukan")
705 }
706 return games[gameID].black
707}
708
709// IsWhiteTurn returns whether it is White's turn for a given game ID
710func IsWhiteTurn(gameID int) bool {
711 if gameID < 0 || gameID >= len(games) {
712 panic("game tidak ditemukan")
713 }
714 return games[gameID].isWhiteTurn
715}
716
717// GetGameStatus returns the game status for a given game ID
718func GetGameStatus(gameID int) string {
719 if gameID < 0 || gameID >= len(games) {
720 panic("game tidak ditemukan")
721 }
722 return games[gameID].status
723}
724
725// GetDrawOfferedBy returns the address that offered a draw, or empty address if none
726func GetDrawOfferedBy(gameID int) address {
727 if gameID < 0 || gameID >= len(games) {
728 panic("game tidak ditemukan")
729 }
730 return games[gameID].drawOfferedBy
731}
732
733// GetResignedBy returns the address of the player who resigned, or empty address if none
734func GetResignedBy(gameID int) address {
735 if gameID < 0 || gameID >= len(games) {
736 panic("game tidak ditemukan")
737 }
738 return games[gameID].resignedBy
739}
740
741// GetQueueCount returns the number of players waiting in the matchmaking queue
742func GetQueueCount() int {
743 return len(matchmakingQueue)
744}
745
746// IsInQueue checks if an address is currently waiting in the matchmaking queue
747func IsInQueue(addr address) bool {
748 for _, a := range matchmakingQueue {
749 if a == addr {
750 return true
751 }
752 }
753 return false
754}
755
756// GetActiveGameForPlayer searches for the latest active game ("PLAYING") for a given address.
757// Returns -1 if no active game found.
758func GetActiveGameForPlayer(addr address) int {
759 for i := len(games) - 1; i >= 0; i-- {
760 g := games[i]
761 if (g.white == addr || g.black == addr) && g.status == "PLAYING" {
762 return g.id
763 }
764 }
765 return -1
766}
767
768// GetLatestGameForPlayer returns the latest game ID involving the player, or -1 if none.
769func GetLatestGameForPlayer(addr address) int {
770 for i := len(games) - 1; i >= 0; i-- {
771 g := games[i]
772 if g.white == addr || g.black == addr {
773 return g.id
774 }
775 }
776 return -1
777}
778
779// GetSettledBy returns the address that finalized the game settlement on-chain
780func GetSettledBy(gameID int) address {
781 if gameID < 0 || gameID >= len(games) {
782 panic("game tidak ditemukan")
783 }
784 return games[gameID].settledBy
785}
786
787// GetProposedBy returns the address that proposed settlement
788func GetProposedBy(gameID int) address {
789 if gameID < 0 || gameID >= len(games) {
790 panic("game tidak ditemukan")
791 }
792 return games[gameID].proposedBy
793}
794
795// GetPendingStatus returns the pending status of settlement
796func GetPendingStatus(gameID int) string {
797 if gameID < 0 || gameID >= len(games) {
798 panic("game tidak ditemukan")
799 }
800 return games[gameID].pendingStatus
801}
802
803// GetChallengeDeadline returns the deadline unix timestamp for challenging settlement
804func GetChallengeDeadline(gameID int) int64 {
805 if gameID < 0 || gameID >= len(games) {
806 panic("game tidak ditemukan")
807 }
808 return games[gameID].challengeDeadline
809}
810
811// GetTurnDeadline returns the deadline unix timestamp for turn timeout
812func GetTurnDeadline(gameID int) int64 {
813 if gameID < 0 || gameID >= len(games) {
814 panic("game tidak ditemukan")
815 }
816 return games[gameID].turnDeadline
817}
818
819// GetTurnChallengedBy returns the address that initiated the turn timer
820func GetTurnChallengedBy(gameID int) address {
821 if gameID < 0 || gameID >= len(games) {
822 panic("game tidak ditemukan")
823 }
824 return games[gameID].turnChallengedBy
825}
826