// Creates a new game, but does not start it. func newGame() *Game { id := generateId() util.LogInfo("Creating a new game with id: %s.", id) game := Game{} game.Id = id fields := make([][]*Field, conf.BoardSize) for i := range fields { fields[i] = make([]*Field, conf.BoardSize) } game.Board = &Board{Fields: fields} game.isStarted = false // init channels game.playerJoinedCh = make(chan int) board := game.Board for i, row := range board.Fields { for j, _ := range row { board.Fields[i][j] = new(Field) } } return &game }
// Adds a player to the registry. func (player *Player) Join(game *Game) error { util.LogInfo("New player (with name: %s) attempts to join the game.", player.Name) if game.hasAlreadyJoined(player) { return util.Errorf("Player with name %s has already joined the game.", player.Name) } err := game.Board.placeShips(game.Players, player) if err != nil { return err } game.addPlayer(player) util.LogInfo("Player with name %s (id: %s) has joined the game.", player.Name, player.Id) return nil }
// Places the appropriate number of ships on the board randomly. The number of // ships is based on the current average score for the players in the game. func (board *Board) placeShips(allPlayers []*Player, player *Player) error { scores := make([]float64, len(allPlayers)) for idx, p := range allPlayers { scores[idx] = p.getCurrentScore() } avg := average(scores) util.LogInfo("average board score is %f", avg) if len(allPlayers) == 0 { avg = BASE_SCORE } deployment := computeShipDeployment(avg) util.LogInfo("Deployment for %s player: %v", player.Name, deployment) return board.deployShips(player, deployment) }
// Starts a new turn for a player. Block while the player's turn ends. func (g *Game) doTurn(player *Player) { util.LogInfo("%s started her turn.", player.Name) // set current player g.CurrentPlayerId = player.Id // when the player is a bot, just shoot and end the turn if player.IsBot { if g.isAllBot() && conf.WaitForBots { time.Sleep(time.Second * time.Duration(conf.BotTurnDurationSec)) } g.shootForAI() } else { // human player have time to react g.endTurn = make(chan int) go g.measureTurnTime() <-g.endTurn } util.LogInfo("%s finished her turn.", player.Name) }
// main loop for the program, starts new game when needed func mainLoop() { endGame := make(chan int) for { currentGame = engine.NewGame(endGame) currentGame.Start() <-endGame History = append(History, currentGame) util.LogInfo("Game ended with id: %s.", currentGame.Id) } }
func (g *Game) shootForAI() { row, col := rand.Intn(conf.BoardSize), rand.Intn(conf.BoardSize) result, _ := g.Board.shootAt(row, col, nil) for result == INVALID { row, col = rand.Intn(conf.BoardSize), rand.Intn(conf.BoardSize) result, _ = g.Board.shootAt(row, col, nil) } g.notifyViewsAfterShot(row, col, result) util.LogInfo("Bot player shot at %s and the result is: %s", RowColToS(row, col), result) }
func (g *Game) step() { // wait for enough players to start for { numberOfPlayers := <-g.playerJoinedCh if numberOfPlayers > conf.MinimalPlayerCnt-1 { g.isStarted = true break } } // notify views that the game started for _, reporter := range g.views { reporter.ReportGameStarted() } // we have enough players so start the first player's turn g.reportTurnStart(g.Players[0], g.Players[1]) g.doTurn(g.Players[0]) // game event loop for { // check if we have a winner // when we do, break the game loop if result, winner := g.hasWinner(); result { g.Winner = winner util.LogInfo("The winner is: %s", winner.Name) break } prevPlayerId := g.CurrentPlayerId player, err := g.getNextPlayer(prevPlayerId) if err != nil { util.LogError("No player found for id %v", prevPlayerId) continue } // report new turn for player nextPlayer, _ := g.getNextPlayer(player.Id) g.reportTurnStart(player, nextPlayer) g.doTurn(player) } // notify views for _, reporter := range g.views { reporter.ReportGameOver(g.Winner) } g.endCh <- 1 // signal the main loop g.cleanUp() }
// shoot handler func shootHandler(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie(PLAYER_ID_COOKIE) // the play might not be registered if err != nil { // TODO: error handling fmt.Fprint(w, "Join the game first!") return } var shootResult ShootResult shootResult.Game = currentGame shootResult.Player = currentGame.GetPlayerById(cookie.Value) if shootResult.Player == nil { fmt.Fprint(w, "No player registered with the given id. Go to home page and register.") return } if r.Method == "POST" { // check if the player is up if currentGame.CurrentPlayerId != cookie.Value { // -1 : it's not your turn fmt.Fprint(w, "-1") return } colS := r.FormValue("col") rowS := r.FormValue("row") row, err := strconv.Atoi(rowS) if renderParseError(w, err) { return } // 0 based indexing row -= 1 if len(colS) == 0 && renderParseError(w, errors.New("Column should be [A-Z]")) { return } col := int(colS[0] - 'A') shootResult.Feedback = currentGame.Shoot(row, col) util.LogInfo("Player shot at (%s) with result: %s", engine.RowColToS(row, col), shootResult.Feedback) fmt.Fprint(w, shootResult.Feedback) return } renderTemplate(w, SHOOT_TEMPLATE, shootResult) }
// Makes the player quit the game, sets the player to be a bot for the rest of // the game. There is no re-entry option. func quitHandler(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie(PLAYER_ID_COOKIE) if err != nil { http.Redirect(w, r, "/", http.StatusFound) return } for _, player := range currentGame.Players { // if player exists, set it to be a bot if player.Id == cookie.Value { player.IsBot = true break } } // delete the cookie cookie.MaxAge = -1 http.SetCookie(w, cookie) http.Redirect(w, r, "/", http.StatusFound) util.LogInfo("Player with %s id left the game.", cookie.Value) }