diff --git a/src/main/java/net/deckserver/dwr/DeckserverRemote.java b/src/main/java/net/deckserver/dwr/DeckserverRemote.java index 2f4b83c2..ed8de068 100644 --- a/src/main/java/net/deckserver/dwr/DeckserverRemote.java +++ b/src/main/java/net/deckserver/dwr/DeckserverRemote.java @@ -6,12 +6,16 @@ import net.deckserver.dwr.creators.UpdateFactory; import net.deckserver.dwr.model.GameModel; import net.deckserver.dwr.model.GameView; +import net.deckserver.dwr.model.JolGame; import net.deckserver.dwr.model.PlayerModel; import net.deckserver.game.enums.GameFormat; +import net.deckserver.game.enums.RegionType; import net.deckserver.services.*; import net.deckserver.storage.json.deck.Deck; import net.deckserver.storage.json.deck.ExtendedDeck; +import net.deckserver.storage.json.game.CardData; import net.deckserver.storage.json.game.ChatData; +import net.deckserver.storage.json.game.RegionData; import net.deckserver.storage.json.system.DeckInfo; import net.deckserver.storage.json.system.GameHistory; import net.deckserver.storage.json.system.PlayerResult; @@ -411,6 +415,75 @@ public String exportPastGamesAsCsv() throws IOException { return writer.toString(); } + /** + * Swap two cards in a given Region + * @param gameName - Name where cards should be swapped + * @param player - Player whom card shall be swapped + * @param region - Region where the cards shall be swapped + * @param oldPos - Old Position of the Card + * @param newPos - New Positon of the Card + * @return Map with View to Update + */ + public Map swapCardsInRegion(String gameName, String player, String region, int oldPos, int newPos) { + //get Game & Region & Cards + JolGame game = GameService.getGameByName(gameName); + RegionData playerRegion = game.data().getPlayerRegion(player, RegionType.valueOf(region)); + LinkedList cards = (LinkedList) playerRegion.getCards(); + CardData cardData = getCard(cards, String.valueOf(oldPos)); + //swap pos in collection + Collections.swap(cards, oldPos-1, newPos); + sendChatMessage(game.id(), player, cardData.getName(), String.valueOf(oldPos), String.valueOf(newPos+1), region); + //reload State + return doReload(gameName); + } + + /** + * Detach a Card from another card to a Region + * + * @param gameName - Name where cards should be detached + * @param player - Player whom card shall be detached + * @param region - Region where the cards shall be detached + * @param oldPos - Old Position of the Card + * @param newPos - New Positon of the Card + * @return Map with View to Update + */ + public Map detachRegionCard(String gameName, String player, String region, String oldPos, int newPos) { + //get Game & Region & Cards + JolGame game = GameService.getGameByName(gameName); + RegionData playerRegion = game.data().getPlayerRegion(player, RegionType.valueOf(region)); + LinkedList cards = (LinkedList) playerRegion.getCards(); + //get Card + CardData cardData = getCard(cards, oldPos); + //detach Card + playerRegion.addCard(cardData, newPos); + sendChatMessage(game.id(), player, cardData.getName(), oldPos, String.valueOf(newPos+1), region); + //reload State + return doReload(gameName); + } + + /** + * Attach Card to another Card in a given Region + * @param gameName - Name where cards should be attached + * @param player - Player whom card shall be attached + * @param region - Region where the cards shall be attached + * @param oldPos - Old Position of the Card + * @param newPos - New Positon of the Card + * @return Map with View to Update + */ + public Map attachRegionCard(String gameName, String player, String region, String oldPos, String newPos) { + //get Game & Region & Cards + JolGame game = GameService.getGameByName(gameName); + RegionData playerRegion = game.data().getPlayerRegion(player, RegionType.valueOf(region)); + LinkedList cards = (LinkedList) playerRegion.getCards(); + //get card + CardData cardData = getCard(cards, oldPos); + //attach Card + getCard(cards, newPos).add(cardData, true); + sendChatMessage(game.id(), player, cardData.getName(), oldPos, newPos, region); + //reload State + return doReload(gameName); + } + private GameView getView(String name) { String player = getPlayer(request); return getModel(name).getView(player); @@ -420,4 +493,27 @@ private GameModel getModel(String name) { return JolAdmin.getGameModel(name); } + public Map doReload(String name) { + //reload State + getModel(name).doReload(true,false,false); + return UpdateFactory.getUpdate(); + } + + private CardData getCard(LinkedList cards, String pos) { + LinkedList targetCards = cards; + if(pos.contains(".")){ + String[] split = pos.split("\\."); + int lastCard = Integer.valueOf(split[split.length-1]); + for(int i = 0; i < split.length-1; i++) { + targetCards = targetCards.get(Integer.valueOf(split[i])-1).getCards(); + } + return targetCards.get(lastCard-1); + } else { + return cards.get(Integer.valueOf(pos)-1); + } + } + + private void sendChatMessage(String id, String playerName, String cardName, String oldPos, String newPos, String region) { + ChatService.sendMessage(id, playerName, playerName + " moves "+cardName+" from "+oldPos+" to "+newPos+" of their "+region+" region."); + } } diff --git a/src/main/java/net/deckserver/storage/json/game/RegionData.java b/src/main/java/net/deckserver/storage/json/game/RegionData.java index 6dc2cb98..39cd7bda 100644 --- a/src/main/java/net/deckserver/storage/json/game/RegionData.java +++ b/src/main/java/net/deckserver/storage/json/game/RegionData.java @@ -72,6 +72,31 @@ public void addCard(CardData card, boolean top) { card.setParent(null); card.setRegion(this); } + public void addCard(CardData card, int pos) { + // Remove from parent first, if it exists + if (card.getParent() != null) { + card.getParent().remove(card); + } + // Remove from region if it exists + if (card.getRegion() != null) { + card.getRegion().removeCard(card); + } + // Add new Card to correct position + LinkedList newCardsOrder = new LinkedList<>(); + for(int i = 0; i 0 ? " / " + capacity : ""); String attributes = cardDetail.buildAttributes(region, index, true); @@ -56,7 +57,7 @@ request.setAttribute("cards", cards); %>
  • onclick="<%= showAction %>" - class="list-group-item d-flex justify-content-between align-items-baseline px-2 pt-2 pb-1 <%= regionStyle%> <%= shadowStyle %> <%= contestedStyle %>"> + class="list-group-item d-flex justify-content-between align-items-baseline px-2 pt-2 pb-1 dropable-item <%= regionStyle%> <%= shadowStyle %> <%= contestedStyle %>">
    @@ -102,7 +103,7 @@
    -
      +
        @@ -130,4 +131,5 @@
      + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsps/game/region.jsp b/src/main/webapp/WEB-INF/jsps/game/region.jsp index ef50b609..02561d30 100755 --- a/src/main/webapp/WEB-INF/jsps/game/region.jsp +++ b/src/main/webapp/WEB-INF/jsps/game/region.jsp @@ -42,7 +42,7 @@
        + class="region sortable1 list-group list-group-flush list-group-numbered drop-zone<%= regionStyle %> collapse <%= show %>"> diff --git a/src/main/webapp/WEB-INF/main.jsp b/src/main/webapp/WEB-INF/main.jsp index 1fbd7d1f..30114b65 100755 --- a/src/main/webapp/WEB-INF/main.jsp +++ b/src/main/webapp/WEB-INF/main.jsp @@ -89,6 +89,7 @@ + diff --git a/src/main/webapp/js/ds.js b/src/main/webapp/js/ds.js index 4fb64f7a..0313e27b 100644 --- a/src/main/webapp/js/ds.js +++ b/src/main/webapp/js/ds.js @@ -1301,6 +1301,80 @@ function loadGame(data) { addCardTooltips("#hand"); } + // drag and drop + //add sortable to highest order list and connect to sublists + let startPlayer; + $(".sortable1").each(function (index, region) { + let regionName = region.id.substring(2); + if(region.id.indexOf("READY")>-1) { + $(region).sortable({ + connectWith: ".sortable1, .sortable2, .drop-zone", + handle: ".bi-grip-vertical", + dropOnEmpty: true, + start: function (event, ui) { + startPlayer = ui.item.closest(".player").attr("data-player"); + }, + stop: function (event, ui) { + let playerName = ui.item.closest(".player").attr("data-player"); + if(playerName==startPlayer) { + let newPos; + if(ui.item.parent("ol").hasClass("region")) { + ui.item.parent("ol").children("li").each(function (index, li) { + if(li===ui.item[0]) { + newPos = index; + } + }); + DS.swapCardsInRegion(data.name, playerName, regionName, ui.item.attr("data-coordinates"), newPos, {callback: processData, errorHandler: errorhandler}); + } else { + DS.attachRegionCard(data.name, playerName, regionName, ui.item.attr("data-coordinates"), ui.item.parents("li").attr("data-coordinates"), {callback: processData, errorHandler: errorhandler}); + } + } else { + DS.doReload(data.name, {callback: processData, errorHandler: errorhandler}); + } + } + }); + $(region).disableSelection(); + } + //add drag and drop to all sub lists and connect to parent list + $(".sortable2").sortable({ + handle: ".bi-grip-vertical", + dropOnEmpty: true, + connectWith: ".sortable1, .drop-zone, .sortable2 ", + start: function (event, ui) { + startPlayer = ui.item.closest(".player").attr("data-player"); + }, + stop: function (event, ui) { + let playerName = ui.item.closest(".player").attr("data-player"); + if(playerName==startPlayer) { + let newPos; + if(ui.item.parent("ol").hasClass("region")) { + ui.item.parent("ol").children("li").each(function (index, li) { + if(li===ui.item[0]) { + newPos = index; + } + }); + DS.detachRegionCard(data.name, playerName, "READY", ui.item.attr("data-coordinates"), newPos, {callback: processData, errorHandler: errorhandler}); + } else { + DS.attachRegionCard(data.name, playerName, "READY", ui.item.attr("data-coordinates"), ui.item.parents("li").attr("data-coordinates"), {callback: processData, errorHandler: errorhandler}); + } + } else { + DS.doReload(data.name, {callback: processData, errorHandler: errorhandler}); + } + } + }); + $(".sortable2").disableSelection(); + //create a drop zone + $(".drop-zone").droppable({ + accept: ".dropable-item", + }); + $(".drop-zone").sortable({ + handle: ".bi-grip-vertical", + connectWith: '.drop-zone, .sortable1, .sortable2', + }); + $(".drop-zone").disableSelection(); + + }); + // Setup polling if (refresher) clearTimeout(refresher); if (data.refresh > 0 || fetchFullLog) { diff --git a/src/main/webapp/js/jquery.ui.touch-punch.min.js b/src/main/webapp/js/jquery.ui.touch-punch.min.js new file mode 100644 index 00000000..31272ce6 --- /dev/null +++ b/src/main/webapp/js/jquery.ui.touch-punch.min.js @@ -0,0 +1,11 @@ +/*! + * jQuery UI Touch Punch 0.2.3 + * + * Copyright 2011–2014, Dave Furfero + * Dual licensed under the MIT or GPL Version 2 licenses. + * + * Depends: + * jquery.ui.widget.js + * jquery.ui.mouse.js + */ +!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery); \ No newline at end of file