/* * Copyright (c) 2015 Shervin Asgari * 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. */ package no.asgari.civilization.server.action; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Sets; import com.mongodb.DB; import lombok.SneakyThrows; import lombok.extern.log4j.Log4j; import no.asgari.civilization.server.application.CivSingleton; import no.asgari.civilization.server.dto.ChatDTO; import no.asgari.civilization.server.dto.CivHighscoreDTO; import no.asgari.civilization.server.dto.CreateNewGameDTO; import no.asgari.civilization.server.dto.DrawDTO; import no.asgari.civilization.server.dto.GameDTO; import no.asgari.civilization.server.dto.GameLogDTO; import no.asgari.civilization.server.dto.MessageDTO; import no.asgari.civilization.server.dto.PbfDTO; import no.asgari.civilization.server.dto.PlayerDTO; import no.asgari.civilization.server.dto.WinnerDTO; import no.asgari.civilization.server.email.SendEmail; import no.asgari.civilization.server.excel.ItemReader; import no.asgari.civilization.server.misc.CivUtil; import no.asgari.civilization.server.misc.SecurityCheck; import no.asgari.civilization.server.model.Chat; import no.asgari.civilization.server.model.GameLog; import no.asgari.civilization.server.model.GameType; import no.asgari.civilization.server.model.Item; import no.asgari.civilization.server.model.PBF; import no.asgari.civilization.server.model.Player; import no.asgari.civilization.server.model.Playerhand; import org.apache.commons.lang3.StringUtils; import org.mongojack.DBQuery; import org.mongojack.DBSort; import org.mongojack.JacksonDBCollection; import org.mongojack.WriteResult; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import java.io.IOException; import java.net.URLDecoder; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Random; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.stream.Collectors.toList; @Log4j public class GameAction extends BaseAction { private final JacksonDBCollection<PBF, String> pbfCollection; private final JacksonDBCollection<Player, String> playerCollection; private final GameLogAction gameLogAction; private final JacksonDBCollection<Chat, String> chatCollection; private final DrawAction drawAction; public GameAction(DB db) { super(db); this.playerCollection = JacksonDBCollection.wrap(db.getCollection(Player.COL_NAME), Player.class, String.class); this.pbfCollection = JacksonDBCollection.wrap(db.getCollection(PBF.COL_NAME), PBF.class, String.class); this.chatCollection = JacksonDBCollection.wrap(db.getCollection(Chat.COL_NAME), Chat.class, String.class); this.gameLogAction = new GameLogAction(db); this.drawAction = new DrawAction(db); } public String createNewGame(CreateNewGameDTO dto, String playerId) { PBF pbf = new PBF(); pbf.setName(dto.getName()); pbf.setType(dto.getType()); pbf.setNumOfPlayers(dto.getNumOfPlayers()); ItemReader itemReader = new ItemReader(); readItemFromExcel(dto.getType(), itemReader); pbf.getItems().addAll(itemReader.shuffledCivs); pbf.getItems().addAll(itemReader.shuffledCultureI); pbf.getItems().addAll(itemReader.shuffledCultureII); pbf.getItems().addAll(itemReader.shuffledCultureIII); pbf.getItems().addAll(itemReader.shuffledGPs); pbf.getItems().addAll(itemReader.shuffledHuts); pbf.getItems().addAll(itemReader.shuffledVillages); pbf.getItems().addAll(itemReader.shuffledTiles); pbf.getItems().addAll(itemReader.shuffledCityStates); pbf.getItems().addAll(itemReader.ancientWonders); pbf.getItems().addAll(itemReader.medievalWonders); pbf.getItems().addAll(itemReader.modernWonders); pbf.getItems().addAll(itemReader.mountedList); pbf.getItems().addAll(itemReader.aircraftList); pbf.getItems().addAll(itemReader.artilleryList); pbf.getItems().addAll(itemReader.infantryList); pbf.getTechs().addAll(itemReader.allTechs); pbf.getSocialPolicies().addAll(itemReader.socialPolicies); Collections.shuffle(pbf.getItems(), new Random(System.nanoTime())); Collections.shuffle(pbf.getTechs(), new Random(System.nanoTime())); Collections.shuffle(pbf.getSocialPolicies(), new Random(System.nanoTime())); pbf.getItems().forEach(it -> it.setItemNumber(ItemReader.itemCounter.incrementAndGet())); pbf.getTechs().forEach(it -> it.setItemNumber(ItemReader.itemCounter.incrementAndGet())); pbf.getSocialPolicies().forEach(it -> it.setItemNumber(ItemReader.itemCounter.incrementAndGet())); WriteResult<PBF, String> pbfInsert = pbfCollection.insert(pbf); pbf.setId(pbfInsert.getSavedId()); log.info("PBF game created with id " + pbfInsert.getSavedId()); joinGame(pbf, playerId, Optional.of(dto.getColor()), true); //Do this in a new thread Thread thread = new Thread(() -> { playerCollection.find().toArray().stream() .filter(p -> !p.isDisableEmail()) .filter(CivUtil::shouldSendEmail) .forEach(p -> { SendEmail.sendMessage(p.getEmail(), "New Civilization game created", "A new game by the name " + pbf.getName() + " was just created! Visit " + SendEmail.URL + " to join the game.", p.getId()); playerCollection.updateById(p.getId(), p); }); }); thread.start(); return pbf.getId(); } private void readItemFromExcel(GameType gameType, ItemReader itemReader) { try { itemReader.readItemsFromExcel(gameType); if (!CivSingleton.instance().itemsCache().containsKey(gameType)) { CivSingleton.instance().itemsCache().put(gameType, itemReader); } } catch (IOException e) { log.error("Couldn't read Excel document " + e.getMessage(), e); } } /** * Returns all games sorted on active first * * @return */ public List<PbfDTO> getAllGames() { List<PBF> pbfs = pbfCollection.find().toArray(); return pbfs.stream() .map(GameAction::createPbfDTO) .sorted((o1, o2) -> { int v = Boolean.valueOf(o1.isActive()).compareTo(o2.isActive()); if (v != 0) return v; return Long.valueOf(o1.getCreated()).compareTo(o2.getCreated()); }) .collect(toList()); } /** * Creating PbfDTO so to not include every players Ghand and information * * @param pbf - the PBF * @return PbfDto */ private static PbfDTO createPbfDTO(PBF pbf) { PbfDTO dto = new PbfDTO(); dto.setType(pbf.getType()); dto.setId(pbf.getId()); dto.setName(pbf.getName()); dto.setActive(pbf.isActive()); long created = pbf.getCreated().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); dto.setCreated(created); dto.setNumOfPlayers(pbf.getNumOfPlayers()); dto.setPlayers(pbf.getPlayers().stream() .map(p -> createPlayerDTO(p, pbf.getId())) .collect(toList())); dto.setNameOfUsersTurn(pbf.getNameOfUsersTurn()); return dto; } private static PlayerDTO createPlayerDTO(Playerhand player, String pbfId) { PlayerDTO dto = new PlayerDTO(); dto.setUsername(player.getUsername()); dto.setPlayerId(player.getPlayerId()); dto.setPbfId(pbfId); return dto; } private static Playerhand createPlayerHand(Player player, String color, boolean gameCreator) { Playerhand playerhand = new Playerhand(); playerhand.setUsername(player.getUsername()); playerhand.setPlayerId(player.getId()); playerhand.setYourTurn(false); playerhand.setColor(color); playerhand.setGameCreator(gameCreator); playerhand.setEmail(player.getEmail()); return playerhand; } public void joinGame(String pbfId, Player player, Optional<String> colorOpt) { PBF pbf = pbfCollection.findOneById(pbfId); joinGame(pbf, player.getId(), colorOpt, false); Thread thread = new Thread(() -> { pbf.getPlayers().stream() .filter(p -> !p.getPlayerId().equals(player.getId())) .forEach(p -> SendEmail.sendMessage(p.getEmail(), "Game update", player.getUsername() + " joined " + pbf.getName() + ". Go to " + SendEmail.URL + " to find out who!", p.getPlayerId())); }); thread.start(); } /** * Joins a game. If it is full it will throw exception */ private void joinGame(PBF pbf, String playerId, Optional<String> colorOpt, boolean gameCreator) { if (pbf.getNumOfPlayers() == pbf.getPlayers().size()) { log.warn("Cannot join the game. Its full"); Response badReq = Response.status(Response.Status.BAD_REQUEST) .entity(new MessageDTO("Cannot join the game. Its full!")) .build(); throw new WebApplicationException(badReq); } Player player = playerCollection.findOneById(playerId); boolean playerAlreadyJoined = pbf.getPlayers().stream() .anyMatch(p -> p.getPlayerId().equals(player.getId())); if (playerAlreadyJoined) { log.warn("Cannot join the game. Player has already joined it"); Response badReq = Response.status(Response.Status.BAD_REQUEST) .entity(new MessageDTO("Cannot join the game. You have already joined!")) .build(); throw new WebApplicationException(badReq); } player.getGameIds().add(pbf.getId()); playerCollection.updateById(player.getId(), player); Playerhand playerhand; if (!pbf.getWithdrawnPlayers().isEmpty()) { playerhand = pbf.getWithdrawnPlayers().remove(0); boolean updated = gameLogAction.updateGameLog(pbf.getId(), playerhand.getUsername(), player.getUsername()); log.info("Managed to update gameLog: " + updated); playerhand.setEmail(player.getEmail()); playerhand.setPlayerId(player.getId()); playerhand.setUsername(player.getUsername()); } else { String color = colorOpt.orElse(chooseColorForPlayer(pbf)); playerhand = createPlayerHand(player, color, gameCreator); } if (!pbf.getPlayers().contains(playerhand)) { createInfoLog(pbf.getId(), playerhand.getUsername() + " joined the game and is playing color " + playerhand.getColor()); pbf.getPlayers().add(playerhand); } pbf = startIfAllPlayers(pbf); pbfCollection.updateById(pbf.getId(), pbf); } private String chooseColorForPlayer(PBF pbf) { if (pbf.getPlayers().isEmpty()) { return Playerhand.green(); } Set<String> allColors = Sets.newHashSet(Playerhand.green(), Playerhand.purple(), Playerhand.blue(), Playerhand.yellow(), Playerhand.red()); Set<String> colors = pbf.getPlayers().stream() .map(Playerhand::getColor) .collect(Collectors.toSet()); Sets.SetView<String> difference = Sets.difference(allColors, colors); return difference.iterator().next(); } public List<PlayerDTO> getAllPlayers(String pbfId) { Preconditions.checkNotNull(pbfId); PBF pbf = pbfCollection.findOneById(pbfId); return pbf.getPlayers().stream() .map(p -> createPlayerDTO(p, pbf.getId())) .sorted((o1, o2) -> o1.getUsername().compareTo(o2.getUsername())) .collect(toList()); } private PBF startIfAllPlayers(PBF pbf) { if (pbf.getPlayers().stream().anyMatch(Playerhand::isYourTurn)) { return pbf; } final int numOfPlayersNeeded = pbf.getNumOfPlayers(); if (numOfPlayersNeeded == pbf.getPlayers().size()) { Collections.shuffle(pbf.getPlayers()); Playerhand firstplayer = pbf.getPlayers().get(0); firstplayer.setYourTurn(true); firstplayer.setPlayernumber(1); createInfoLog(pbf.getId(), "Game has now started. Good luck, and have fun!"); StringBuilder sb = new StringBuilder(); for (int i = 0; i < pbf.getPlayers().size(); i++) { Playerhand player = pbf.getPlayers().get(i); player.setPlayernumber(i + 1); sb.append(getNameForPlayerNumber(i) + " player is " + player.getUsername() + ". "); } createInfoLog(pbf.getId(), sb.toString()); } return pbf; } public GameDTO mapGameDTO(PBF pbf, Player player) { Preconditions.checkNotNull(pbf); //Set common stuff GameDTO dto = new GameDTO(); long created = pbf.getCreated().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); dto.setCreated(created); dto.setId(pbf.getId()); dto.setType(pbf.getType()); dto.setName(pbf.getName()); dto.setWhosTurnIsIt(pbf.getNameOfUsersTurn()); dto.setActive(pbf.isActive()); dto.setMapLink(pbf.getMapLink()); dto.setAssetLink(pbf.getAssetLink()); //Set logs List<GameLog> allPublicLogs = gameLogAction.getGameLogs(pbf.getId()); List<GameLogDTO> publicGamelogDTOs = allPublicLogs.stream() .filter(log -> !Strings.isNullOrEmpty(log.getPublicLog())) .map(log -> new GameLogDTO(log.getId(), log.getPublicLog(), log.getCreatedInMillis(), new DrawDTO(log.getDraw()))) .collect(toList()); dto.setPublicLogs(publicGamelogDTOs); //Set private player info if correct player is loggedIn. if (player != null && !Strings.isNullOrEmpty(player.getUsername()) && !Strings.isNullOrEmpty(player.getId())) { //Get all the private player stuff Optional<Playerhand> playerhand = pbf.getPlayers() .stream() .filter(p -> p.getPlayerId().equals(player.getId())) .findFirst(); if (playerhand.isPresent()) { List<GameLog> allPrivateLogs = gameLogAction.getGameLogsBelongingToPlayer(pbf.getId(), playerhand.get().getUsername()); List<GameLogDTO> privateGamelogDTOs = allPrivateLogs.stream() .filter(log -> !Strings.isNullOrEmpty(log.getPrivateLog())) .map(log -> new GameLogDTO(log.getId(), log.getPrivateLog(), log.getCreatedInMillis(), new DrawDTO(log.getDraw()))) .collect(toList()); dto.setPlayer(playerhand.get()); dto.setPrivateLogs(privateGamelogDTOs); } } dto.setRevealedItems(getAllRevealedItems(pbf)); return dto; } public boolean withdrawFromGame(String pbfId, String playerId) { PBF pbf = pbfCollection.findOneById(pbfId); if (!SecurityCheck.hasUserAccess(pbf, playerId)) { log.warn("User with id " + playerId + " is not player of this game, and cannot withdraw"); Response badReq = Response.status(Response.Status.FORBIDDEN) .entity(new MessageDTO("User is not player of this game, and cannot withdraw")) .build(); throw new WebApplicationException(badReq); } Iterator<Playerhand> iterator = pbf.getPlayers().iterator(); while (iterator.hasNext()) { Playerhand playerhand = iterator.next(); if (playerhand.getPlayerId().equals(playerId)) { if (playerhand.isGameCreator()) { Optional<Playerhand> optionalNextPlayer = getRandomPlayerExcept(pbf.getPlayers(), playerhand.getUsername()); if (optionalNextPlayer.isPresent()) { Playerhand nextPlayer = optionalNextPlayer.get(); nextPlayer.setGameCreator(true); gameLogAction.createCommonPrivatePublicLog("Is now game creator", pbfId, nextPlayer.getPlayerId()); } else { Response badReq = Response.status(Response.Status.FORBIDDEN) .entity(new MessageDTO("As game creator, you must end game not withdraw from it")) .build(); throw new WebApplicationException(badReq); } } pbf.getWithdrawnPlayers().add(playerhand); iterator.remove(); gameLogAction.createCommonPublicLog("withdrew from game", pbfId, playerId); //TODO remove from PlayerCollection also pbfCollection.updateById(pbf.getId(), pbf); return true; } } return false; } private Optional<Playerhand> getRandomPlayerExcept(List<Playerhand> players, String username) { return players.stream().filter(p -> !p.getUsername().equals(username)).findAny(); } @SneakyThrows public Chat chat(String pbfId, String message, String username) { Chat chat = new Chat(); chat.setPbfId(pbfId); chat.setMessage(URLDecoder.decode(message, "UTF-8")); chat.setUsername(username); String id = chatCollection.insert(chat).getSavedId(); chat.setId(id); if (pbfId != null) { PBF pbf = findPBFById(pbfId); if (StringUtils.isNotBlank(message)) { pbf.getPlayers() .stream() .filter(p -> !p.getUsername().equals(username)) .filter(CivUtil::shouldSendEmailInGame) .forEach(p -> { SendEmail.sendMessage(p.getEmail(), "New Chat", username + " wrote in the chat: " + chat.getMessage() + ".\nLogin to " + SendEmail.gamelink(pbfId) + " to see the chat", p.getPlayerId()); pbfCollection.updateById(pbfId, pbf); } ); } } return chat; } public List<ChatDTO> getChat(String pbfId) { Preconditions.checkNotNull(pbfId); List<Chat> chats = chatCollection.find(DBQuery.is("pbfId", pbfId)).sort(DBSort.desc("created")).toArray(); if (chats == null) { return new ArrayList<>(); } PBF pbf = findPBFById(pbfId); Map<String, String> colorMap = pbf.getPlayers().stream() .collect(Collectors.toMap(Playerhand::getUsername, (playerhand) -> { return (playerhand.getColor() != null) ? playerhand.getColor() : ""; })); List<ChatDTO> chatDTOs = new ArrayList<>(chats.size()); for (Chat c : chats) { chatDTOs.add(new ChatDTO(c.getId(), c.getPbfId(), c.getUsername(), c.getMessage(), colorMap.get(c.getUsername()), c.getCreatedInMillis())); } //Sort newest date first chatDTOs.sort((o1, o2) -> -Long.valueOf(o1.getCreated()).compareTo(o2.getCreated())); return chatDTOs; } public void endGame(String pbfId, Player player, String winner) { PBF pbf = pbfCollection.findOneById(pbfId); if (!"admin".equals(player.getUsername())) { Playerhand playerhand = getPlayerhandByPlayerId(player.getId(), pbf); //Only game creator can end game if (!playerhand.isGameCreator()) { Response response = Response.status(Response.Status.FORBIDDEN) .entity(new MessageDTO("Only game creator can end game")) .build(); throw new WebApplicationException(response); } } if (!Strings.isNullOrEmpty(winner)) { if (!pbf.getPlayers().stream() .anyMatch(p -> p.getUsername().equals(winner))) { throw new WebApplicationException(Response.Status.NOT_FOUND); } createInfoLog(pbfId, winner + " won the game! Congratulations!"); pbf.setWinner(winner); } pbf.setActive(false); createInfoLog(pbfId, player.getUsername() + " Ended this game"); createInfoLog(pbfId, "Thank you for playing! Please donate if you liked this game!"); pbfCollection.updateById(pbfId, pbf); Thread thread = new Thread(() -> { pbf.getPlayers().forEach(p -> SendEmail.sendMessage(p.getEmail(), "Game ended", pbf.getName() + " has ended. I hope you enjoyed playing.\n" + "If you like this game, please consider donating. You can find the link at the bottom of the site. It will help keep the lights on, and continue adding more features!" + "\n\nBest regards Shervin Asgari aka Cash", p.getPlayerId())); }); thread.start(); } /** * Will return the id from the google presentation */ @SneakyThrows public String addMapLink(String pbfId, String linkEncoded, String playerId) { String link = URLDecoder.decode(linkEncoded, "UTF-8"); if (link.matches("(?i)^https://docs\\.google\\.com/presentation/d/.*$")) { String removedPart = link.replace("https://docs.google.com/presentation/d/", ""); String id = removedPart.split("/")[0]; log.info("Id from google presentation is: " + id); PBF pbf = pbfCollection.findOneById(pbfId); pbf.setMapLink(id); pbfCollection.updateById(pbfId, pbf); Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); createInfoLog(pbfId, playerhand.getUsername() + " Added map link"); return id; } return null; } /** * Will return the id from the google presentation */ @SneakyThrows public String addAssetLink(String pbfId, String linkEncoded, String playerId) { String link = URLDecoder.decode(linkEncoded, "UTF-8"); if (link.matches("(?i)^https://docs\\.google\\.com/spreadsheets/d/.*$")) { String removedPart = link.replace("https://docs.google.com/spreadsheets/d/", ""); String id = removedPart.split("/")[0]; log.info("Id from google presentation is: " + id); PBF pbf = pbfCollection.findOneById(pbfId); pbf.setAssetLink(id); pbfCollection.updateById(pbfId, pbf); Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); createInfoLog(pbfId, playerhand.getUsername() + " Added asset link"); return id; } return null; } public void changeUserFromExistingGame(String gameid, String oldUsername, String newUsername) { Preconditions.checkNotNull(gameid); Preconditions.checkNotNull(oldUsername); Preconditions.checkNotNull(newUsername); PBF pbf = pbfCollection.findOneById(gameid); Player toPlayer = playerCollection.find(DBQuery.is("username", newUsername)).toArray(1).get(0); //Find all instance of ownerid, and replace with newUsername Playerhand playerhandToReplace = pbf.getPlayers().stream().filter(p -> p.getUsername().equals(oldUsername)).findFirst().orElseThrow(PlayerAction::cannotFindPlayer); playerhandToReplace.setUsername(newUsername); playerhandToReplace.setPlayerId(toPlayer.getId()); playerhandToReplace.setEmail(toPlayer.getEmail()); playerhandToReplace.getBarbarians().forEach(b -> b.setOwnerId(toPlayer.getId())); playerhandToReplace.getBattlehand().forEach(b -> b.setOwnerId(toPlayer.getId())); playerhandToReplace.getTechsChosen().forEach(b -> b.setOwnerId(toPlayer.getId())); playerhandToReplace.getItems().forEach(b -> b.setOwnerId(toPlayer.getId())); pbfCollection.updateById(pbf.getId(), pbf); createInfoLog(pbf.getId(), newUsername + " is now playing instead of " + oldUsername); SendEmail.sendMessage(playerhandToReplace.getEmail(), "You are now playing in " + pbf.getName(), "Please log in to http://playciv.com and start playing!", playerhandToReplace.getPlayerId()); } public boolean deleteGame(String gameid) { Preconditions.checkNotNull(gameid); final PBF pbf = findPBFById(gameid); WriteResult<PBF, String> writeResult = pbfCollection.removeById(gameid); log.warn("Managed to delete game: " + Strings.isNullOrEmpty(writeResult.getWriteResult().toString())); List<Player> playerList = playerCollection.find().toArray().stream() .filter(p -> p.getGameIds().contains(gameid)) .collect(toList()); playerList.forEach(player -> { log.info("Deleting game from " + player.getUsername() + "s collection also"); player.getGameIds().remove(gameid); SendEmail.sendMessage(player.getEmail(), "Game deleted", "Your game " + pbf.getName() + " was deleted by the admin. " + "If this was incorrect, please contact the admin.", player.getId()); playerCollection.save(player); }); return true; } public void sendMailToAll(String msg) { playerCollection.find().toArray() .stream() .filter(p -> !p.isDisableEmail()) .forEach(player -> { SendEmail.sendMessage(player.getEmail(), "Message from cash at playciv.com", "Hello " + player.getUsername() + "\n" + msg, player.getId()); }); } /** * Gets public chat which is 1 week old and maximum 50 entries, sorted on created */ public List<ChatDTO> getPublicChat() { return chatCollection.find(DBQuery.notExists("pbfId")).sort(DBSort.desc("created")).toArray() .stream() .filter(c -> c.getCreated().isAfter(LocalDateTime.now().minusWeeks(2))) .sorted((a, b) -> a.getCreated().compareTo(b.getCreated())) .map(c -> new ChatDTO(c.getUsername(), c.getMessage(), c.getCreatedInMillis())) .limit(50) .collect(toList()); } public List<CivHighscoreDTO> getCivHighscore() { if (!CivSingleton.instance().itemsCache().containsKey(GameType.WAW)) { readItemFromExcel(GameType.WAW, new ItemReader()); } ItemReader itemReader = CivSingleton.instance().itemsCache().get(GameType.WAW); if (itemReader == null) { return Collections.emptyList(); } List<PBF> pbfs = pbfCollection.find().toArray(); try { Map<String, Long> numberOfCivsWinning = pbfs.stream() .filter(pbf -> !Strings.isNullOrEmpty(pbf.getWinner())) .filter(pbf -> !pbf.isActive()) .filter(pbf -> { String playerWhoWon = pbf.getWinner(); return pbf.getPlayers().stream() .filter(p -> p.getUsername().equals(playerWhoWon)) .filter(p -> p.getCivilization() != null) .findFirst().isPresent(); }) .map(pbf -> { String playerWhoWon = pbf.getWinner(); Playerhand playerhand = pbf.getPlayers().stream() .filter(p -> p.getUsername().equals(playerWhoWon)) .filter(p -> p.getCivilization() != null) .findFirst() .get(); return playerhand.getCivilization().getName(); }) .collect(Collectors.groupingBy(e -> e, Collectors.counting())); Map<String, Long> numberOfCivAttempts = pbfs.stream() .filter(pbf -> !Strings.isNullOrEmpty(pbf.getWinner())) .filter(pbf -> !pbf.isActive()) .flatMap(pbf -> pbf.getPlayers().stream()) .filter(p -> p.getCivilization() != null) .map(p -> p.getCivilization().getName()) .collect(Collectors.groupingBy(e -> e, Collectors.counting())); return itemReader.shuffledCivs.stream() .map(civ -> new CivHighscoreDTO(civ.getName(), numberOfCivsWinning.get(civ.getName()), numberOfCivAttempts.get(civ.getName()))) .sorted() .collect(toList()); } catch (Exception ex) { ex.printStackTrace(); return Collections.emptyList(); } } public List<WinnerDTO> getWinners() { List<PBF> pbfs = pbfCollection.find().toArray(); final ListMultimap<String, String> multimap = ArrayListMultimap.create(); pbfs.stream() .filter(pbf -> !Strings.isNullOrEmpty(pbf.getWinner())) .forEach(pbf -> multimap.put(pbf.getWinner(), pbf.getId())); Map<String, Long> attemptsPerUsername = pbfs.stream() .filter(pbf -> !Strings.isNullOrEmpty(pbf.getWinner())) .filter(pbf -> !pbf.isActive()) .flatMap(pbf -> pbf.getPlayers().stream()) .map(Playerhand::getUsername) .collect(Collectors.groupingBy(e -> e, Collectors.counting())); List<Player> allPlayers = playerCollection.find().toArray(); List<WinnerDTO> filteredPlayers = allPlayers.stream() .filter(p -> !multimap.containsKey(p.getUsername()) && p.getUsername() != null) .map(p -> { long attempts = attemptsPerUsername.get(p.getUsername()) == null ? 0L : attemptsPerUsername.get(p.getUsername()); WinnerDTO winner = new WinnerDTO(p.getUsername(), 0, attempts); return winner; }) .collect(toList()); List<WinnerDTO> winners = multimap.keySet().stream() .map(user -> new WinnerDTO(user, multimap.get(user).size(), attemptsPerUsername.get(user))) .collect(toList()); winners.addAll(filteredPlayers); Collections.sort(winners); return winners; } private List<Item> getAllRevealedItems(PBF pbf) { //Had to have comparator inside sort, otherwise weird exception Stream<Item> discardedStream = pbf.getDiscardedItems().stream() .sorted((o1, o2) -> o1.getSheetName().compareTo(o2.getSheetName())); Stream<Item> playerStream = pbf.getPlayers().stream() .flatMap(p -> p.getItems().stream()) .filter(it -> !it.isHidden()) .sorted((o1, o2) -> o1.getSheetName().compareTo(o2.getSheetName())); Stream<Item> concatedStream = Stream.concat(discardedStream, playerStream); return concatedStream.collect(toList()); } private String getNameForPlayerNumber(int nr) { switch (nr) { case 0: return "First"; case 1: return "Second"; case 2: return "Third"; case 3: return "Fourth"; case 4: return "Fifth"; case 5: return "Sixth"; } return ""; } public boolean disableEmailForPlayer(String playerId) { Preconditions.checkNotNull(playerId); Player player = playerCollection.findOneById(playerId); if (player != null) { log.warn("Player " + player.getEmail() + " no longer wants email"); player.setDisableEmail(true); playerCollection.updateById(playerId, player); return true; } return false; } public boolean startEmailForPlayer(String playerId) { Preconditions.checkNotNull(playerId); Player player = playerCollection.findOneById(playerId); if (player != null) { log.warn("Player " + player.getEmail() + " no longer wants email"); player.setDisableEmail(false); playerCollection.updateById(playerId, player); return true; } return false; } }