diff --git a/cypress.json b/cypress.json index fe6cedc..4882bd6 100644 --- a/cypress.json +++ b/cypress.json @@ -9,7 +9,8 @@ "topbar_actions.spec.js", "map_info.spec.js", "narrator.spec.js", - "manual.spec.js" + "manual.spec.js", + "movement_drag.spec.js" ], "video": false } diff --git a/cypress/integration/core/movement.spec.js b/cypress/integration/core/movement.spec.js index f81401a..d7e5e85 100644 --- a/cypress/integration/core/movement.spec.js +++ b/cypress/integration/core/movement.spec.js @@ -51,6 +51,7 @@ context("Units movements", () => { click("#icon32a"); click("#cell62"); + click("#modal-ok"); cy.get('#icon32a[src="./src/images/board/mob/roman/1.png"]').should( "exist" @@ -63,6 +64,7 @@ context("Units movements", () => { click("#icon32a"); click("#cell33"); + click("#modal-ok"); cy.get('#icon32a[src="./src/images/board/mob/roman/1.png"]').should( "exist" diff --git a/cypress/integration/movement_drag.spec.js b/cypress/integration/movement_drag.spec.js new file mode 100644 index 0000000..0c0656d --- /dev/null +++ b/cypress/integration/movement_drag.spec.js @@ -0,0 +1,103 @@ +/// + +import { start, click, endTurn, moreStrength } from "../utils/ui.js"; + +context("Human Soldiers movements using drag and drop", () => { + it("User drags a soldier 2 steps and cannot move anymore; then next turn user drags it 2 steps again", () => { + start(); + + cy.get("#tooltip32a").drag("#cell30"); + + click('#icon30a[src="./src/images/board/mob/roman/grey/1.png"]'); + cy.get("#movement").should("contain", "Movements left: [0]"); + click("#cell32"); + + cy.get('#icon30a[src="./src/images/board/mob/roman/grey/1.png"]').should( + "exist" + ); + + endTurn(); + + cy.get("#tooltip30a").drag("#cell32"); + + cy.get('#icon32a[src="./src/images/board/mob/roman/grey/1.png"]').should( + "exist" + ); + }); + + it("User drags a soldier 1 step and then move it 1 step again; after that, it cannot be moved anymore", () => { + start(); + + cy.get("#tooltip32a").drag("#cell31"); + click('#icon31a[src="./src/images/board/mob/roman/1.png"]'); + cy.get("#movement").should("contain", "Movements left: [1]"); + + cy.get("#tooltip31a").drag("#cell30"); + click('#icon30a[src="./src/images/board/mob/roman/grey/1.png"]'); + cy.get("#movement").should("contain", "Movements left: [0]"); + + cy.get("#tooltip30a").drag("#cell31", { force: true }); + click("#modal-ok"); + cy.get('#icon30a[src="./src/images/board/mob/roman/grey/1.png"]').should( + "exist" + ); + }); + + it("User cannot drag a soldier more than 2 steps", () => { + start(); + + cy.get("#tooltip32a").drag("#cell62", { force: true }); + click("#modal-ok"); + + cy.get('#icon32a[src="./src/images/board/mob/roman/1.png"]').should( + "exist" + ); + click("#icon32a"); + cy.get("#movement").should("contain", "Movements left: [2]"); + }); + + it("User cannot drag a soldier into an obstacle", () => { + start(); + + cy.get("#tooltip32a").drag("#cell33", { force: true }); + click("#modal-ok"); + + cy.get('#icon32a[src="./src/images/board/mob/roman/1.png"]').should( + "exist" + ); + + click("#icon32a"); + cy.get("#movement").should("contain", "Movements left: [2]"); + }); + + it("User can drag a soldier after moving to next map", () => { + start(19); + + cy.get("#icon71a").should("not.exist"); + + endTurn(); + + click("#icon64a"); + moreStrength(); + moreStrength(); + + click("#cell54").then(() => { + cy.get("#modal-content").should( + "contain", + "Victory! The area is safe again." + ); + click("#modal-ok"); + + cy.get("#modal-content").should("contain", "A new map awaits you..."); + click("#modal-ok"); + + cy.get("#icon71a").should("exist"); + + endTurn(); + + cy.get("#icon54a").should("not.exist"); + cy.get("#tooltip71a").drag("#cell72"); + cy.get("#icon72a").should("exist"); + }); + }); +}); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index ca4d256..e6b370d 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -23,3 +23,5 @@ // // -- This will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) + +require("@4tw/cypress-drag-drop"); diff --git a/package-lock.json b/package-lock.json index f29cd6e..827a867 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,12 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@4tw/cypress-drag-drop": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@4tw/cypress-drag-drop/-/cypress-drag-drop-1.6.0.tgz", + "integrity": "sha512-B61iPspk2hZuuo3mjmlTqYZXJ9tusc8VyEk+5KMO/FTBrHKDWqYp8ANOJnIkRz6QfYZbx+qBoKBu7MTfvBCKew==", + "dev": true + }, "@cypress/listr-verbose-renderer": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz", diff --git a/package.json b/package.json index 8c9c29c..6dcf13c 100644 --- a/package.json +++ b/package.json @@ -22,5 +22,8 @@ "bugs": { "url": "https://github.com/W01fw00d/barbarians/issues" }, - "homepage": "https://github.com/W01fw00d/barbarians#readme" + "homepage": "https://github.com/W01fw00d/barbarians#readme", + "devDependencies": { + "@4tw/cypress-drag-drop": "^1.6.0" + } } diff --git a/src/scripts/browser/BrowserUtils.js b/src/scripts/browser/BrowserUtils.js index 9ae06cd..90a686c 100644 --- a/src/scripts/browser/BrowserUtils.js +++ b/src/scripts/browser/BrowserUtils.js @@ -4,6 +4,8 @@ function BrowserUtils() { }; this.showMessage = function (message, onClose) { + //TODO: add a flag to disable this feature + // (take into account that next map won't load without user confirming modal right now...) $("#modal-content").html(message); $("#modal").modal("show"); onClose && diff --git a/src/scripts/game/Game.js b/src/scripts/game/Game.js index 08a4458..8c2891a 100644 --- a/src/scripts/game/Game.js +++ b/src/scripts/game/Game.js @@ -80,9 +80,10 @@ Game.prototype.getUnit = function (icon) { }, annotation = icon[icon.length - 1]; - let units = this.players[unitsAnnotationCorralation[annotation][0]].units[ - unitsAnnotationCorralation[annotation][1] - ]; + let units = + this.players[unitsAnnotationCorralation[annotation][0]].units[ + unitsAnnotationCorralation[annotation][1] + ]; let unitsLength = units.length; for (i = 0; i < unitsLength; i++) { @@ -95,8 +96,6 @@ Game.prototype.getUnit = function (icon) { Game.prototype.onCellClick = function (event, unit) { const target = event.target.id; - let newMapLevel; - //if this cell has an icon and the icon represents unit or town data, show that data instead of moving unit if (target.indexOf("icon") !== -1 && this.getUnit(target)) { // showIconData.apply(targetIcon); @@ -123,6 +122,7 @@ Game.prototype.onCellClick = function (event, unit) { if (newMapLevel) { this.currentMapLevel = newMapLevel; this.map.generate(this.currentMapLevel, this.players); + this.bindDrag(); } this.resetBoardBindings(); @@ -149,6 +149,7 @@ Game.prototype.moveMode = function (unit) { if (newMapLevel) { this.currentMapLevel = newMapLevel; this.map.generate(this.currentMapLevel, this.players); + this.bindDrag(); } this.resetBoardBindings(); @@ -163,6 +164,71 @@ Game.prototype.moveMode = function (unit) { }); }; +Game.prototype.bindDrag = function () { + document.querySelectorAll(".cell").forEach((node) => { + const handleDrop = (event) => { + var data = event.dataTransfer.getData("Text"); + + const checkEncounter = (unit) => { + const target = event.target.id; + + //if this cell has an icon and the icon represents unit or town data, don't do anything + if ( + (target.indexOf("icon") === -1 || !this.getUnit(target)) && + unit.movements > 0 && + unit.cell.replace("icon", "").substring(0, 2) !== + target.replace("cell", "") + ) { + const dataElement = document.getElementById(data); + result = this.players.human.moveSoldier(unit, target); + + if (result) { + event.target.appendChild(dataElement); + + this.encounter.check(unit, this.players); + + this.levelManager.checkEndOfLevelCondition( + this.currentMapLevel, + this.players, + (newMapLevel) => { + if (newMapLevel) { + this.currentMapLevel = newMapLevel; + this.map.generate(this.currentMapLevel, this.players); + this.bindDrag(); + } + + this.resetBoardBindings(); + } + ); + } + } else { + this.browserUtils.showMessage("Invalid movement"); + } + }; + + event.preventDefault(); + + const draggedIcon = data.replace("tooltip", "icon"); + + const unit = this.infoLayer.findUnit( + draggedIcon, + this.players.human.units.mobs + ); + checkEncounter(unit); + }; + + node.addEventListener("drop", handleDrop, false); + + node.addEventListener( + "dragover", + (event) => { + event.preventDefault(); + }, + false + ); + }); +}; + Game.prototype.bindIconClick = function () { $(".icon").one("click", (event) => { event.stopPropagation(); @@ -175,6 +241,16 @@ Game.prototype.bindIconClick = function () { this.bindIconClick(); } }); + + Array.from(document.getElementsByClassName("icon-wrapper")).forEach( + (node) => { + const handleDragstart = (event) => { + event.dataTransfer.setData("Text", event.currentTarget.id); + }; + + node.addEventListener("dragstart", handleDragstart, false); + } + ); }; Game.prototype.resetBoardBindings = function () { @@ -193,6 +269,7 @@ Game.prototype.bindAll = function () { if (confirm("Reset current map?")) { this.map.generate(this.currentMapLevel, this.players); this.bindIconClick(); + this.bindDrag(); } }); @@ -201,6 +278,7 @@ Game.prototype.bindAll = function () { if (newMapLevel) { this.currentMapLevel = newMapLevel; this.map.generate(this.currentMapLevel, this.players); + this.bindDrag(); } $("#end_turn").html("End turn (+3 gold)"); @@ -257,4 +335,6 @@ Game.prototype.bindAll = function () { enableAnimationsCheckbox.addEventListener("change", ({ currentTarget }) => { this.animationManager.enableAnimations = currentTarget.checked; }); + + this.bindDrag(); }; diff --git a/src/scripts/game/InfoLayer.js b/src/scripts/game/InfoLayer.js index 9c448f5..ec9344b 100644 --- a/src/scripts/game/InfoLayer.js +++ b/src/scripts/game/InfoLayer.js @@ -9,7 +9,6 @@ function InfoLayer(painter) { mode: null, unit: null, }, - color, unit; switch (type) { @@ -90,4 +89,5 @@ function InfoLayer(painter) { return unit; }; + this.findUnit = findUnit; } diff --git a/src/scripts/players/Player.js b/src/scripts/players/Player.js index b470c77..3588d7c 100644 --- a/src/scripts/players/Player.js +++ b/src/scripts/players/Player.js @@ -26,6 +26,7 @@ Player.prototype.setGold = function (gold) { }; Player.prototype.showModal = function (message) { + //TODO: use BrowserUtils modal here $("#modal-content").html(message); $("#modal").modal("show"); }; @@ -45,7 +46,7 @@ Player.prototype.moveSoldier = function (unit, target) { Math.abs(finalCell[1] - initialCell[1]); if (icon === null && movement > 0 && movement <= unit.movements) { - //Move the soldier icon to he selected cell, and calculate movements left + // Move the soldier icon to the selected cell, and calculate movements left unit.cell = "icon" + finalCell[0] + "" + finalCell[1] + initialCell[2]; unit.movements -= movement; @@ -53,7 +54,7 @@ Player.prototype.moveSoldier = function (unit, target) { this.mapPainter.clearCell(initialCell[0], initialCell[1]); } else { - if (unit.player === "Roman") { + if (unit.player === "human") { this.showModal("Invalid movement"); result = null; } diff --git a/src/scripts/units/IconTemplates.js b/src/scripts/units/IconTemplates.js index 583f9ac..d9292f9 100644 --- a/src/scripts/units/IconTemplates.js +++ b/src/scripts/units/IconTemplates.js @@ -1,25 +1,25 @@ //TODO all this should probably go into browser/ function IconTemplates() {} -IconTemplates.prototype.getBase = function (id, title, img) { - return ( - '' + - '' - ); -}; +IconTemplates.prototype.getBase = (id, title, img, draggable = false) => + `` + + `` + + ``; -IconTemplates.prototype.getMob = function (id, name, img, extraTitle = "") { +IconTemplates.prototype.getMob = function ( + id, + name, + img, + extraTitle = "", + draggable = false +) { const title = `[${name}]${extraTitle}`; - return this.getBase(id, title, `mob/${img}`); + return this.getBase(id, title, `mob/${img}`, draggable); }; IconTemplates.prototype.getStarterAIMob = function (id, name) { @@ -47,11 +47,14 @@ IconTemplates.prototype.getSoldierIconByStrength = function (strength) { }; IconTemplates.prototype.getHumanMob = function (id, name, movements, strength) { + const draggable = true; + return this.getMob( id, name, `roman/${this.getSoldierIconByStrength(strength)}`, - ` | Moves: [${movements}] | Strength: [${strength}]` + ` | Moves: [${movements}] | Strength: [${strength}]`, + draggable ); };