diff --git a/room_contract.go b/room_contract.go deleted file mode 100644 index 3150a36..0000000 --- a/room_contract.go +++ /dev/null @@ -1,468 +0,0 @@ -package contracts - -import ( - "fmt" - "github.com/google/uuid" - "github.com/nspcc-dev/neo-go/pkg/interop" - "github.com/nspcc-dev/neo-go/pkg/interop/native/std" - "github.com/nspcc-dev/neo-go/pkg/interop/runtime" - "github.com/nspcc-dev/neo-go/pkg/interop/storage" - "sort" -) - -var notificationName string - -// init initializes notificationName before calling any other smart-contract method -func init() { - notificationName = "Hello world!" -} - -// RuntimeNotify sends runtime notification with "Hello world!" name -func RuntimeNotify(args []any) { - runtime.Notify(notificationName, args) -} - -// CONSTANTS - -const ( - RoomStatusWaiting = "waiting" // Waiting for the game to start, players are joining - RoomStatusGaming = "gaming" // In this phase, players are ready, but the question hasn't been asked yet - RoomStatusAnswering = "answering" // Phase when the round has started and the question has been asked, players can submit answers - RoomStatusVoting = "voting" // Voting phase, where players select the best answer from the options - RoomStatusFinished = "finished" // Game is finished, and results have been determined -) - -// STRUCTS - -type Room struct { - Id string - Host interop.Hash160 - Status string - PrizePool int - RoundWinnersCount int - GameWinnersCount int - Players []Player - Rounds []Round -} - -type Round struct { - Question string - Answers []Answer -} - -type Answer struct { - Wallet interop.Hash160 - Content string - Votes []interop.Hash160 // Wallets who voted for answer -} - -type Player struct { - Wallet interop.Hash160 - RoundsWon int - IsReady bool - IsVotedToFinish bool -} - -// GLOBAL PRIVATE METHODS FOR ROOM - -func getSender() interop.Hash160 { - return runtime.GetScriptContainer().Sender -} - -// Function to set room in storage with serialize -func setRoom(ctx storage.Context, room *Room) { - var serializedRoom = std.Serialize(room) - storage.Put(ctx, "room:"+room.Id, serializedRoom) -} - -// Function to get room from storage with deserialize -func getRoom(ctx storage.Context, roomId string) Room { - var roomData = storage.Get(ctx, "room:"+roomId) - - if roomData == nil { - panic(fmt.Sprintf("Room with roomId=%s not found", roomId)) - } - - var room = std.Deserialize(roomData.([]byte)).(Room) - return room -} - -// Function to send message to players, event is recorded in blockchain -// Could be read through getapplicationlog or RPC call -func sendMessageToPlayers(notificationName, message string) { - runtime.Notify(notificationName, message) -} - -// MAIN METHODS TO PLAY IN GAME - -func CreateRoom(host interop.Hash160, RoundWinnersCount, GameWinnersCount int) string { - var ctx = storage.GetContext() - var id = uuid.NewString() - var room = Room{ - Id: id, - Host: host, - Status: RoomStatusWaiting, - PrizePool: 0, - RoundWinnersCount: RoundWinnersCount, - GameWinnersCount: GameWinnersCount, - Players: []Player{}, - Rounds: []Round{}, - } - - // todo: Добавить списание токенов за создание комнаты - - setRoom(ctx, &room) - return id -} - -func JoinRoom(roomId string) bool { - var ctx = storage.GetContext() - var room = getRoom(ctx, roomId) - var wallet = getSender() - - if room.Host.Equals(wallet) || room.Status != RoomStatusWaiting { - return false // Host can not be player, player cannot join started room - } - - for _, player := range room.Players { - if player.Wallet.Equals(wallet) { - return false // Player already joined room - } - } - - // todo: Добавить списание токенов за вход в комнату - - var player = Player{ - Wallet: wallet, - RoundsWon: 0, - IsReady: false, - IsVotedToFinish: false, - } - - room.Players = append(room.Players, player) - setRoom(ctx, &room) - return true -} - -func ConfirmReadiness(roomId string) bool { - var ctx = storage.GetContext() - var room = getRoom(ctx, roomId) - var wallet = getSender() - - for i, p := range room.Players { - if p.Wallet.Equals(wallet) { - if p.IsReady { - return false // Player is already ready - } - room.Players[i].IsReady = true - setRoom(ctx, &room) - return true - } - } - - return false // Player not found in the room -} - -func StartGame(roomId string) bool { - var ctx = storage.GetContext() - var room = getRoom(ctx, roomId) - - if !room.Host.Equals(getSender()) || room.Status != RoomStatusWaiting || len(room.Players) <= room.RoundWinnersCount { - return false // Only host can start game, room status must be waiting and players count must be > count winners - } - - for _, player := range room.Players { - if !player.IsReady { - return false // If any player is not ready, the game can not be started - } - } - - room.Status = RoomStatusGaming - setRoom(ctx, &room) - return true -} - -func AskQuestion(roomId string, question string) bool { - var ctx = storage.GetContext() - var room = getRoom(ctx, roomId) - - if !room.Host.Equals(getSender()) || room.Status != RoomStatusGaming { - return false // Only host can ask question, room status must be gaming - } - - var round = Round{ - Question: question, - Answers: []Answer{}, - } - room.Rounds = append(room.Rounds, round) - room.Status = RoomStatusAnswering - - sendMessageToPlayers("RoundQuestion", question) - - // todo: Не забыть про nns - // todo: Добавить списание токенов за создание вопроса - - setRoom(ctx, &room) - return true -} - -func roomContainsPlayer(players []Player, wallet interop.Hash160) bool { - for _, player := range players { - if player.Wallet.Equals(wallet) { - return true - } - } - return false -} - -func SendAnswer(roomId string, text string) bool { - var ctx = storage.GetContext() - var room = getRoom(ctx, roomId) - var wallet = getSender() - - if !roomContainsPlayer(room.Players, wallet) || room.Status != RoomStatusAnswering { - return false // Only player can send content, room status must be answering - } - - // todo: Добавить списание токенов за добавление ответа - - var round = room.Rounds[len(room.Rounds)-1] - - for _, answer := range round.Answers { - if answer.Wallet.Equals(wallet) { - return false // Player cannot send answer twice - } - } - - var answer = Answer{ - Wallet: wallet, - Content: text, - Votes: []interop.Hash160{}, - } - - round.Answers = append(round.Answers, answer) - room.Rounds[len(room.Rounds)-1] = round - setRoom(ctx, &room) - return true -} - -func EndQuestion(roomId string) bool { - var ctx = storage.GetContext() - var room = getRoom(ctx, roomId) - - if !room.Host.Equals(getSender()) || room.Status != RoomStatusAnswering { - return false // Only host can end question, room status must be answering - } - - room.Status = RoomStatusVoting - - var round = room.Rounds[len(room.Rounds)-1] - var result string - for i, answer := range round.Answers { - result += fmt.Sprintf("index:%d, player:%s, answer:%s\n", i, answer.Wallet, answer.Content) - } // todo: нужно проверить нормально ли всё с \n в логах - - sendMessageToPlayers("RoundAnswers", result) - - setRoom(ctx, &room) - return true -} - -func VoteAnswer(roomId string, answerIdx int) bool { - var ctx = storage.GetContext() - var room = getRoom(ctx, roomId) - var wallet = getSender() - - if room.Host.Equals(wallet) || room.Status != RoomStatusVoting { - return false // Only player can choose answer, room status must be voting - } - - var round = room.Rounds[len(room.Rounds)-1] - if !(0 <= answerIdx && answerIdx < len(round.Answers)) || round.Answers[answerIdx].Wallet.Equals(wallet) { - return false // answerIdx is incorrect and player cannot vote for himself - } - - for _, votedWallet := range round.Answers[answerIdx].Votes { - if votedWallet.Equals(wallet) { - return false // Player cannot vote twice for one answer - } - } - - round.Answers[answerIdx].Votes = append(round.Answers[answerIdx].Votes, wallet) - setRoom(ctx, &room) - return true -} - -func chooseWonAnswers(round Round, RoundWinnersCount int) []Answer { - var wonAnswers []Answer - var answers = round.Answers - sort.Slice(answers, func(a, b int) bool { - return len(answers[a].Votes) > len(answers[b].Votes) - }) - - if RoundWinnersCount > len(round.Answers) { - return round.Answers - } - - if RoundWinnersCount == 1 { - return []Answer{answers[0]} - } - - // Choose wonAnswers from sorted answers. If the current answer has the same number of votes as the previous one, - // we add it to the wonAnswers list. We increase the number of wonAnswers if there are multiple answers with the same - // number of votes, as in cases where there are 5 answers with equal votes and RoundWinnersCount is 3, all should be included. - var lastVote = len(answers[0].Votes) - wonAnswers = append(wonAnswers, answers[0]) - for i := 1; i < len(answers) && len(wonAnswers) < RoundWinnersCount; i++ { - var currentVote = len(answers[i].Votes) - if lastVote == currentVote { - RoundWinnersCount++ // todo: Могут возникнуть проблемы, надо протестировать - } - lastVote = currentVote - wonAnswers = append(wonAnswers, answers[i]) - } - - return wonAnswers -} - -// GetRoundWinner todo(-): Нам сказали, что ссылку на другой контракт нельзя чисто, используем nns -// GetRoundWinner todo: Разные реализации распределения наград можно реализовать в контракте с деньгами -func GetRoundWinner(roomId string) bool { - var ctx = storage.GetContext() - var room = getRoom(ctx, roomId) - - if !room.Host.Equals(getSender()) || room.Status != RoomStatusVoting { - return false // Only host can get winner, room status must be voting - } - - var round = room.Rounds[len(room.Rounds)-1] - if len(round.Answers) == 0 { - return false // Zero winners, because no answer - } - - var wonAnswers = chooseWonAnswers(round, room.RoundWinnersCount) - var players = room.Players - for _, answer := range wonAnswers { - for _, player := range players { - if answer.Wallet.Equals(player.Wallet) { - player.RoundsWon++ - break - } - } - } - room.Players = players - - var result string - for i, answer := range wonAnswers { - result += fmt.Sprintf("place:%d, winner:%s, votes:%s\n", i, answer.Wallet, answer.Votes) - } - - sendMessageToPlayers("RoundWinners", result) - - // todo: Отправка награды победителям - - room.Status = RoomStatusGaming // Next game cycle available to AskQuestion - setRoom(ctx, &room) - return true -} - -func automaticFinishGame(ctx storage.Context, room *Room, voted int) bool { - if voted != len(room.Players) { - return false // All players must have voted to finish the game - } - - return finishGame(ctx, room) -} - -func VoteToFinishGame(roomId string) bool { - var ctx = storage.GetContext() - var room = getRoom(ctx, roomId) - var wallet = getSender() - - if room.Host.Equals(wallet) { - return false // Host cannot vote to finish game - } - - var voted = 0 - var isFound = false - for i, p := range room.Players { - if p.Wallet.Equals(wallet) { - if p.IsVotedToFinish { - return false // Player has already voted to finish the game - } - room.Players[i].IsVotedToFinish = true - isFound = true - } - - if p.IsVotedToFinish { - voted++ // Count voted players to finish the game - } - } - - if !isFound { - return false // Player was not found in the room - } - - // todo: Можно добавить notify для уведомления, сколько людей проголосовало за завершение игры - - return automaticFinishGame(ctx, &room, voted) -} - -func ManuallyFinishGame(roomId string) bool { - var ctx = storage.GetContext() - var room = getRoom(ctx, roomId) - - if !room.Host.Equals(getSender()) || room.Status != RoomStatusGaming { - return false // Only host can finish game, room status must be gaming - } - - return finishGame(ctx, &room) -} - -func finishGame(ctx storage.Context, room *Room) bool { - var winners = getGameWinner(room) - - // todo: Отправка награды победителям и хосту - - // todo: Нотификация результатов всей игры - - room.Status = RoomStatusFinished - setRoom(ctx, room) - return true -} - -// todo: @vr61v /check -func getGameWinner(room *Room) []Player { - var players = room.Players - var winners []Player - var GameWinnersCount = room.GameWinnersCount - - sort.Slice(players, func(a, b int) bool { - return players[a].RoundsWon > players[b].RoundsWon - }) - - if GameWinnersCount > len(room.Players) { - return room.Players - } - - if GameWinnersCount == 1 { - return []Player{players[0]} - } - - var lastWinner = players[0].RoundsWon - winners = append(winners, players[0]) - for i := 1; i < len(players) && len(winners) < GameWinnersCount; i++ { - var currentVote = players[i].RoundsWon - if lastWinner == currentVote { - GameWinnersCount++ // todo: Могут возникнуть проблемы, надо протестировать - } - lastWinner = currentVote - winners = append(winners, players[i]) - } - - return winners -} - -/* todo: Логика выкидывания игрока из игры, если пропускает несколько вопросов подряд - - * делаем булевый флаг по которому определяем, активен игрок или нет. - */