Java tutorial
/* * 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.mongodb.DB; import lombok.SneakyThrows; import lombok.extern.log4j.Log4j; import no.asgari.civilization.server.SheetName; import no.asgari.civilization.server.application.CivSingleton; import no.asgari.civilization.server.dto.AllTechsDTO; import no.asgari.civilization.server.dto.ForgotpassDTO; import no.asgari.civilization.server.dto.ItemDTO; import no.asgari.civilization.server.dto.MessageDTO; import no.asgari.civilization.server.dto.TechDTO; import no.asgari.civilization.server.email.SendEmail; import no.asgari.civilization.server.exception.PlayerExistException; import no.asgari.civilization.server.misc.SecurityCheck; import no.asgari.civilization.server.model.Civ; import no.asgari.civilization.server.model.Draw; import no.asgari.civilization.server.model.GameLog; 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 no.asgari.civilization.server.model.SocialPolicy; import no.asgari.civilization.server.model.Tech; import no.asgari.civilization.server.model.Tradable; import no.asgari.civilization.server.model.Unit; import org.apache.commons.codec.digest.DigestUtils; import org.mongojack.DBQuery; import org.mongojack.JacksonDBCollection; import org.mongojack.WriteResult; import javax.ws.rs.WebApplicationException; import javax.ws.rs.client.Entity; import javax.ws.rs.core.Response; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.Base64; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @Log4j public class PlayerAction extends BaseAction { private final JacksonDBCollection<Player, String> playerCollection; private final JacksonDBCollection<PBF, String> pbfCollection; private final JacksonDBCollection<GameLog, String> gameLogCollection; private final DrawAction drawAction; public PlayerAction(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.gameLogCollection = JacksonDBCollection.wrap(db.getCollection(GameLog.COL_NAME), GameLog.class, String.class); this.drawAction = new DrawAction(db); } /** * Returns a set of all the game ids of player */ public Set<String> getGames(Player player) { Preconditions.checkNotNull(player); log.debug("Getting all games for player " + player.getUsername()); return player.getGameIds(); } /** * Choose a tech for player and store back in the pbf collection * * @param pbfId - The pbf id * @param techName - The tech * @param playerId - The id of player */ public GameLog chooseTech(String pbfId, String techName, String playerId) { Preconditions.checkNotNull(pbfId); Preconditions.checkNotNull(techName); PBF pbf = pbfCollection.findOneById(pbfId); if (!SecurityCheck.hasUserAccess(pbf, playerId)) { log.error("User with id " + playerId + " has no access to pbf " + pbf.getName()); throw new WebApplicationException(Response.Status.FORBIDDEN); } Optional<Tech> tech = pbf.getTechs().stream().filter(techToFind -> techToFind.getName().equals(techName)) .findFirst(); //if not static then this::cannotFindItem Tech chosenTech = tech.orElseThrow(PlayerAction::cannotFindItem); chosenTech.setHidden(true); chosenTech.setOwnerId(playerId); Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); if (playerhand.getTechsChosen().contains(chosenTech)) { log.warn("Player with id " + playerId + " tried to add same tech as they had"); return null; } playerhand.getTechsChosen().add(chosenTech); pbfCollection.updateById(pbf.getId(), pbf); log.debug("Player " + playerId + " chose tech " + chosenTech.getName()); return super.createLog(chosenTech, pbfId, GameLog.LogType.TECH); } public boolean removeTech(String pbfId, String techName, String playerId) { Preconditions.checkNotNull(techName); Preconditions.checkNotNull(pbfId); Preconditions.checkNotNull(playerId); PBF pbf = pbfCollection.findOneById(pbfId); if (!SecurityCheck.hasUserAccess(pbf, playerId)) { log.error("User with id " + playerId + " has no access to pbf " + pbf.getName()); throw new WebApplicationException(Response.Status.FORBIDDEN); } Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); Tech techToRemove = playerhand.getTechsChosen().stream().filter(tech -> tech.getName().equals(techName)) .findFirst().orElseThrow(PlayerAction::cannotFindItem); boolean removed = playerhand.getTechsChosen().remove(techToRemove); if (!removed) { log.error("Could not remove tech " + techName + " from player with id " + playerId + " in pbf " + pbf.getName()); return false; } pbfCollection.updateById(pbf.getId(), pbf); super.createLog(techToRemove, pbfId, GameLog.LogType.REMOVED_TECH); return true; } public boolean endTurn(String pbfId, Player player) { Preconditions.checkNotNull(pbfId); Preconditions.checkNotNull(player.getUsername()); PBF pbf = pbfCollection.findOneById(pbfId); if (pbf.getPlayers().get(0).getPlayernumber() > 0) { Playerhand playerhand = pbf.getPlayers().stream().filter(Playerhand::isYourTurn).findFirst().get(); playerhand.setYourTurn(false); int nextPlayerNumber = playerhand.getPlayernumber() + 1; Playerhand firstPlayer = pbf.getPlayers().stream().filter(p -> p.getPlayernumber() == 1).findFirst() .get(); Playerhand nextPlayer = pbf.getPlayers().stream().filter(p -> p.getPlayernumber() == nextPlayerNumber) .findFirst().orElse(firstPlayer); nextPlayer.setYourTurn(true); SendEmail.sendYourTurn(pbf.getName(), nextPlayer.getEmail(), pbf.getId()); pbfCollection.updateById(pbf.getId(), pbf); return true; } else { //Old way, this else can be deleted once all games after januar 4 is over //Loop through the list and find next starting player for (int i = 0; i < pbf.getPlayers().size(); i++) { Playerhand playerhand = pbf.getPlayers().get(i); if (playerhand.getUsername().equals(player.getUsername())) { playerhand.setYourTurn(false); //Choose next player in line to be starting player Playerhand nextPlayer; if (pbf.getPlayers().size() == (i + 1)) { nextPlayer = pbf.getPlayers().get(0); } else { nextPlayer = pbf.getPlayers().get(i + 1); } nextPlayer.setYourTurn(true); SendEmail.sendYourTurn(pbf.getName(), nextPlayer.getEmail(), pbf.getId()); pbfCollection.updateById(pbf.getId(), pbf); return true; } } } return false; } /** * Revealing of items are really just saving a public log with the hidden content information * * @param pbfId * @param playerId * @param itemDTO - The item to reveal */ @SuppressWarnings("unchecked") public void revealItem(String pbfId, String playerId, ItemDTO itemDTO) { Preconditions.checkNotNull(itemDTO); Preconditions.checkNotNull(pbfId); Preconditions.checkNotNull(playerId); //Check if item can be found on the player PBF pbf = pbfCollection.findOneById(pbfId); Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); if (!SecurityCheck.hasUserAccess(pbf, playerId)) { log.error("User with id " + playerId + " has no access to pbf " + pbf.getName()); throw new WebApplicationException(Response.Status.FORBIDDEN); } List<Item> items = playerhand.getItems(); Optional<SheetName> sheetName = SheetName.find(itemDTO.getSheetName()); if (!sheetName.isPresent()) { log.error("Cannot find Sheetname " + itemDTO.getSheetName()); throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST) .entity(Entity.json("{\"msg\": \"Cannot find Sheetname " + itemDTO.getSheetName() + "\"}")) .build()); } Optional<Item> itemToRevealOptional = items.stream().filter(it -> it.getName().equals(itemDTO.getName())) .filter(it -> it.getSheetName() == sheetName.get()).filter(Item::isHidden).findAny(); if (!itemToRevealOptional.isPresent()) { log.warn("Item " + itemDTO.getName() + " already revealed"); throw new WebApplicationException(Response.status(Response.Status.NOT_MODIFIED) .entity(Entity.json("{\"msg\": \"Item already revealed\"}")).build()); } boolean isCiv = isCivilization(playerhand, sheetName); Item itemToReveal = itemToRevealOptional.get(); itemToReveal.setHidden(false); if (isCiv) { Civ civ = (Civ) itemToReveal; playerhand.setCivilization(civ); Tech startingTech = civ.getStartingTech(); startingTech.setHidden(false); startingTech.setOwnerId(playerId); playerhand.getTechsChosen().add(startingTech); pbfCollection.updateById(pbf.getId(), pbf); //Create a new log entry logAction.createGameLog(itemToReveal, pbf.getId(), GameLog.LogType.REVEAL); log.debug("item to be reveal " + itemToReveal); //If player has no units, then no need to call this if (!playerhand.getItems().stream().anyMatch(p -> p instanceof Unit)) { drawStartingItems(pbfId, playerId, civ); } deleteTheOtherCivs(pbfId, playerId, civ); } else { pbfCollection.updateById(pbf.getId(), pbf); //Create a new log entry logAction.createGameLog(itemToReveal, pbf.getId(), GameLog.LogType.REVEAL); log.debug("item to be reveal " + itemToReveal); } } private void deleteTheOtherCivs(String pbfId, String playerId, Civ civ) { PBF pbf = pbfCollection.findOneById(pbfId); Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); Iterator<Item> iterator = playerhand.getItems().iterator(); boolean deleted = false; while (iterator.hasNext()) { Item item = iterator.next(); if (item instanceof Civ && !item.equals(civ)) { item.setHidden(true); pbf.getDiscardedItems().add(item); iterator.remove(); deleted = true; createLog(item, pbf.getId(), GameLog.LogType.DISCARD, playerId); } } if (deleted) { pbfCollection.updateById(pbf.getId(), pbf); } } private boolean isCivilization(Playerhand playerhand, Optional<SheetName> sheetName) { if (sheetName.get() == SheetName.CIV) { if (playerhand.getCivilization() != null) { log.warn("Cannot choose civilization again"); throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST) .entity(Entity.json("{\"msg\": \"Civilization already chosen\"}")).build()); } return true; } return false; } /** * Only units and wonder for Egypt is drawn */ private void drawStartingItems(String pbfId, String playerId, Civ civ) { switch (civ.getName()) { case "Germans": drawAction.draw(pbfId, playerId, SheetName.INFANTRY); drawAction.draw(pbfId, playerId, SheetName.INFANTRY); drawAction.draw(pbfId, playerId, SheetName.INFANTRY); drawAction.draw(pbfId, playerId, SheetName.ARTILLERY); drawAction.draw(pbfId, playerId, SheetName.MOUNTED); break; case "Mongols": drawAction.draw(pbfId, playerId, SheetName.INFANTRY); drawAction.draw(pbfId, playerId, SheetName.ARTILLERY); drawAction.draw(pbfId, playerId, SheetName.MOUNTED); drawAction.draw(pbfId, playerId, SheetName.MOUNTED); drawAction.draw(pbfId, playerId, SheetName.MOUNTED); break; case "Zulu": drawAction.draw(pbfId, playerId, SheetName.INFANTRY); drawAction.draw(pbfId, playerId, SheetName.ARTILLERY); drawAction.draw(pbfId, playerId, SheetName.ARTILLERY); drawAction.draw(pbfId, playerId, SheetName.ARTILLERY); drawAction.draw(pbfId, playerId, SheetName.MOUNTED); break; case "Egyptians": drawAction.draw(pbfId, playerId, SheetName.INFANTRY); drawAction.draw(pbfId, playerId, SheetName.ARTILLERY); drawAction.draw(pbfId, playerId, SheetName.MOUNTED); drawAction.draw(pbfId, playerId, SheetName.ANCIENT_WONDERS); break; default: drawAction.draw(pbfId, playerId, SheetName.INFANTRY); drawAction.draw(pbfId, playerId, SheetName.ARTILLERY); drawAction.draw(pbfId, playerId, SheetName.MOUNTED); break; } } /** * Revealing of techs are really just saving a public log with the hidden content information * * @param gameLog * @param pbfId * @param playerId */ public void revealTech(GameLog gameLog, String pbfId, String playerId) { Preconditions.checkNotNull(gameLog); Preconditions.checkNotNull(pbfId); Preconditions.checkNotNull(playerId); PBF pbf = pbfCollection.findOneById(pbfId); if (!SecurityCheck.hasUserAccess(pbf, playerId)) { log.error("User with id " + playerId + " has no access to pbf " + pbf.getName()); throw new WebApplicationException(Response.Status.FORBIDDEN); } Draw<?> draw = gameLog.getDraw(); if (draw == null || draw.getItem() == null) { log.error("Couldn't find tech to reveal"); throw new WebApplicationException(Response.Status.NOT_FOUND); } Item item = draw.getItem(); item.setHidden(false); Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); Tech tech = playerhand.getTechsChosen().stream().filter(t -> t.getName().equals(item.getName())).findFirst() .orElseThrow(PlayerAction::cannotFindItem); tech.setHidden(false); gameLogCollection.updateById(gameLog.getId(), gameLog); pbfCollection.updateById(pbfId, pbf); createLog(item, pbf.getId(), GameLog.LogType.REVEAL, playerId); } /** * Returns the remaining techs the player can choose from * * @param playerId - The player * @param pbfId - The PBF * @return */ public List<Tech> getRemaingTechsForPlayer(String playerId, String pbfId) { PBF pbf = pbfCollection.findOneById(pbfId); Optional<Playerhand> playerhandOptional = pbf.getPlayers().stream() .filter(p -> p.getPlayerId().equals(playerId)).findFirst(); Set<Tech> techsChosen = playerhandOptional.orElseThrow(PlayerAction::cannotFindPlayer).getTechsChosen(); Playerhand playerhand = playerhandOptional.get(); if (playerhand.getCivilization() != null && playerhand.getCivilization().getStartingTech() != null) { techsChosen.add(playerhandOptional.get().getCivilization().getStartingTech()); } List<Tech> techs = pbf.getTechs(); techs.removeAll(techsChosen); techs.sort(((o1, o2) -> Integer.valueOf(o1.getLevel()).compareTo(o2.getLevel()))); return techs; } /** * Method that's checks whether it is players turn. * Not the same as #checkYourTurn() * * @param pbfId - PBF id * @param playerId - Player id * @return - true if it is players turn * @see #checkYourTurn(String, String) */ public boolean isYourTurn(String pbfId, String playerId) { PBF pbf = pbfCollection.findOneById(pbfId); Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); return playerhand.isYourTurn(); } /** * Will send the item to the new owner * * @param item * @param playerId * @return */ public boolean tradeToPlayer(ItemDTO item, String playerId) { Preconditions.checkNotNull(item); Preconditions.checkNotNull(item.getPbfId()); Preconditions.checkNotNull(item.getOwnerId()); PBF pbf = pbfCollection.findOneById(item.getPbfId()); Playerhand fromPlayer = getPlayerhandByPlayerId(playerId, pbf); Playerhand toPlayer = getPlayerhandByPlayerId(item.getOwnerId(), pbf); Optional<SheetName> dtoSheet = SheetName.find(item.getSheetName()); if (!dtoSheet.isPresent()) { log.error("Couldn't find sheetname " + item.getSheetName()); throw cannotFindItem(); } Item itemToTrade = fromPlayer.getItems().stream().filter(it -> it instanceof Tradable) .filter(it -> it.getSheetName() == dtoSheet.get()).filter(it -> it.getName().equals(item.getName())) .findFirst().orElseThrow(PlayerAction::cannotFindItem); boolean remove = fromPlayer.getItems().remove(itemToTrade); if (!remove) { log.error("Didn't find item from playerhand: " + item); return false; } toPlayer.getItems().add(itemToTrade); itemToTrade.setOwnerId(toPlayer.getPlayerId()); pbfCollection.updateById(pbf.getId(), pbf); logAction.createTradeGameLog(itemToTrade, pbf.getId(), GameLog.LogType.TRADE_BETWEEN_PLAYERS, fromPlayer.getUsername()); return true; } public void discardItem(String pbfId, String playerId, ItemDTO itemdto) { PBF pbf = pbfCollection.findOneById(pbfId); Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); Optional<SheetName> dtoSheet = SheetName.find(itemdto.getSheetName()); if (!dtoSheet.isPresent()) { log.error("Couldn't find sheetname " + itemdto.getSheetName()); throw cannotFindItem(); } //Find the item, then delete it Optional<Item> itemToDeleteOptional = playerhand.getItems().stream() .filter(item -> item.getSheetName() == dtoSheet.get() && item.getName().equals(itemdto.getName())) .findAny(); if (!itemToDeleteOptional.isPresent()) throw cannotFindItem(); Item itemToDelete = itemToDeleteOptional.get(); itemToDelete.setHidden(true); //itemToDelete.setOwnerId(null); //I think I need this in case of undo if (playerhand.getItems().remove(itemToDeleteOptional.get())) { pbf.getDiscardedItems().add(itemToDeleteOptional.get()); createLog(itemToDelete, pbf.getId(), GameLog.LogType.DISCARD, playerId); pbfCollection.updateById(pbf.getId(), pbf); return; } log.error("Found the item " + itemToDelete + " , but couldn't delete it for some reason"); throw cannotFindItem(); } public Player getPlayerById(String playerId) { return playerCollection.findOneById(playerId); } public Set<Tech> getPlayersTechs(String pbfId, String playerId) { Preconditions.checkNotNull(pbfId); Preconditions.checkNotNull(playerId); PBF pbf = pbfCollection.findOneById(pbfId); return pbf.getPlayers().stream().filter(p -> p.getPlayerId().equals(playerId)).findFirst() .orElseThrow(PlayerAction::cannotFindPlayer).getTechsChosen(); } /** * Returns the id to the player created * * @return the id of the newly created player * @throws PlayerExistException - Throws this exception if username already exists */ @SneakyThrows public String createPlayer(String usernameEncoded, String passwordEncoded, String emailEncoded) throws PlayerExistException { Preconditions.checkNotNull(usernameEncoded); Preconditions.checkNotNull(passwordEncoded); Preconditions.checkNotNull(emailEncoded); String username = URLDecoder.decode(usernameEncoded, "UTF-8"); String email = URLDecoder.decode(emailEncoded, "UTF-8"); String password = URLDecoder.decode(passwordEncoded, "UTF-8"); if (CivSingleton.instance().playerCache().asMap().containsValue(username)) { throw new PlayerExistException(); } Player player = new Player(); player.setUsername(username); String decodedPassword = new String(Base64.getDecoder().decode(password), "UTF-8"); player.setPassword(DigestUtils.sha1Hex(decodedPassword)); player.setEmail(email); WriteResult<Player, String> insert = playerCollection.insert(player); log.info(String.format("Saving player with id %s", insert.getSavedId())); return insert.getSavedId(); } public void newPassword(String username, String newPass) throws Exception { Player player = playerCollection.findOne(DBQuery.is(Player.USERNAME, username)); if (player == null) { throw new WebApplicationException(Response.Status.BAD_REQUEST); } String password = URLDecoder.decode(newPass, "UTF-8"); player.setPassword(DigestUtils.sha1Hex(password)); playerCollection.updateById(player.getId(), player); } public boolean newPassword(ForgotpassDTO forgotpassDTO) { Preconditions.checkNotNull(forgotpassDTO.getEmail()); Preconditions.checkNotNull(forgotpassDTO.getNewpassword()); Player player = playerCollection.findOne(DBQuery.is(Player.EMAIL, forgotpassDTO.getEmail())); if (player == null) { log.error("Couldn't find user by email " + forgotpassDTO.getEmail()); throw new WebApplicationException(Response.Status.NOT_FOUND); } player.setNewPassword(forgotpassDTO.getNewpassword()); playerCollection.updateById(player.getId(), player); return SendEmail.sendMessage(player.getEmail(), "Please verify your email", "Your password was requested to be changed. If you want to change your password then please press this link: " + SendEmail.REST_URL + "api/auth/verify/" + player.getId(), player.getId()); } public boolean verifyPassword(String playerId) { Player player = playerCollection.findOneById(playerId); if (player != null && !Strings.isNullOrEmpty(player.getNewPassword())) { try { String password = URLDecoder.decode(player.getNewPassword(), "UTF-8"); player.setPassword(DigestUtils.sha1Hex(password)); player.setNewPassword(null); playerCollection.updateById(player.getId(), player); return true; } catch (UnsupportedEncodingException e) { log.error("Couldn't write password ", e); throw new WebApplicationException(Response.Status.BAD_REQUEST); } } return false; } public GameLog chooseSocialPolicy(String pbfId, String socialPolicyName, String playerId) { Preconditions.checkNotNull(pbfId); Preconditions.checkNotNull(socialPolicyName); PBF pbf = pbfCollection.findOneById(pbfId); if (!SecurityCheck.hasUserAccess(pbf, playerId)) { log.error("User with id " + playerId + " has no access to pbf " + pbf.getName()); throw new WebApplicationException(Response.Status.FORBIDDEN); } Optional<SocialPolicy> socialPolicyOptional = pbf.getSocialPolicies().stream() .filter(it -> it.getName().equals(socialPolicyName)).findFirst(); //if not static then this::cannotFindItem SocialPolicy socialPolicy = socialPolicyOptional.orElseThrow(PlayerAction::cannotFindItem); Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); if (playerhand.getSocialPolicies().contains(socialPolicy)) { log.warn("Player with id " + playerId + " tried to add same social policy as they had"); return null; } if (playerhand.getSocialPolicies().stream() .anyMatch(sp -> sp.getName().equals(socialPolicy.getFlipside()))) { log.warn("Player with id " + playerId + " tried to add a social policy on same flipside"); throw new WebApplicationException(Response.Status.BAD_REQUEST); } SocialPolicy sp = new SocialPolicy(socialPolicyName); sp.setFlipside(socialPolicy.getFlipside()); sp.setOwnerId(playerId); sp.setHidden(true); playerhand.getSocialPolicies().add(sp); pbfCollection.updateById(pbf.getId(), pbf); log.debug("Player " + playerId + " chose social policy " + sp.getName()); return super.createLog(sp, pbfId, GameLog.LogType.SOCIAL_POLICY, playerId); } public List<AllTechsDTO> getTechsForAllPlayers(String pbfId) { Preconditions.checkNotNull(pbfId); PBF pbf = pbfCollection.findOneById(pbfId); return pbf.getPlayers().stream().filter(p -> p.getCivilization() != null) .map(p -> new AllTechsDTO(p.getCivilization().getName(), p.getColor(), p.getTechsChosen().stream().filter(t -> !t.isHidden()) .map(t -> new TechDTO(t.getName(), t.getLevel())).collect(Collectors.toList()))) .collect(Collectors.toList()); } public void saveNote(String pbfId, String playerId, MessageDTO messageDTO) { Preconditions.checkNotNull(pbfId); Preconditions.checkNotNull(playerId); PBF pbf = pbfCollection.findOneById(pbfId); Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); playerhand.setGamenote(messageDTO.getMessage()); pbfCollection.updateById(pbfId, pbf); } }