/* * 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.mongodb.DB; import lombok.extern.log4j.Log4j; import no.asgari.civilization.server.SheetName; import no.asgari.civilization.server.dto.MessageDTO; import no.asgari.civilization.server.exception.NoMoreItemsException; 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.Tradable; import no.asgari.civilization.server.model.Unit; import org.mongojack.JacksonDBCollection; import javax.ws.rs.WebApplicationException; import javax.ws.rs.client.Entity; import javax.ws.rs.core.Response; import java.util.Collections; import java.util.EnumSet; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.function.Consumer; import static java.util.stream.Collectors.toList; import static no.asgari.civilization.server.SheetName.ARTILLERY; import static no.asgari.civilization.server.SheetName.INFANTRY; import static no.asgari.civilization.server.SheetName.MOUNTED; /** * Class that will perform draws and log them. * Draws will be saved in the gamelog collection */ @Log4j public class DrawAction extends BaseAction { private final JacksonDBCollection<PBF, String> pbfCollection; private final GameLogAction gameLogAction; private final StringBuilder sb = new StringBuilder(); private final Consumer<Unit> revealUnitConsumer = unit -> sb.append(unit.revealAll()).append(", "); public DrawAction(DB db) { super(db); this.pbfCollection = JacksonDBCollection.wrap(db.getCollection(PBF.COL_NAME), PBF.class, String.class); gameLogAction = new GameLogAction(db); } public Optional<GameLog> draw(String pbfId, String playerId, SheetName sheetName) { Preconditions.checkNotNull(pbfId); Preconditions.checkNotNull(playerId); Preconditions.checkNotNull(sheetName); checkYourTurn(pbfId, playerId); PBF pbf = pbfCollection.findOneById(pbfId); if (SheetName.TECHS.contains(sheetName)) { log.warn("Drawing of techs is not possible. Techs are supposed to be chosen, not drawn."); return Optional.empty(); } else { //Java 8 streamFromIterable doesn't support remove very well Iterator<Item> iterator = pbf.getItems().iterator(); while (iterator.hasNext()) { Item item = iterator.next(); if (item.getSheetName() == sheetName) { putItemToPlayer(item, pbf, playerId); iterator.remove(); item.setOwnerId(playerId); pbfCollection.updateById(pbf.getId(), pbf); log.debug("Drew item " + item + " and updated pbf"); Draw<Item> draw = createDraw(pbfId, playerId, item); GameLog gamelog = createLog(draw, GameLog.LogType.ITEM); return Optional.of(gamelog); } } } log.warn("No more " + sheetName.getName() + " to draw. Possibly no more items left to draw in the deck. Will try to reshuffle"); reshuffleItems(sheetName, pbf); //Recursion call (should only be called once, because shuffle is made) return draw(pbfId, playerId, sheetName); } private void reshuffleItems(SheetName sheetName, PBF pbf) throws NoMoreItemsException { if (!SheetName.SHUFFLABLE_ITEMS.contains(sheetName)) { log.warn("Tried to reshuffle " + sheetName.getName() + " but not a shufflable type"); throw new IllegalArgumentException(); } List<Item> itemsToPutBackInDeck = pbf.getDiscardedItems().stream() .filter(s -> s.getSheetName() == sheetName) .collect(toList()); if (itemsToPutBackInDeck.isEmpty()) { log.warn("All items are still in use, cannot make a shuffle. Nothing to draw!"); throw new NoMoreItemsException(sheetName.getName()); } List<Item> itemsToKeep = pbf.getDiscardedItems().stream() .filter(s -> s.getSheetName() != sheetName) .collect(toList()); log.debug("Shuffling, and adding items back in the pbf"); Collections.shuffle(itemsToPutBackInDeck); pbf.getItems().addAll(itemsToPutBackInDeck); pbf.setDiscardedItems(itemsToKeep); pbfCollection.updateById(pbf.getId(), pbf); logShuffle(sheetName, pbf); } public List<Unit> drawUnitsFromBattlehandForBattle(String pbfId, String playerId, int numberOfDraws) { PBF pbf = pbfCollection.findOneById(pbfId); Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); playerhand.getBattlehand().clear(); List<Unit> unitsInHand = playerhand.getItems().stream() .filter(p -> SheetName.UNITS.contains(p.getSheetName())) .map(p -> (Unit) p) .collect(toList()); if (unitsInHand.isEmpty()) { return unitsInHand; } if (unitsInHand.size() <= numberOfDraws) { //username has drawn X units from his battlehand playerhand.setBattlehand(unitsInHand); pbfCollection.updateById(pbfId, pbf); createCommonPublicLog("has drawn " + unitsInHand.size() + " units his battlehand", pbfId, playerId); return unitsInHand; } Collections.shuffle(unitsInHand); List<Unit> drawnUnitsList = unitsInHand.stream().limit(numberOfDraws).collect(toList()); playerhand.setBattlehand(drawnUnitsList); pbfCollection.updateById(pbfId, pbf); createCommonPublicLog("has drawn " + drawnUnitsList.size() + " units from his battlehand", pbfId, playerId); return drawnUnitsList; } /** * Draws up to three barbarians. * If it cannot get a type, it will try to draw another * * @param pbfId * @param playerId * @return */ public List<Unit> drawBarbarians(String pbfId, String playerId) { PBF pbf = pbfCollection.findOneById(pbfId); Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); if (!playerhand.getBarbarians().isEmpty()) { log.warn("Cannot draw more barbarians until they are discarded"); throw new WebApplicationException(Response.status(Response.Status.PRECONDITION_FAILED) .entity(Entity.json(new MessageDTO("Cannot draw more barbarians until they are discarded"))).build()); } drawBarbarianInfantry(pbf, playerhand); drawBarbarianArtillery(pbf, playerhand); drawBarbarianMounted(pbf, playerhand); pbf = pbfCollection.findOneById(pbfId); playerhand = getPlayerhandByPlayerId(playerId, pbf); gameLogAction.createCommonPrivatePublicLog("has drawn " + playerhand.getBarbarians().size() + " barbarian units", pbfId, playerId); return playerhand.getBarbarians(); } private void drawBarbarianInfantry(PBF pbf, Playerhand playerhand) { boolean anyInfantry = pbf.getItems().stream().anyMatch(p -> p.getSheetName() == INFANTRY); if (!anyInfantry) { try { reshuffleItems(INFANTRY, pbf); drawBarbarianInfantry(pbf, playerhand); } catch (NoMoreItemsException e) { StringBuilder stringBuilder = new StringBuilder(); if (tryToFindUnit(pbf, playerhand, stringBuilder, ARTILLERY, INFANTRY) || tryToFindUnit(pbf, playerhand, stringBuilder, MOUNTED, INFANTRY)) { gameLogAction.createCommonPrivatePublicLog(stringBuilder.toString(), pbf.getId(), playerhand.getPlayerId()); } else { throw new NoMoreItemsException("units"); } } } else { Iterator<Item> iterator = pbf.getItems().iterator(); while (iterator.hasNext()) { Item item = iterator.next(); if (item.getSheetName() == SheetName.INFANTRY) { addBarbarian(pbf, playerhand, iterator, item); return; } } } } private void drawBarbarianMounted(PBF pbf, Playerhand playerhand) { boolean any = pbf.getItems().stream().anyMatch(p -> p.getSheetName() == MOUNTED); if (!any) { try { reshuffleItems(MOUNTED, pbf); drawBarbarianMounted(pbf, playerhand); } catch (NoMoreItemsException e) { StringBuilder stringBuilder = new StringBuilder(); if (tryToFindUnit(pbf, playerhand, stringBuilder, ARTILLERY, MOUNTED) || tryToFindUnit(pbf, playerhand, stringBuilder, INFANTRY, MOUNTED)) { gameLogAction.createCommonPrivatePublicLog(stringBuilder.toString(), pbf.getId(), playerhand.getPlayerId()); } else { throw new NoMoreItemsException("units"); } } } else { Iterator<Item> iterator = pbf.getItems().iterator(); while (iterator.hasNext()) { Item item = iterator.next(); if (item.getSheetName() == SheetName.MOUNTED) { addBarbarian(pbf, playerhand, iterator, item); return; } } } } private void drawBarbarianArtillery(PBF pbf, Playerhand playerhand) { boolean any = pbf.getItems().stream().anyMatch(p -> p.getSheetName() == ARTILLERY); if (!any) { try { reshuffleItems(ARTILLERY, pbf); drawBarbarianArtillery(pbf, playerhand); } catch (NoMoreItemsException e) { StringBuilder stringBuilder = new StringBuilder(); if (tryToFindUnit(pbf, playerhand, stringBuilder, MOUNTED, ARTILLERY) || tryToFindUnit(pbf, playerhand, stringBuilder, INFANTRY, ARTILLERY)) { gameLogAction.createCommonPrivatePublicLog(stringBuilder.toString(), pbf.getId(), playerhand.getPlayerId()); } else { throw new NoMoreItemsException("units"); } } } else { Iterator<Item> iterator = pbf.getItems().iterator(); while (iterator.hasNext()) { Item item = iterator.next(); if (item.getSheetName() == SheetName.ARTILLERY) { addBarbarian(pbf, playerhand, iterator, item); return; } } } } private boolean tryToFindUnit(PBF pbf, Playerhand playerhand, StringBuilder stringBuilder, SheetName sheetToDraw, SheetName originalSheet) { Optional<Item> anyUnit = pbf.getItems().stream().filter(p -> p.getSheetName() == sheetToDraw).findAny(); if (anyUnit.isPresent()) { Iterator<Item> iterator = pbf.getItems().iterator(); while (iterator.hasNext()) { Item item = iterator.next(); if (item.getSheetName() == sheetToDraw) { addBarbarian(pbf, playerhand, iterator, item); stringBuilder.append(" tried to draw ") .append(originalSheet.getName()) .append(" barbarian unit. However there are no more in the deck. Will instead draw ") .append(sheetToDraw.getName()) .append(" unit instead!"); return true; } } } return false; } public void discardBarbarians(String pbfId, String playerId) { PBF pbf = pbfCollection.findOneById(pbfId); Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); if (playerhand.getBarbarians().isEmpty()) { return; } playerhand.getBarbarians().forEach(unit -> unit.setOwnerId(null)); pbf.getDiscardedItems().addAll(playerhand.getBarbarians()); revealAndDiscardUnits(" as barbarians", playerhand.getBarbarians(), pbfId, playerId); pbfCollection.updateById(pbf.getId(), pbf); } public void revealAndDiscardBattlehand(String pbfId, String playerId) { PBF pbf = pbfCollection.findOneById(pbfId); Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); if (playerhand.getBattlehand().isEmpty()) { log.warn("Tried to reveal playerhand, but was empty"); return; } revealAndDiscardUnits(" from their battlehand", playerhand.getBattlehand(), pbfId, playerId); pbfCollection.updateById(pbfId, pbf); } private void revealAndDiscardUnits(String message, List<Unit> units, String pbfId, String playerId) { //Clear just in case sb.setLength(0); units.forEach(revealUnitConsumer); //Remove the last append ', ' if (sb.length() > 1) { sb.setLength(sb.length() - 2); } createCommonPublicLog(" reveals " + sb.toString() + message, pbfId, playerId); units.clear(); } /** * Sets playerhands units to isBattle = false * * @param pbfId * @param playerId */ public void endBattle(String pbfId, String playerId) { PBF pbf = pbfCollection.findOneById(pbfId); Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); playerhand.getItems().stream() .filter(item -> SheetName.UNITS.contains(item.getSheetName())) .forEach(item -> { Unit unit = (Unit) item; unit.setInBattle(false); }); pbfCollection.updateById(pbfId, pbf); } /** * draws a random item from playerhand and gives to another player * * @param pbfId - The pbf id * @param sheetNames - the item to be automatically drawn from playerhand and given to another player * @param targetPlayerId - The targeted player which will recieve the item * @param playerId - The logged in player that we will take the item from */ public Item loot(String pbfId, EnumSet<SheetName> sheetNames, String targetPlayerId, String playerId) { Preconditions.checkNotNull(pbfId); Preconditions.checkNotNull(sheetNames); Preconditions.checkNotNull(targetPlayerId); Preconditions.checkNotNull(playerId); PBF pbf = findPBFById(pbfId); Playerhand playerFrom = getPlayerhandByPlayerId(playerId, pbf); Playerhand playerTo = getPlayerhandByPlayerId(targetPlayerId, pbf); //Locate the item from player List<Item> itemToShuffle = playerFrom.getItems().stream() .filter(it -> sheetNames.contains(it.getSheetName())) .collect(toList()); if (itemToShuffle.isEmpty()) { throw new WebApplicationException( Response.status(Response.Status.NOT_FOUND) .entity(new MessageDTO("You have nothing to draw")) .build() ); } if (!(itemToShuffle.get(0) instanceof Tradable)) { throw new WebApplicationException( Response.status(Response.Status.NOT_ACCEPTABLE) .entity(new MessageDTO("Item is not lootable")) .build() ); } Collections.shuffle(itemToShuffle); Item itemToGive = itemToShuffle.get(0); log.debug(playerFrom.getUsername() + " gives " + itemToGive + " to " + playerTo.getUsername()); boolean removed = playerFrom.getItems().remove(itemToGive); if (removed) { //Give to the other player playerTo.getItems().add(itemToGive); createCommonPrivateLog(" is randomly looted " + itemToGive.revealAll() + " and gives to " + playerTo.getUsername(), pbfId, playerFrom.getPlayerId()); createCommonPrivateLog(" receives as loot " + itemToGive.revealAll() + " from " + playerFrom.getUsername(), pbfId, playerTo.getPlayerId()); createCommonPublicLog(" is randomly looted " + itemToGive.revealPublic() + " and gives to " + playerTo.getUsername(), pbfId, playerFrom.getPlayerId()); createCommonPublicLog(" receives as loot " + itemToGive.revealPublic() + " from " + playerFrom.getUsername(), pbfId, playerTo.getPlayerId()); pbfCollection.updateById(pbf.getId(), pbf); return itemToGive; } else { throw cannotFindItem(); } } private void addBarbarian(PBF pbf, Playerhand playerhand, Iterator<Item> iterator, Item item) { playerhand.getBarbarians().add((Unit) item); iterator.remove(); item.setOwnerId(playerhand.getPlayerId()); pbfCollection.updateById(pbf.getId(), pbf); } private void logShuffle(SheetName sheetName, PBF pbf) { GameLog log = new GameLog(); log.setUsername("System"); log.setPbfId(pbf.getId()); log.setPublicLog(sheetName.getName() + " reshuffled and put back in the deck"); gameLogAction.save(log); } private void putItemToPlayer(Item item, PBF pbf, String playerId) { Playerhand playerhand = getPlayerhandByPlayerId(playerId, pbf); playerhand.getItems().add(item); } private static Draw<Item> createDraw(String pbfId, String playerId, Item item) { Draw<Item> draw = new Draw<>(pbfId, playerId); item.setHidden(true); draw.setItem(item); return draw; } }