/* * 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.BasicDBObject; import com.mongodb.DB; import lombok.extern.log4j.Log4j; import no.asgari.civilization.server.SheetName; import no.asgari.civilization.server.dto.ItemDTO; 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.Playerhand; import no.asgari.civilization.server.model.Tech; import no.asgari.civilization.server.model.Undo; import org.mongojack.DBQuery; import org.mongojack.JacksonDBCollection; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Random; import java.util.stream.Collectors; @Log4j public class UndoAction extends BaseAction { private final JacksonDBCollection<PBF, String> pbfCollection; private final JacksonDBCollection<GameLog, String> gameLogCollection; public UndoAction(DB db) { super(db); 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); } private boolean putDrawnItemBackInPBF(PBF pbf, String playerId, Item item) { Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); if (item instanceof Tech) { //Remove from tech if (playerhand.getTechsChosen().remove(item)) { logAction.createUndoLog(pbf.getId(), "has removed " + item.getName() + " from " + playerhand.getUsername(), item); log.debug("Successfully undoed tech"); } else if (pbf.getDiscardedItems().remove(item)) { playerhand.getTechsChosen().add((Tech) item); logAction.createUndoLog(pbf.getId(), "has added back " + item.getName() + " to " + playerhand.getUsername(), item); } else { log.error("Didn't find tech to remove from playerhand: " + item); return false; } } else { if (playerhand.getItems().remove(item)) { item.setHidden(true); logAction.createUndoLog(pbf.getId(), "has removed " + item.getName() + " from " + playerhand.getUsername() + " and put back in the deck. Deck is reshuffled", item); pbf.getItems().add(item); shufflePBFTwice(pbf); log.debug("Successfully undoed item"); } else if (pbf.getDiscardedItems().remove(item)) { item.setHidden(true); playerhand.getItems().add(item); logAction.createUndoLog(pbf.getId(), "has added back " + item.getName() + " to " + playerhand.getUsername(), item); } else if (pbf.getItems().remove(item)) { //In rare cases the item is put back to the player item.setHidden(true); playerhand.getItems().add(item); logAction.createUndoLog(pbf.getId(), "has added back " + item.getName() + " to " + playerhand.getUsername(), item); } else { log.error("Didn't find item to remove from playerhand: " + item); return false; } } pbfCollection.updateById(pbf.getId(), pbf); return true; } private boolean putDrawnItemBackInPBF(PBF pbf, Draw draw) { Preconditions.checkNotNull(pbf); Preconditions.checkNotNull(draw); Preconditions.checkNotNull(draw.getItem()); Item item = draw.getItem(); return putDrawnItemBackInPBF(pbf, item.getOwnerId(), draw); } private boolean putDrawnItemBackInPBF(PBF pbf, String playerId, Draw draw) { Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); Item item = draw.getItem(); if (item instanceof Tech) { //Remove from tech if (playerhand.getTechsChosen().remove(item)) { logAction.createUndoLog(pbf.getId(), "has removed " + item.getName() + " from " + playerhand.getUsername(), item); log.debug("Successfully undoed tech"); } else if (pbf.getDiscardedItems().remove(item)) { playerhand.getTechsChosen().add((Tech) item); logAction.createUndoLog(pbf.getId(), "has added back " + item.getName() + " to " + playerhand.getUsername(), item); } else { log.error("Didn't find tech to remove from playerhand: " + item); return false; } } else { if (!Strings.isNullOrEmpty(draw.getGameLogId())) { GameLog gamelog = gameLogCollection.findOneById(draw.getGameLogId()); if (gamelog.getPrivateLog().contains("discarded")) { if (pbf.getDiscardedItems().remove(item)) { item.setHidden(true); playerhand.getItems().add(item); logAction.createUndoLog(pbf.getId(), "has added back " + item.getName() + " to " + playerhand.getUsername(), item); } } else if (gamelog.getPrivateLog().contains("drew") && !gamelog.getPrivateLog().contains("barbarian")) { if (playerhand.getItems().remove(item) || pbf.getDiscardedItems().remove(item)) { item.setHidden(true); logAction.createUndoLog(pbf.getId(), "has removed " + item.getName() + " from " + playerhand.getUsername() + " and put back in the deck. Deck is reshuffled", item); pbf.getItems().add(item); shufflePBFTwice(pbf); } } else if (gamelog.getPrivateLog().contains("drew") && gamelog.getPrivateLog().contains("barbarian")) { pbf.getItems().addAll(playerhand.getBarbarians()); playerhand.getBarbarians().clear(); logAction.createUndoLog(pbf.getId(), "has removed barbarians from " + playerhand.getUsername() + " and put back in the deck. Deck is reshuffled", item); shufflePBFTwice(pbf); } else if (pbf.getItems().remove(item)) { //In rare cases the item is put back to the player (Not sure if I need this) item.setHidden(true); playerhand.getItems().add(item); logAction.createUndoLog(pbf.getId(), "has added back " + item.getName() + " to " + playerhand.getUsername(), item); } else { log.error("Didn't find item to remove from playerhand: " + item); return false; } } else { //Backward compability if (playerhand.getItems().remove(item)) { item.setHidden(true); logAction.createUndoLog(pbf.getId(), "has removed " + item.getName() + " from " + playerhand.getUsername() + " and put back in the deck. Deck is reshuffled", item); pbf.getItems().add(item); shufflePBFTwice(pbf); log.debug("Successfully undoed item"); } else if (pbf.getDiscardedItems().remove(item)) { item.setHidden(true); playerhand.getItems().add(item); logAction.createUndoLog(pbf.getId(), "has added back " + item.getName() + " to " + playerhand.getUsername(), item); } else if (pbf.getItems().remove(item)) { //In rare cases the item is put back to the player item.setHidden(true); playerhand.getItems().add(item); logAction.createUndoLog(pbf.getId(), "has added back " + item.getName() + " to " + playerhand.getUsername(), item); } else { log.error("Didn't find item to remove from playerhand: " + item); return false; } } } pbfCollection.updateById(pbf.getId(), pbf); return true; } private void shufflePBFTwice(PBF pbf) { Collections.shuffle(pbf.getItems(), new Random(System.nanoTime())); Collections.shuffle(pbf.getItems(), new Random(System.nanoTime())); } /** * Will perform vote, and if all votes are successfull, item is put back in the deck * <p> * A vote must have already been initiated * * @param gameLog * @param playerId * @param vote * @return */ public GameLog vote(GameLog gameLog, String playerId, boolean vote) { Preconditions.checkNotNull(gameLog); Preconditions.checkNotNull(gameLog.getDraw()); Preconditions.checkNotNull(gameLog.getDraw().getUndo()); PBF pbf = pbfCollection.findOneById(gameLog.getPbfId()); Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); if (gameLog.getDraw() == null || gameLog.getDraw().getUndo() == null) { log.error("This item cannot be undone. Nothing to undo."); throw new WebApplicationException(Response.status(Response.Status.PRECONDITION_FAILED) .build()); } gameLog.getDraw().getUndo().vote(playerId, vote); gameLog.getDraw().setGameLogId(gameLog.getId()); createLog(gameLog.getDraw(), pbf.getId(), playerhand.getUsername(), vote); Optional<Boolean> resultOfVotes = gameLog.getDraw().getUndo().getResultOfVotes(); if (resultOfVotes.isPresent() && resultOfVotes.get()) { log.info("Everyone has performed a vote, so we put item back in the deck"); gameLog.getDraw().getUndo().setDone(true); putDrawnItemBackInPBF(pbf, gameLog.getDraw()); } gameLogCollection.updateById(gameLog.getId(), gameLog); return gameLog; } /** * Will request for undo and initiate a vote * * @param logContainingItemToUndo * @param playerId * @return */ public void initiateUndo(GameLog logContainingItemToUndo, String playerId) { Preconditions.checkNotNull(logContainingItemToUndo); Preconditions.checkNotNull(logContainingItemToUndo.getDraw()); Draw<?> draw = logContainingItemToUndo.getDraw(); if (draw.getUndo() != null) { log.error("Cannot initiate a undo. Its already been initiated"); throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST) .build()); } PBF pbf = pbfCollection.findOneById(logContainingItemToUndo.getPbfId()); if (!pbf.getPlayers().stream().anyMatch(p -> p.getPlayerId().equals(playerId))) { log.error("Couldn't find playerId " + playerId + " in PBF's players"); throw PlayerAction.cannotFindPlayer(); } draw.setUndo(new Undo(pbf.getNumOfPlayers(), playerId)); gameLogCollection.updateById(logContainingItemToUndo.getId(), logContainingItemToUndo); createLog(draw.getItem(), pbf.getId(), GameLog.LogType.UNDO, playerId); } public List<GameLog> getAllActiveUndos(String pbfId) { List<GameLog> gameLogs = gameLogCollection.find(DBQuery.is("pbfId", pbfId), new BasicDBObject()).toArray(); return gameLogs.stream() .filter(GameLog::hasActiveUndo) .collect(Collectors.toList()); } public List<GameLog> getPlayersActiveUndoes(String pbfId, String username) { List<GameLog> gamelogs = gameLogCollection.find(DBQuery.is("pbfId", pbfId).is("username", username), new BasicDBObject()).toArray(); return gamelogs.stream() .filter(GameLog::hasActiveUndo) .collect(Collectors.toList()); } public List<GameLog> getAllFinishedUndos(String pbfId) { List<GameLog> gameLogs = gameLogCollection.find(DBQuery.is("pbfId", pbfId), new BasicDBObject()).toArray(); return gameLogs.stream() .filter(log -> log.getDraw() != null && log.getDraw().getUndo() != null && log.getDraw().getUndo().isDone()) .collect(Collectors.toList()); } public void playerPutsItemBackInDeck(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 putback to deck Optional<Item> itemToPutBack = playerhand.getItems().stream() .filter(item -> item.getSheetName() == dtoSheet.get() && item.getName().equals(itemdto.getName())) .findAny(); if (!itemToPutBack.isPresent()) { throw cannotFindItem(); } Item item = itemToPutBack.get(); boolean ok = putDrawnItemBackInPBF(pbf, playerId, item); if (!ok) { throw cannotFindItem(); } } }