text: // Copyright 2019 DeepMind Technologies Limited
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //      http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  #include "open_spiel/games/mancala.h"
  
  #include <algorithm>
  #include <memory>
  #include <utility>
  #include <vector>
  
  #include "open_spiel/spiel_utils.h"
  #include "open_spiel/utils/tensor_view.h"
  
  namespace open_spiel {
  namespace mancala {
  namespace {
  
  // Facts about the game.
  const GameType kGameType{
      /*short_name=*/"mancala",
      /*long_name=*/"Mancala",
      GameType::Dynamics::kSequential,
      GameType::ChanceMode::kDeterministic,
      GameType::Information::kPerfectInformation,
      GameType::Utility::kZeroSum,
      GameType::RewardModel::kTerminal,
      /*max_num_players=*/2,
      /*min_num_players=*/2,
      /*provides_information_state_string=*/false,
      /*provides_information_state_tensor=*/false,
      /*provides_observation_string=*/true,
      /*provides_observation_tensor=*/true,
      /*parameter_specification=*/{}  // no parameters
  };
  
  std::shared_ptr<const Game> Factory(const GameParameters& params) {
    return std::shared_ptr<const Game>(new MancalaGame(params));
  }
  
  REGISTER_SPIEL_GAME(kGameType, Factory);
  
  int GetPlayerHomePit(Player player) {
    if (player == 0) {
      return kTotalPits / 2;
    }
    return 0;
  }
  
  bool IsPlayerPit(Player player, int pit) {
    if (player == 0) {
      if (pit < kTotalPits / 2 && pit > 0) return true;
      return false;
    }
    if (pit > kTotalPits / 2) return true;
    return false;
  }
  
  int GetOppositePit(int pit) { return kTotalPits - pit; }
  
  int GetNextPit(Player player, int pit) {
    int next_pit = (pit + 1) % kTotalPits;
    if (next_pit == GetPlayerHomePit(1 - player)) next_pit++;
    return next_pit;
  }
  }  // namespace
  
  void MancalaState::DoApplyAction(Action move) {
    SPIEL_CHECK_GT(board_[move], 0);
    int num_beans = board_[move];
    board_[move] = 0;
    int current_pit = move;
    for (int i = 0; i < num_beans; ++i) {
      current_pit = GetNextPit(current_player_, current_pit);
      board_[current_pit]++;
    }
  
    // capturing logic
    if (board_[current_pit] == 1 && IsPlayerPit(current_player_, current_pit) &&
        board_[GetOppositePit(current_pit)] > 0) {
      board_[GetPlayerHomePit(current_player_)] +=
          (1 + board_[GetOppositePit(current_pit)]);
      board_[current_pit] = 0;
      board_[GetOppositePit(current_pit)] = 0;
    }
  
    if (current_pit != GetPlayerHomePit(current_player_))
      current_player_ = 1 - current_player_;
  }
  
  std::vector<Action> MancalaState::LegalActions() const {
    if (IsTerminal()) return {};
    std::vector<Action> moves;
    if (current_player_ == 0) {
      for (int i = 0; i < kNumPits; ++i) {
        if (board_[i + 1] > 0) {
          moves.push_back(i + 1);
        }
      }
    } else {
      for (int i = 0; i < kNumPits; ++i) {
        if (board_[board_.size() - 1 - i] > 0) {
          moves.push_back(board_.size() - 1 - i);
        }
      }
    }
    std::sort(moves.begin(), moves.end());
    return moves;
  }
  
  std::string MancalaState::ActionToString(Player player,
                                           Action action_id) const {
    return absl::StrCat(action_id);
  }
  
  void MancalaState::InitBoard() {
    std::fill(begin(board_), end(board_), 4);
    board_[0] = 0;
    board_[board_.size() / 2] = 0;
  }
  
  void MancalaState::SetBoard(const std::array<int, (kNumPits + 1) * 2>& board) {
    board_ = board;
  }
  
  MancalaState::MancalaState(std::shared_ptr<const Game> game) : State(game) {
    InitBoard();
  }
  
  std::string MancalaState::ToString() const {
    std::string str;
    std::string separator = "-";
    absl::StrAppend(&str, separator);
    for (int i = 0; i < kNumPits; ++i) {
      absl::StrAppend(&str, board_[board_.size() - 1 - i]);
      absl::StrAppend(&str, separator);
    }
    absl::StrAppend(&str, "\n");
  
    absl::StrAppend(&str, board_[0]);
    for (int i = 0; i < kNumPits * 2 - 1; ++i) {
      absl::StrAppend(&str, separator);
    }
    absl::StrAppend(&str, board_[board_.size() / 2]);
    absl::StrAppend(&str, "\n");
  
    absl::StrAppend(&str, separator);
    for (int i = 0; i < kNumPits; ++i) {
      absl::StrAppend(&str, board_[i + 1]);
      absl::StrAppend(&str, separator);
    }
    return str;
  }
  
  bool MancalaState::IsTerminal() const {
    if (move_number_ > game_->MaxGameLength()) {
      return true;
    }
  
    bool player_0_has_moves = false;
    bool player_1_has_moves = false;
    for (int i = 0; i < kNumPits; ++i) {
      if (board_[board_.size() - 1 - i] > 0) {
        player_1_has_moves = true;
        break;
      }
    }
    for (int i = 0; i < kNumPits; ++i) {
      if (board_[i + 1] > 0) {
        player_0_has_moves = true;
        break;
      }
    }
    return !player_0_has_moves || !player_1_has_moves;
  }
  
  std::vector<double> MancalaState::Returns() const {
    if (IsTerminal()) {
      int player_0_bean_sum = std::accumulate(
          board_.begin() + 1, board_.begin() + kTotalPits / 2 + 1, 0);
      int player_1_bean_sum =
          std::accumulate(board_.begin() + kTotalPits / 2 + 1, board_.end(), 0) +
          board_[0];
      if (player_0_bean_sum > player_1_bean_sum) {
        return {1.0, -1.0};
      } else if (player_0_bean_sum < player_1_bean_sum) {
        return {-1.0, 1.0};
      } else {
        // Reaches max game length or they have the same bean sum, it is a draw.
        return {0.0, 0.0};
      }
    }
    return {0.0, 0.0};
  }
  
  std::string MancalaState::ObservationString(Player player) const {
    SPIEL_CHECK_GE(player, 0);
    SPIEL_CHECK_LT(player, num_players_);
    return ToString();
  }
  
  void MancalaState::ObservationTensor(Player player,
                                       absl::Span<float> values) const {
    SPIEL_CHECK_GE(player, 0);
    SPIEL_CHECK_LT(player, num_players_);
  
    SPIEL_CHECK_EQ(values.size(), kTotalPits);
    auto value_it = values.begin();
    for (int count : board_) {
      *value_it++ = count;
    }
    SPIEL_CHECK_EQ(value_it, values.end());
  }
  
  std::unique_ptr<State> MancalaState::Clone() const {
    return std::unique_ptr<State>(new MancalaState(*this));
  }
  
  MancalaGame::MancalaGame(const GameParameters& params)
      : Game(kGameType, params) {}
  
  }  // namespace mancala
  }  // namespace open_spiel
  