werewolf.go (20548B)
1 package interceptors 2 3 import ( 4 "bytes" 5 "context" 6 "dkforest/pkg/database" 7 dutils "dkforest/pkg/database/utils" 8 "dkforest/pkg/hashset" 9 "dkforest/pkg/utils" 10 "dkforest/pkg/web/handlers/interceptors/command" 11 "errors" 12 "fmt" 13 "github.com/sirupsen/logrus" 14 "html/template" 15 "math/rand" 16 "sort" 17 "strings" 18 "time" 19 ) 20 21 var WWInstance *Werewolf 22 23 const ( 24 PreGameState = iota + 1 25 DayState 26 NightState 27 VoteState 28 EndGameState 29 ) 30 31 const ( 32 TownspeopleRole = "townspeople" 33 WerewolfRole = "werewolf" 34 SeerRole = "seer" 35 HealerRole = "healer" 36 ) 37 38 var ErrInvalidPlayerName = errors.New("unknown player name, please send a valid name") 39 40 type Werewolf struct { 41 db *database.DkfDB 42 ctx context.Context 43 cancel context.CancelFunc 44 readyCh chan bool 45 narratorID database.UserID 46 roomID database.RoomID 47 werewolfGroupID database.GroupID 48 spectatorGroupID database.GroupID 49 deadGroupID database.GroupID 50 players map[database.Username]*Player 51 playersAlive map[database.Username]*Player 52 state int64 53 werewolfSet *hashset.HashSet[database.UserID] 54 spectatorSet *hashset.HashSet[database.UserID] 55 townspersonSet *hashset.HashSet[database.UserID] 56 healerID *database.UserID 57 seerID *database.UserID 58 werewolfCh chan string 59 seerCh chan string 60 healerCh chan string 61 votesCh chan string 62 voted *hashset.HashSet[database.UserID] // Keep track of which user voted already 63 } 64 65 // Return either or not the userID is an active player (alive) 66 func (b *Werewolf) isAlivePlayer(userID database.UserID) bool { 67 for _, player := range b.playersAlive { 68 if player.UserID == userID { 69 return true 70 } 71 } 72 return false 73 } 74 75 func (b *Werewolf) InterceptPreGameMsg(cmd *command.Command) { 76 if cmd.Message == "/players" { 77 b.Narrate("Registered players: "+b.alivePlayersStr(), nil, nil) 78 cmd.Err = command.ErrRedirect 79 return 80 81 } else if cmd.Message == "/join" { 82 if cmd.AuthUser.IsHellbanned { 83 cmd.Err = command.ErrRedirect 84 return 85 } 86 if _, found := b.players[cmd.AuthUser.Username]; found { 87 cmd.Err = command.ErrRedirect 88 return 89 } 90 player := &Player{ 91 UserID: cmd.AuthUser.ID, 92 Username: cmd.AuthUser.Username, 93 } 94 b.players[cmd.AuthUser.Username] = player 95 b.playersAlive[cmd.AuthUser.Username] = player 96 b.Narrate(cmd.AuthUser.Username.AtStr()+" joined the Game", nil, nil) 97 cmd.Err = command.ErrRedirect 98 return 99 100 } else if cmd.Message == "/spectate" { 101 b.spectatorSet.Insert(cmd.AuthUser.ID) 102 b.Narrate(cmd.AuthUser.Username.AtStr()+" spectate the Game", nil, nil) 103 cmd.Err = command.ErrRedirect 104 return 105 106 } else if cmd.Message == "/start" { 107 b.cancel() 108 time.Sleep(time.Second) 109 utils.SGo(func() { 110 b.StartGame(cmd.DB) 111 }) 112 cmd.Err = command.ErrRedirect 113 return 114 } 115 } 116 117 func (b *Werewolf) InterceptNightMsg(cmd *command.Command) { 118 if cmd.GroupID != nil && *cmd.GroupID == b.werewolfGroupID { 119 select { 120 case b.werewolfCh <- cmd.Message: 121 cmd.Err = command.ErrRedirect 122 default: 123 cmd.Err = errors.New("narrator doesn't need your input") 124 } 125 return 126 } else if b.isForNarrator(cmd) && b.seerID != nil && cmd.AuthUser.ID == *b.seerID { 127 select { 128 case b.seerCh <- cmd.Message: 129 cmd.Err = command.ErrRedirect 130 default: 131 cmd.Err = errors.New("narrator doesn't need your input") 132 } 133 return 134 } else if b.isForNarrator(cmd) && b.healerID != nil && cmd.AuthUser.ID == *b.healerID { 135 select { 136 case b.healerCh <- cmd.Message: 137 cmd.Err = command.ErrRedirect 138 default: 139 cmd.Err = errors.New("narrator doesn't need your input") 140 } 141 return 142 } 143 cmd.Err = errors.New("chat disabled") 144 return 145 } 146 147 // Return either or not the message is a PM for the narrator 148 func (b *Werewolf) isForNarrator(cmd *command.Command) bool { 149 return cmd.ToUser != nil && cmd.ToUser.ID == b.narratorID 150 } 151 152 func (b *Werewolf) InterceptVoteMsg(cmd *command.Command) { 153 if !b.isAlivePlayer(cmd.AuthUser.ID) || !b.isForNarrator(cmd) { 154 cmd.Err = errors.New("chat disabled") 155 return 156 } 157 if b.isForNarrator(cmd) { 158 if !b.voted.Contains(cmd.AuthUser.ID) { 159 name := cmd.Message 160 if b.isValidPlayerName(name) { 161 b.votesCh <- name 162 } else { 163 b.Narrate(ErrInvalidPlayerName.Error(), &cmd.AuthUser.ID, nil) 164 } 165 } else { 166 b.Narrate("You have already voted", &cmd.AuthUser.ID, nil) 167 } 168 } 169 } 170 171 var tuto = `Tutorial: 172 "/join" to join the Game 173 "/players" list the players that have joined the Game 174 "/start" to start the Game 175 "/stop" to stop the Game 176 "/ready" will skip the 5min conversation 177 "/tuto" will display this tutorial 178 "/clear" will reset the room and display this tutorial 179 180 Werewolf: To kill someone during the night, you have to reply in the "werewolf" group with the name of the person to kill (no @) 181 Seer/Healer: You have reply to the narrator with the name (eg: "/pm 0 n0tr1v") 182 Townspeople: To vote, you have to pm the narrator with a name (eg: "/pm 0 n0tr1v")` 183 184 func (b *Werewolf) InterceptMsg(cmd *command.Command) { 185 if cmd.Room.ID != b.roomID { 186 return 187 } 188 189 SlashInterceptor{}.InterceptMsg(cmd) 190 191 // If the message is a PM not for the narrator, we reject it 192 if cmd.ToUser != nil && (cmd.ToUser.ID != b.narratorID && cmd.AuthUser.ID != b.narratorID) { 193 cmd.Err = errors.New("PM not allowed at this room") 194 return 195 } 196 197 // Spectator can chat all the time 198 if cmd.GroupID != nil && *cmd.GroupID == b.spectatorGroupID { 199 return 200 } 201 202 if cmd.AuthUser.IsModerator() && cmd.Message == "/stop" { 203 b.Narrate(fmt.Sprintf("@%s used /stop", cmd.AuthUser.Username), nil, nil) 204 b.cancel() 205 cmd.Err = command.ErrRedirect 206 return 207 } else if cmd.AuthUser.IsModerator() && cmd.Message == "/ready" { 208 b.Narrate(fmt.Sprintf("@%s used /ready", cmd.AuthUser.Username), nil, nil) 209 b.readyCh <- true 210 cmd.Err = command.ErrRedirect 211 return 212 } else if cmd.AuthUser.IsModerator() && cmd.Message == "/tuto" { 213 b.Narrate(tuto, nil, nil) 214 cmd.Err = command.ErrRedirect 215 return 216 } else if cmd.AuthUser.IsModerator() && cmd.Message == "/clear" { 217 _ = cmd.DB.DeleteChatRoomMessages(b.roomID) 218 b.Narrate(tuto, nil, nil) 219 cmd.Err = command.ErrRedirect 220 return 221 } 222 223 // Anyone can talk during these states 224 if b.state == PreGameState || b.state == EndGameState { 225 if b.state == PreGameState { 226 b.InterceptPreGameMsg(cmd) 227 } 228 return 229 } 230 231 // Otherwise, non-playing people cannot talk in public chat 232 if !b.isAlivePlayer(cmd.AuthUser.ID) { 233 cmd.Err = errors.New("public chat disabled") 234 return 235 } 236 237 switch b.state { 238 case DayState: 239 case VoteState: 240 b.InterceptVoteMsg(cmd) 241 case NightState: 242 b.InterceptNightMsg(cmd) 243 default: 244 cmd.Err = errors.New("public chat disabled") 245 return 246 } 247 } 248 249 // Wait until we receive the votes from all the players 250 func (b *Werewolf) waitVotes() (votes []string) { 251 for len(votes) < len(b.playersAlive) { 252 var vote string 253 select { 254 case vote = <-b.votesCh: 255 case <-time.After(15 * time.Second): 256 b.Narrate(fmt.Sprintf("Waiting votes %d/%d", len(votes), len(b.playersAlive)), nil, nil) 257 continue 258 case <-b.ctx.Done(): 259 return 260 } 261 votes = append(votes, vote) 262 } 263 return 264 } 265 266 func (b *Werewolf) waitNameFromWerewolf() (name string) { 267 for { 268 select { 269 case name = <-b.werewolfCh: 270 case <-time.After(15 * time.Second): 271 b.Narrate("Waiting reply from werewolf", nil, nil) 272 continue 273 case <-b.ctx.Done(): 274 return 275 } 276 if b.isValidPlayerName(name) { 277 break 278 } 279 b.Narrate(ErrInvalidPlayerName.Error(), nil, &b.werewolfGroupID) 280 } 281 return name 282 } 283 284 func (b *Werewolf) waitNameFromSeer() (name string) { 285 for { 286 select { 287 case name = <-b.seerCh: 288 case <-time.After(15 * time.Second): 289 b.Narrate("Waiting reply from seer", nil, nil) 290 continue 291 case <-b.ctx.Done(): 292 return 293 } 294 if b.isValidPlayerName(name) { 295 break 296 } 297 b.Narrate(ErrInvalidPlayerName.Error(), b.seerID, nil) 298 } 299 return name 300 } 301 302 func (b *Werewolf) waitNameFromHealer() (name string) { 303 for { 304 select { 305 case name = <-b.healerCh: 306 case <-time.After(15 * time.Second): 307 b.Narrate("Waiting reply from healer", nil, nil) 308 continue 309 case <-b.ctx.Done(): 310 return 311 } 312 if b.isValidPlayerName(name) { 313 break 314 } 315 b.Narrate(ErrInvalidPlayerName.Error(), b.healerID, nil) 316 } 317 return name 318 } 319 320 // Return either a name is a valid alive player name or not 321 func (b *Werewolf) isValidPlayerName(name string) bool { 322 name = strings.TrimSpace(name) 323 for _, player := range b.playersAlive { 324 if string(player.Username) == name { 325 return true 326 } 327 } 328 return false 329 } 330 331 // Narrate register a chat message on behalf of the narrator user 332 func (b *Werewolf) Narrate(msg string, toUserID *database.UserID, groupID *database.GroupID) { 333 html, _, _ := dutils.ProcessRawMessage(b.db, msg, "", b.narratorID, b.roomID, nil, false, true, false) 334 b.NarrateRaw(html, toUserID, groupID) 335 } 336 337 func (b *Werewolf) NarrateRaw(msg string, toUserID *database.UserID, groupID *database.GroupID) { 338 _, _ = b.db.CreateOrEditMessage(nil, msg, msg, "", b.roomID, b.narratorID, toUserID, nil, groupID, false, false, false) 339 } 340 341 // Display roles assigned at beginning of the Game 342 func (b *Werewolf) displayRoles() { 343 msg := "Roles were:\n" 344 for _, player := range b.players { 345 msg += player.Username.AtStr() + " : " + player.Role + "\n" 346 } 347 b.Narrate(msg, nil, nil) 348 } 349 350 func (b *Werewolf) StartGame(db *database.DkfDB) { 351 defer func() { 352 b.displayRoles() 353 b.reset() 354 }() 355 b.ctx, b.cancel = context.WithCancel(context.Background()) 356 // Assign roles 357 playersArr := make([]*Player, 0) 358 for _, player := range b.playersAlive { 359 playersArr = append(playersArr, player) 360 } 361 rand.Shuffle(len(playersArr), func(i, j int) { playersArr[i], playersArr[j] = playersArr[j], playersArr[i] }) 362 for idx, player := range playersArr { 363 if idx == 0 { 364 b.werewolfSet.Insert(player.UserID) 365 _, _ = db.AddUserToRoomGroup(b.roomID, b.werewolfGroupID, player.UserID) 366 player.Role = WerewolfRole 367 werewolfMsg := "During the day you seem to be a regular Townsperson.\n" + 368 "However, you’ve been kissed by the Night and transform into a Werewolf when the sun sets.\n" + 369 "Your new nature compels you to kill and eat a Townsperson every night." 370 b.Narrate(werewolfMsg, &player.UserID, nil) 371 } else if idx == 1 { 372 b.townspersonSet.Insert(player.UserID) 373 b.healerID = &player.UserID 374 player.Role = HealerRole 375 healerMsg := "You’re a Townsperson with the unique ability to save lives.\n" + 376 "During the night, you’ll get a chance to protect another Townsperson from death if they are attacked by the Werewolves.\n" + 377 "You can choose to protect yourself." 378 b.Narrate(healerMsg, &player.UserID, nil) 379 } else if idx == 2 { 380 b.townspersonSet.Insert(player.UserID) 381 b.seerID = &player.UserID 382 player.Role = SeerRole 383 seerMsg := "You’re a Townsperson with the unique ability to peer into a person’s soul and see their true nature.\n" + 384 "During the night, you’ll get a chance to see if another Townsperson is a Werewolf.\n" + 385 "However, use this information wisely because it can lead to you being targeted by the Werewolves the next night if they deduce your identity." 386 b.Narrate(seerMsg, &player.UserID, nil) 387 } else { 388 b.townspersonSet.Insert(player.UserID) 389 player.Role = TownspeopleRole 390 townspersonMsg := "You’re a regular member of the town.\n" + 391 "Perhaps you’re a baker, merchant, or soldier.\n" + 392 "Your job is to save the town by eliminating the Werewolves that have infiltrated your town and started feeding on your neighbors.\n" + 393 "Also, try to avoid getting killed yourself." 394 b.Narrate(townspersonMsg, &player.UserID, nil) 395 } 396 } 397 b.state = DayState 398 b.Narrate("players: "+b.alivePlayersStr(), nil, nil) 399 b.Narrate("Day 1: It is day time. players can now introduce themselves. (5min)", nil, nil) 400 401 select { 402 case <-time.After(5 * time.Minute): 403 case <-b.readyCh: 404 case <-b.ctx.Done(): 405 b.Narrate("STOP SIGNAL - Game is being stopped", nil, nil) 406 return 407 } 408 409 for { 410 b.state = NightState 411 b.Narrate("Townspeople, go to sleep", nil, nil) 412 playerNameToKill := b.processWerewolf() 413 b.processSeer() 414 playerNameToSave := b.processHealer() 415 416 b.state = DayState 417 b.Narrate("Townspeople, wake up", nil, nil) 418 if playerNameToKill == playerNameToSave { 419 b.Narrate("Someone was attacked last night, but they survived", nil, nil) 420 } else { 421 b.Narrate("Everyone wakes up to see a trail of blood leading to the forest.\n"+ 422 "There you find @"+playerNameToKill+"’s mangled remains by the Great Oak.\n"+ 423 "Curiously, there are deep claw marks in the bark of the surrounding trees.\n"+ 424 "It looks like @"+playerNameToKill+" put up a fight.", nil, nil) 425 b.kill(db, database.Username(playerNameToKill)) 426 } 427 428 b.Narrate("players still alive: "+b.alivePlayersStr(), nil, nil) 429 if b.werewolfSet.Empty() { 430 b.Narrate("Townspeople win", nil, nil) 431 break 432 } else if b.townspersonSet.Len() <= 1 { 433 b.Narrate("Werewolf win", nil, nil) 434 break 435 } 436 437 b.Narrate("Townspeople now have 5min to discuss the events", nil, nil) 438 439 select { 440 case <-time.After(5 * time.Minute): 441 case <-b.readyCh: 442 case <-b.ctx.Done(): 443 b.Narrate("STOP SIGNAL - Game is being stopped", nil, nil) 444 return 445 } 446 447 b.state = VoteState 448 b.voted = hashset.New[database.UserID]() 449 b.Narrate("It's now time to vote for execution. PM me the name you vote to execute or \"none\"", nil, nil) 450 killName := b.killVote() 451 if killName == "" { 452 b.Narrate("Townspeople do not want to execute anyone", nil, nil) 453 } else { 454 b.Narrate("Townspeople execute @"+killName, nil, nil) 455 b.kill(db, database.Username(killName)) 456 } 457 458 b.Narrate("players still alive: "+b.alivePlayersStr(), nil, nil) 459 460 if b.werewolfSet.Empty() { 461 b.Narrate("Townspeople win", nil, nil) 462 break 463 } else if b.townspersonSet.Len() == 1 { 464 b.Narrate("Werewolf win", nil, nil) 465 break 466 } 467 } 468 b.state = EndGameState 469 b.Narrate("Game ended", nil, nil) 470 } 471 472 // Return the names of alive players. ie: "user1, user2, user3" 473 func (b *Werewolf) alivePlayersStr() (out string) { 474 arr := make([]string, 0) 475 for _, player := range b.playersAlive { 476 arr = append(arr, player.Username.AtStr()) 477 } 478 sort.Slice(arr, func(i, j int) bool { return arr[i] < arr[j] }) 479 return strings.Join(arr, ", ") 480 } 481 482 // Kill a player 483 func (b *Werewolf) kill(db *database.DkfDB, playerName database.Username) { 484 player, found := b.playersAlive[playerName] 485 if !found { 486 return 487 } 488 delete(b.playersAlive, playerName) 489 switch player.Role { 490 case WerewolfRole: 491 b.werewolfSet.Remove(player.UserID) 492 _ = db.RmUserFromRoomGroup(b.roomID, b.werewolfGroupID, player.UserID) 493 case TownspeopleRole: 494 b.townspersonSet.Remove(player.UserID) 495 case HealerRole: 496 b.townspersonSet.Remove(player.UserID) 497 b.healerID = nil 498 case SeerRole: 499 b.townspersonSet.Remove(player.UserID) 500 b.seerID = nil 501 } 502 _, _ = db.AddUserToRoomGroup(b.roomID, b.deadGroupID, player.UserID) 503 } 504 505 // Return the name of the player name that receive the most vote 506 func (b *Werewolf) killVote() string { 507 508 // Send a PM to all players saying they have to vote for a name 509 for _, player := range b.playersAlive { 510 msg := "Who do you vote to kill? (name | none)" 511 msg += b.createKillVoteForm() 512 b.NarrateRaw(msg, &player.UserID, nil) 513 } 514 515 votes := b.waitVotes() 516 // Get the max voted name 517 maxName := "none" 518 maxCount := 0 519 voteMap := make(map[string]int) // keep track of how many votes for each values 520 for _, vote := range votes { 521 tmp := voteMap[vote] 522 tmp++ 523 voteMap[vote] = tmp 524 if tmp > maxCount { 525 maxCount = tmp 526 maxName = vote 527 } 528 } 529 if maxName == "none" { 530 return "" 531 } 532 return maxName 533 } 534 535 func (b *Werewolf) getAlivePlayersArr(includeWerewolves bool) []database.Username { 536 arr := make([]database.Username, 0) 537 for _, player := range b.playersAlive { 538 if !includeWerewolves && b.werewolfSet.Contains(player.UserID) { 539 continue 540 } 541 arr = append(arr, player.Username) 542 } 543 sort.Slice(arr, func(i, j int) bool { return arr[i] < arr[j] }) 544 return arr 545 } 546 547 func (b *Werewolf) createPickUserForm() string { 548 arr := b.getAlivePlayersArr(true) 549 550 htmlTmpl := ` 551 <form method="post" action="/api/v1/werewolf"> 552 {{ range $idx, $p := .Arr }} 553 <input type="radio" ID="player{{ $idx }}" name="message" value="/pm 0 {{ $p }}" /><label for="player{{ $idx }}">{{ $p }}</label><br /> 554 {{ end }} 555 <button type="submit" name="btn_submit">ok</button> 556 </form>` 557 data := map[string]any{ 558 "Arr": arr, 559 } 560 var buf bytes.Buffer 561 _ = utils.Must(template.New("").Parse(htmlTmpl)).Execute(&buf, data) 562 return buf.String() 563 } 564 565 func (b *Werewolf) createKillVoteForm() string { 566 arr := b.getAlivePlayersArr(true) 567 568 htmlTmpl := ` 569 <form method="post" action="/api/v1/werewolf"> 570 {{ range $idx, $p := .Arr }} 571 <input type="radio" ID="player{{ $idx }}" name="message" value="/pm 0 {{ $p }}" /><label for="player{{ $idx }}">{{ $p }}</label><br /> 572 {{ end }} 573 <input type="radio" ID="none" name="message" value="/pm 0 none" /><label for="none">none</label><br /> 574 <button type="submit" name="btn_submit">ok</button> 575 </form>` 576 data := map[string]any{ 577 "Arr": arr, 578 } 579 var buf bytes.Buffer 580 _ = utils.Must(template.New("").Parse(htmlTmpl)).Execute(&buf, data) 581 return buf.String() 582 } 583 584 func (b *Werewolf) createWerewolfPickUserForm() string { 585 arr := b.getAlivePlayersArr(false) 586 587 htmlTmpl := ` 588 <form method="post" action="/api/v1/werewolf"> 589 {{ range $idx, $p := .Arr }} 590 <input type="radio" ID="player{{ $idx }}" name="message" value="/g werewolf {{ $p }}" /><label for="player{{ $idx }}">{{ $p }}</label><br /> 591 {{ end }} 592 <button type="submit" name="btn_submit">ok</button> 593 </form>` 594 data := map[string]any{ 595 "Arr": arr, 596 } 597 var buf bytes.Buffer 598 _ = utils.Must(template.New("").Parse(htmlTmpl)).Execute(&buf, data) 599 return buf.String() 600 } 601 602 func (b *Werewolf) processWerewolf() string { 603 b.UnlockGroup("werewolf") 604 msg := "Werewolf, who do you want to kill?" 605 msg += b.createWerewolfPickUserForm() 606 b.NarrateRaw(msg, nil, &b.werewolfGroupID) 607 name := b.waitNameFromWerewolf() 608 b.Narrate(name+" will be killed", nil, &b.werewolfGroupID) 609 b.LockGroup("werewolf") 610 return name 611 } 612 613 func (b *Werewolf) processSeer() { 614 if b.seerID == nil { 615 return 616 } 617 msg := "Seer, who do you want to identify?" 618 msg += b.createPickUserForm() 619 b.NarrateRaw(msg, b.seerID, nil) 620 name := b.waitNameFromSeer() 621 player := b.playersAlive[database.Username(name)] 622 b.Narrate(name+" is a "+player.Role, b.seerID, nil) 623 } 624 625 func (b *Werewolf) processHealer() string { 626 if b.healerID == nil { 627 return "" 628 } 629 msg := "Healer, who do you want to save?" 630 msg += b.createPickUserForm() 631 b.NarrateRaw(msg, b.healerID, nil) 632 name := b.waitNameFromHealer() 633 b.Narrate(name+" will survive the night", b.healerID, nil) 634 return name 635 } 636 637 func (b *Werewolf) LockGroups() { 638 b.LockGroup("werewolf") 639 } 640 641 func (b *Werewolf) LockGroup(groupName string) { 642 group, _ := b.db.GetRoomGroupByName(b.roomID, groupName) 643 group.Locked = true 644 group.DoSave(b.db) 645 } 646 647 func (b *Werewolf) UnlockGroup(groupName string) { 648 group, _ := b.db.GetRoomGroupByName(b.roomID, groupName) 649 group.Locked = false 650 group.DoSave(b.db) 651 } 652 653 type Player struct { 654 UserID database.UserID 655 Username database.Username 656 Role string 657 } 658 659 func (b *Werewolf) reset() { 660 b.ctx, b.cancel = context.WithCancel(context.Background()) 661 b.state = PreGameState 662 b.players = make(map[database.Username]*Player) 663 b.playersAlive = make(map[database.Username]*Player) 664 b.werewolfSet = hashset.New[database.UserID]() 665 b.spectatorSet = hashset.New[database.UserID]() 666 b.townspersonSet = hashset.New[database.UserID]() 667 b.voted = hashset.New[database.UserID]() 668 b.werewolfCh = make(chan string) 669 b.seerCh = make(chan string) 670 b.healerCh = make(chan string) 671 b.votesCh = make(chan string) 672 b.readyCh = make(chan bool) 673 _ = b.db.ClearRoomGroup(b.roomID, b.werewolfGroupID) 674 _ = b.db.ClearRoomGroup(b.roomID, b.spectatorGroupID) 675 _ = b.db.ClearRoomGroup(b.roomID, b.deadGroupID) 676 } 677 678 func NewWerewolf(db *database.DkfDB) *Werewolf { 679 // Prepare room 680 room, err := db.GetChatRoomByName("werewolf") 681 if err != nil { 682 logrus.Error("#werewolf room not found") 683 return nil 684 } 685 zeroUser := dutils.GetZeroUser(db) 686 _ = db.DeleteChatRoomGroups(room.ID) 687 werewolfGroup, _ := db.CreateChatRoomGroup(room.ID, "werewolf", "#ffffff") 688 werewolfGroup.Locked = true 689 werewolfGroup.DoSave(db) 690 spectatorGroup, _ := db.CreateChatRoomGroup(room.ID, "spectator", "#ffffff") 691 deadGroup, _ := db.CreateChatRoomGroup(room.ID, "dead", "#ffffff") 692 693 b := new(Werewolf) 694 b.db = db 695 b.werewolfGroupID = werewolfGroup.ID 696 b.spectatorGroupID = spectatorGroup.ID 697 b.deadGroupID = deadGroup.ID 698 b.narratorID = zeroUser.ID 699 b.roomID = room.ID 700 b.reset() 701 return b 702 }