docs: public Optional<Integer> start() {
          LOGGER.info("Starting match with: {}", Arrays.toString(players));
          try(ViewerWriter viewerWriter = createViewerWriter()) {
              listeners.forEach(simulatorListener -> simulatorListener.beforeGameStart(players, gameMap));
              Arrays.stream(players).forEach(p -> {
                  GameStartApiResponse startData = getStartData(p.getPlayerIndex());
                  p.getBot().onGameStarted(new GameStartedApiResponseImpl(startData));
              });
  
              for (; ; ) {
                  Map<Integer, PlayerStats> playersStats = calculatePlayersStats(gameMap.getTiles());
                  Arrays.stream(players)
                          .map(player -> sendGameUpdate(player, playersStats))
                          .map(gameMap::move)
                          .filter(Optional::isPresent)
                          .map(Optional::get)
                          .forEach(this::endPlayer);
                  gameMap.tick();
                  viewerWriter.write(gameMap);
                  listeners.forEach(simulatorListener -> simulatorListener.afterHalfTurn(gameMap.getHalfTurnCounter(), gameMap.getTiles()));
  
                  boolean reachedMaxTurns = gameMap.getHalfTurnCounter() > (configuration.getMaxTurns() * 2);
                  Player[] alive = Arrays.stream(this.players).filter(Player::isAlive).toArray(Player[]::new);
                  if (alive.length == 1) {
                      Player winner = alive[0];
                      sendGameUpdate(winner, calculatePlayersStats(gameMap.getTiles()));
                      return endGame(winner);
                  } else if (reachedMaxTurns) {
                      return disconnectPlayers(this.players);
                  }
              }
          }
      }