diff --git a/.gitignore b/.gitignore index 7a367038..10c8ebc7 100755 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store node_modules/ dist/ +.firebase npm-debug.log test/unit/coverage .idea diff --git a/public/static/cardImages/BatteryBackup.png b/public/static/cardImages/BatteryBackup.png deleted file mode 100644 index 1fdea5a6..00000000 Binary files a/public/static/cardImages/BatteryBackup.png and /dev/null differ diff --git a/public/static/cardImages/Generator.png b/public/static/cardImages/Generator.png deleted file mode 100644 index 12c3eb7b..00000000 Binary files a/public/static/cardImages/Generator.png and /dev/null differ diff --git a/public/static/cardImages/Hacker.png b/public/static/cardImages/Hacker.png deleted file mode 100644 index 7b0e0e51..00000000 Binary files a/public/static/cardImages/Hacker.png and /dev/null differ diff --git a/public/static/cardImages/Malware.png b/public/static/cardImages/Malware.png deleted file mode 100644 index 28325c6b..00000000 Binary files a/public/static/cardImages/Malware.png and /dev/null differ diff --git a/public/static/cardImages/OverClock.png b/public/static/cardImages/OverClock.png deleted file mode 100644 index 73fa17c7..00000000 Binary files a/public/static/cardImages/OverClock.png and /dev/null differ diff --git a/public/static/cardImages/PowerOutage.png b/public/static/cardImages/PowerOutage.png deleted file mode 100644 index 6524cef4..00000000 Binary files a/public/static/cardImages/PowerOutage.png and /dev/null differ diff --git a/public/static/cardImages/AntiVirus.png b/public/static/cardImages/antivirus0.png similarity index 100% rename from public/static/cardImages/AntiVirus.png rename to public/static/cardImages/antivirus0.png diff --git a/public/static/cardImages/backOfCard.png.old b/public/static/cardImages/backOfCard.png.old deleted file mode 100644 index 23f6516d..00000000 Binary files a/public/static/cardImages/backOfCard.png.old and /dev/null differ diff --git a/public/static/cardImages/effects/ANTIVIRUS.png b/public/static/cardImages/effects/ANTIVIRUS.png index 0dffd76a..a0a15f37 100644 Binary files a/public/static/cardImages/effects/ANTIVIRUS.png and b/public/static/cardImages/effects/ANTIVIRUS.png differ diff --git a/public/static/cardImages/effects/BATTERYBACKUP.png b/public/static/cardImages/effects/BATTERYBACKUP.png deleted file mode 100644 index 385435af..00000000 Binary files a/public/static/cardImages/effects/BATTERYBACKUP.png and /dev/null differ diff --git a/public/static/cardImages/effects/DISCARD.png b/public/static/cardImages/effects/DISCARD.png new file mode 100644 index 00000000..47c16f6c Binary files /dev/null and b/public/static/cardImages/effects/DISCARD.png differ diff --git a/public/static/cardImages/effects/FIREWALL.png b/public/static/cardImages/effects/FIREWALL.png index f61e178f..8a58c2bc 100644 Binary files a/public/static/cardImages/effects/FIREWALL.png and b/public/static/cardImages/effects/FIREWALL.png differ diff --git a/public/static/cardImages/effects/GENERATOR.png b/public/static/cardImages/effects/GENERATOR.png deleted file mode 100644 index aefe8b53..00000000 Binary files a/public/static/cardImages/effects/GENERATOR.png and /dev/null differ diff --git a/public/static/cardImages/effects/GROUP2.png b/public/static/cardImages/effects/GROUP2.png new file mode 100644 index 00000000..1d0346e3 Binary files /dev/null and b/public/static/cardImages/effects/GROUP2.png differ diff --git a/public/static/cardImages/effects/GROUP3.png b/public/static/cardImages/effects/GROUP3.png new file mode 100644 index 00000000..5e7611d8 Binary files /dev/null and b/public/static/cardImages/effects/GROUP3.png differ diff --git a/public/static/cardImages/effects/GROUP4.png b/public/static/cardImages/effects/GROUP4.png new file mode 100644 index 00000000..b401a98c Binary files /dev/null and b/public/static/cardImages/effects/GROUP4.png differ diff --git a/public/static/cardImages/effects/GROUP5.png b/public/static/cardImages/effects/GROUP5.png new file mode 100644 index 00000000..78871595 Binary files /dev/null and b/public/static/cardImages/effects/GROUP5.png differ diff --git a/public/static/cardImages/effects/GROUP6.png b/public/static/cardImages/effects/GROUP6.png new file mode 100644 index 00000000..c549ef66 Binary files /dev/null and b/public/static/cardImages/effects/GROUP6.png differ diff --git a/public/static/cardImages/effects/HACK.png b/public/static/cardImages/effects/HACK.png deleted file mode 100644 index 9781e31e..00000000 Binary files a/public/static/cardImages/effects/HACK.png and /dev/null differ diff --git a/public/static/cardImages/effects/INSTRUCTION1.png b/public/static/cardImages/effects/INSTRUCTION1.png new file mode 100644 index 00000000..dadc6df4 Binary files /dev/null and b/public/static/cardImages/effects/INSTRUCTION1.png differ diff --git a/public/static/cardImages/effects/INSTRUCTION2.png b/public/static/cardImages/effects/INSTRUCTION2.png new file mode 100644 index 00000000..02c54795 Binary files /dev/null and b/public/static/cardImages/effects/INSTRUCTION2.png differ diff --git a/public/static/cardImages/effects/INSTRUCTION3.png b/public/static/cardImages/effects/INSTRUCTION3.png new file mode 100644 index 00000000..216e5551 Binary files /dev/null and b/public/static/cardImages/effects/INSTRUCTION3.png differ diff --git a/public/static/cardImages/effects/OVERCLOCK.png b/public/static/cardImages/effects/OVERCLOCK.png deleted file mode 100644 index a9390015..00000000 Binary files a/public/static/cardImages/effects/OVERCLOCK.png and /dev/null differ diff --git a/public/static/cardImages/effects/POWEROUTAGE.png b/public/static/cardImages/effects/POWEROUTAGE.png deleted file mode 100644 index 06af791b..00000000 Binary files a/public/static/cardImages/effects/POWEROUTAGE.png and /dev/null differ diff --git a/public/static/cardImages/effects/RANSOM.png b/public/static/cardImages/effects/RANSOM.png new file mode 100644 index 00000000..f759b48e Binary files /dev/null and b/public/static/cardImages/effects/RANSOM.png differ diff --git a/public/static/cardImages/effects/REDRAW.png b/public/static/cardImages/effects/REDRAW.png new file mode 100644 index 00000000..0fd39cdd Binary files /dev/null and b/public/static/cardImages/effects/REDRAW.png differ diff --git a/public/static/cardImages/effects/REPEAT1.png b/public/static/cardImages/effects/REPEAT1.png new file mode 100644 index 00000000..622af055 Binary files /dev/null and b/public/static/cardImages/effects/REPEAT1.png differ diff --git a/public/static/cardImages/effects/REPEAT2.png b/public/static/cardImages/effects/REPEAT2.png new file mode 100644 index 00000000..25b64e80 Binary files /dev/null and b/public/static/cardImages/effects/REPEAT2.png differ diff --git a/public/static/cardImages/effects/REPEAT3.png b/public/static/cardImages/effects/REPEAT3.png new file mode 100644 index 00000000..779f81e9 Binary files /dev/null and b/public/static/cardImages/effects/REPEAT3.png differ diff --git a/public/static/cardImages/effects/REPEAT4.png b/public/static/cardImages/effects/REPEAT4.png new file mode 100644 index 00000000..cfbcf8a2 Binary files /dev/null and b/public/static/cardImages/effects/REPEAT4.png differ diff --git a/public/static/cardImages/effects/SCAN.png b/public/static/cardImages/effects/SCAN.png new file mode 100644 index 00000000..701f9b8e Binary files /dev/null and b/public/static/cardImages/effects/SCAN.png differ diff --git a/public/static/cardImages/effects/SPYWARE.png b/public/static/cardImages/effects/SPYWARE.png new file mode 100644 index 00000000..53cb5c71 Binary files /dev/null and b/public/static/cardImages/effects/SPYWARE.png differ diff --git a/public/static/cardImages/effects/TROJAN.png b/public/static/cardImages/effects/TROJAN.png new file mode 100644 index 00000000..31e97c1f Binary files /dev/null and b/public/static/cardImages/effects/TROJAN.png differ diff --git a/public/static/cardImages/effects/VARIABLE3.png b/public/static/cardImages/effects/VARIABLE3.png new file mode 100644 index 00000000..f1e1201a Binary files /dev/null and b/public/static/cardImages/effects/VARIABLE3.png differ diff --git a/public/static/cardImages/effects/VARIABLE4.png b/public/static/cardImages/effects/VARIABLE4.png new file mode 100644 index 00000000..2e7564e5 Binary files /dev/null and b/public/static/cardImages/effects/VARIABLE4.png differ diff --git a/public/static/cardImages/effects/VARIABLE5.png b/public/static/cardImages/effects/VARIABLE5.png new file mode 100644 index 00000000..9f9a8e3c Binary files /dev/null and b/public/static/cardImages/effects/VARIABLE5.png differ diff --git a/public/static/cardImages/effects/VARIABLE6.png b/public/static/cardImages/effects/VARIABLE6.png new file mode 100644 index 00000000..4652b77e Binary files /dev/null and b/public/static/cardImages/effects/VARIABLE6.png differ diff --git a/public/static/cardImages/effects/VIRUS.png b/public/static/cardImages/effects/VIRUS.png index 55652342..4bb06189 100644 Binary files a/public/static/cardImages/effects/VIRUS.png and b/public/static/cardImages/effects/VIRUS.png differ diff --git a/public/static/cardImages/Firewall.png b/public/static/cardImages/firewall0.png similarity index 100% rename from public/static/cardImages/Firewall.png rename to public/static/cardImages/firewall0.png diff --git a/public/static/cardImages/Group2.png b/public/static/cardImages/group2.png similarity index 100% rename from public/static/cardImages/Group2.png rename to public/static/cardImages/group2.png diff --git a/public/static/cardImages/Group3.png b/public/static/cardImages/group3.png similarity index 100% rename from public/static/cardImages/Group3.png rename to public/static/cardImages/group3.png diff --git a/public/static/cardImages/Group4.png b/public/static/cardImages/group4.png similarity index 100% rename from public/static/cardImages/Group4.png rename to public/static/cardImages/group4.png diff --git a/public/static/cardImages/Group5.png b/public/static/cardImages/group5.png similarity index 100% rename from public/static/cardImages/Group5.png rename to public/static/cardImages/group5.png diff --git a/public/static/cardImages/Group6.png b/public/static/cardImages/group6.png similarity index 100% rename from public/static/cardImages/Group6.png rename to public/static/cardImages/group6.png diff --git a/public/static/cardImages/Instruction1.png b/public/static/cardImages/instruction1.png similarity index 100% rename from public/static/cardImages/Instruction1.png rename to public/static/cardImages/instruction1.png diff --git a/public/static/cardImages/Instruction2.png b/public/static/cardImages/instruction2.png similarity index 100% rename from public/static/cardImages/Instruction2.png rename to public/static/cardImages/instruction2.png diff --git a/public/static/cardImages/Instruction3.png b/public/static/cardImages/instruction3.png similarity index 100% rename from public/static/cardImages/Instruction3.png rename to public/static/cardImages/instruction3.png diff --git a/public/static/cardImages/ransom0.png b/public/static/cardImages/ransom0.png new file mode 100644 index 00000000..8d418d52 Binary files /dev/null and b/public/static/cardImages/ransom0.png differ diff --git a/public/static/cardImages/RepetitionX.png b/public/static/cardImages/repeat1.png similarity index 100% rename from public/static/cardImages/RepetitionX.png rename to public/static/cardImages/repeat1.png diff --git a/public/static/cardImages/Repetition2.png b/public/static/cardImages/repeat2.png similarity index 100% rename from public/static/cardImages/Repetition2.png rename to public/static/cardImages/repeat2.png diff --git a/public/static/cardImages/Repetition3.png b/public/static/cardImages/repeat3.png similarity index 100% rename from public/static/cardImages/Repetition3.png rename to public/static/cardImages/repeat3.png diff --git a/public/static/cardImages/Repetition4.png b/public/static/cardImages/repeat4.png similarity index 100% rename from public/static/cardImages/Repetition4.png rename to public/static/cardImages/repeat4.png diff --git a/public/static/cardImages/scan0.png b/public/static/cardImages/scan0.png new file mode 100644 index 00000000..b85ecdfc Binary files /dev/null and b/public/static/cardImages/scan0.png differ diff --git a/public/static/cardImages/spyware0.png b/public/static/cardImages/spyware0.png new file mode 100644 index 00000000..8d80f7cf Binary files /dev/null and b/public/static/cardImages/spyware0.png differ diff --git a/public/static/cardImages/trojan0.png b/public/static/cardImages/trojan0.png new file mode 100644 index 00000000..18189c94 Binary files /dev/null and b/public/static/cardImages/trojan0.png differ diff --git a/public/static/cardImages/Variable3.png b/public/static/cardImages/variable3.png similarity index 100% rename from public/static/cardImages/Variable3.png rename to public/static/cardImages/variable3.png diff --git a/public/static/cardImages/Variable4.png b/public/static/cardImages/variable4.png similarity index 100% rename from public/static/cardImages/Variable4.png rename to public/static/cardImages/variable4.png diff --git a/public/static/cardImages/Variable5.png b/public/static/cardImages/variable5.png similarity index 100% rename from public/static/cardImages/Variable5.png rename to public/static/cardImages/variable5.png diff --git a/public/static/cardImages/Variable6.png b/public/static/cardImages/variable6.png similarity index 100% rename from public/static/cardImages/Variable6.png rename to public/static/cardImages/variable6.png diff --git a/public/static/cardImages/virus0.png b/public/static/cardImages/virus0.png new file mode 100644 index 00000000..6c432dbf Binary files /dev/null and b/public/static/cardImages/virus0.png differ diff --git a/public/static/miscIcons/arrow.png b/public/static/miscIcons/arrow.png new file mode 100644 index 00000000..e30990d6 Binary files /dev/null and b/public/static/miscIcons/arrow.png differ diff --git a/public/static/miscIcons/noIcon.png b/public/static/miscIcons/noIcon.png new file mode 100644 index 00000000..37e87574 Binary files /dev/null and b/public/static/miscIcons/noIcon.png differ diff --git a/public/static/playerImages/player0.png b/public/static/playerImages/player0.png new file mode 100644 index 00000000..06832578 Binary files /dev/null and b/public/static/playerImages/player0.png differ diff --git a/public/static/playerImages/player1.png b/public/static/playerImages/player1.png new file mode 100644 index 00000000..acc68008 Binary files /dev/null and b/public/static/playerImages/player1.png differ diff --git a/public/static/playerImages/player2.png b/public/static/playerImages/player2.png new file mode 100644 index 00000000..a19b4332 Binary files /dev/null and b/public/static/playerImages/player2.png differ diff --git a/public/static/playerImages/player3.png b/public/static/playerImages/player3.png new file mode 100644 index 00000000..49641208 Binary files /dev/null and b/public/static/playerImages/player3.png differ diff --git a/public/static/playerImages/player4.png b/public/static/playerImages/player4.png new file mode 100644 index 00000000..b54482bf Binary files /dev/null and b/public/static/playerImages/player4.png differ diff --git a/public/static/playerImages/player5.png b/public/static/playerImages/player5.png new file mode 100644 index 00000000..2830ff8c Binary files /dev/null and b/public/static/playerImages/player5.png differ diff --git a/public/static/playerImages/player6.png b/public/static/playerImages/player6.png new file mode 100644 index 00000000..4dba0f89 Binary files /dev/null and b/public/static/playerImages/player6.png differ diff --git a/public/static/playerImages/player7.png b/public/static/playerImages/player7.png new file mode 100644 index 00000000..ced6728c Binary files /dev/null and b/public/static/playerImages/player7.png differ diff --git a/public/static/playerImages/robo_0.jpg b/public/static/playerImages/robo_0.jpg deleted file mode 100644 index e5184306..00000000 Binary files a/public/static/playerImages/robo_0.jpg and /dev/null differ diff --git a/public/static/playerImages/robo_1.jpg b/public/static/playerImages/robo_1.jpg deleted file mode 100644 index 142d8f12..00000000 Binary files a/public/static/playerImages/robo_1.jpg and /dev/null differ diff --git a/public/static/playerImages/robo_2.jpg b/public/static/playerImages/robo_2.jpg deleted file mode 100644 index b76ea462..00000000 Binary files a/public/static/playerImages/robo_2.jpg and /dev/null differ diff --git a/public/static/playerImages/robo_3.jpg b/public/static/playerImages/robo_3.jpg deleted file mode 100644 index 3d7210cf..00000000 Binary files a/public/static/playerImages/robo_3.jpg and /dev/null differ diff --git a/src/classes/ai/AiHandler.js b/src/classes/ai/AiHandler.js index 3b090981..c8577135 100644 --- a/src/classes/ai/AiHandler.js +++ b/src/classes/ai/AiHandler.js @@ -38,9 +38,16 @@ export default class AiHandler { chooseAction(hand, players, stacks, scores) { // Try all the action handlers until one returns a turn object for (let action of this.actionHandlers) { - let result = action.handle(hand, players, stacks, scores) - if (result) { - return result + try { + let result = action.handle(hand, players, stacks, scores) + if (result) { + return result + } + + // Ensure that a component error does not stop the AI turn + } catch (err) { + console.log("AI handler error:", err) // eslint-disable-line + continue } } // If no handler can handle this action use the default action diff --git a/src/classes/ai/AiHandlerFactory.js b/src/classes/ai/AiHandlerFactory.js index 34d311bf..6d338141 100644 --- a/src/classes/ai/AiHandlerFactory.js +++ b/src/classes/ai/AiHandlerFactory.js @@ -12,15 +12,15 @@ const CARD_ORDER = { basic: ["VARIABLE", "REPEAT", "INSTRUCTION"], standard: [ "GROUP", "VARIABLE", "REPEAT", "INSTRUCTION", "ANTIVIRUS", "FIREWALL", - "OVERCLOCK", "HACK", "VIRUS" + "OVERCLOCK", "RANSOM", "TROJAN", "VIRUS" ], aggressive: [ - "HACK", "VIRUS", "VARIABLE", "REPEAT", "INSTRUCTION", "GROUP", + "VIRUS", "RANSOM", "TROJAN", "VARIABLE", "REPEAT", "INSTRUCTION", "GROUP", "FIREWALL", "ANTIVIRUS", "OVERCLOCK" ], defensive: [ "FIREWALL", "ANTIVIRUS", "OVERCLOCK", "GROUP", "VARIABLE", "REPEAT", - "INSTRUCTION", "VIRUS", "HACK" + "INSTRUCTION", "TROJAN", "VIRUS", "RANSOM" ] } diff --git a/src/classes/ai/PlayBestCardAction.js b/src/classes/ai/PlayBestCardAction.js index e4c9598f..f545ceee 100644 --- a/src/classes/ai/PlayBestCardAction.js +++ b/src/classes/ai/PlayBestCardAction.js @@ -156,24 +156,24 @@ export default class PlayBestCardAction extends ActionHandler { } /** - * Hack another players stack under specific conditions. - * Will not hack single card stacks as this is a waste. Picks the biggest + * play a virus on another players stack under specific conditions. + * Will not attack single card stacks as this is a waste. Picks the biggest * stack available that meets the criteria. * @param card The card to attempt to play. * @param state an object with all the state needed to make a decision * @return a move object for hacking a stack, or undefined if - * no stack can be hacked. + * no stack can be attacked. */ - hack (card, state) { + virus (card, state) { let stack = state.stacks.filter((s) => { - return s.playerId !== this.player.id && s.cards.length > 1 && s.isHackable() + return s.playerId !== this.player.id && s.cards.length > 1 }).sort((a, b) => { return b.getScore() - a.getScore() }).shift() if (stack) { return { - playType: 'hackStack', + playType: 'playCardOnStack', card: card, player: this.player, target: stack @@ -238,6 +238,7 @@ export default class PlayBestCardAction extends ActionHandler { let groupable = state.stacks.filter((s) => { // don't group single group cards with the same value as card return s.playerId === this.player.id && s.getScore() <= card.value + && s.getTop().type !== 'VIRUS' }) if (groupable.length == 0) { return undefined } diff --git a/src/classes/game/Card.js b/src/classes/game/Card.js index 4c352439..ef1ddf3a 100755 --- a/src/classes/game/Card.js +++ b/src/classes/game/Card.js @@ -3,6 +3,9 @@ * @author Steve modified on 2020-05-25 */ +// Function to create a unique object id +const uuidV1 = require('uuid/v1') + /** * A Playing card. */ @@ -10,35 +13,39 @@ export default class Card { /** * Constructor for the Card class * @constructor Card - * @param {int} id The ID of the card * @param {int} value The value of the card * @param {char} type The type of the card - * @param {char} image is a string pointing to the image of the card */ - constructor (id, value, type, image) { - this.id = id + constructor (type, value) { this.value = value this.type = type - this.image = image + this.id = uuidV1() + this.image = 'static/cardImages/' + type.toLowerCase() + value + '.png' + this.isExtra = false + this.isMimic = false } /** - * Checks if this card is an attack card. + * Checks if this card is an attack card that is played with a popup and + * not placed. */ isAttack () { - return this.type === "VIRUS" + return this.type === "RANSOM" || this.type === "SPYWARE" + || this.type === "TROJAN" } /** - * Checks if this card is a safety or remedy card + * Checks if this card is a safety or remedy card that is played with a + * popup and not placed. */ isSafety () { - return this.type === "OVERCLOCK" - || this.type === "FIREWALL" || this.type === "ANTIVIRUS" + return this.type === "SCAN" || this.type === "FIREWALL" + || this.type === "ANTIVIRUS" } /** - * Checks if this card is an attack or safety card. + * Checks if this card is an attack or safety card that is played with + * a popup and not placed. */ isSpecial () { return this.isSafety() || this.isAttack() diff --git a/src/classes/game/CyberEffect.js b/src/classes/game/CyberEffect.js new file mode 100644 index 00000000..8d3fdaa2 --- /dev/null +++ b/src/classes/game/CyberEffect.js @@ -0,0 +1,42 @@ +/** + * @file CyberEffect.js file + * @author Steve on 2020-06-18 + */ + +// Function to create a unique object id +const uuidV1 = require('uuid/v1') + +/** + * An effect for threat prevention or cyber attack on a player. + */ +export default class CyberEffect { + /** + * Constructor for the CyberEffect class. + * @constructor CyberEffect + */ + constructor (type, targetId, attackerId = undefined) { + this.id = uuidV1() + this.type = type + this.targetId = targetId + this.attackerId = attackerId + this.turnsLeft = undefined + if (type === "SPYWARE") { + this.turnsLeft = 2 + } + this.image = 'static/cardImages/effects/' + type + '.png' + } + + /** + * Decrements turnsLeft and returns the result + */ + takeTurn () { + if (this.turnsLeft === 0) { + return 0 + } else if (this.turnsLeft === undefined) { + return 9999 + } + this.turnsLeft -= 1 + return this.turnsLeft + } +} + diff --git a/src/classes/game/Deck.js b/src/classes/game/Deck.js index d09af140..60b68f62 100755 --- a/src/classes/game/Deck.js +++ b/src/classes/game/Deck.js @@ -5,68 +5,28 @@ import Card from './Card' +// card types along with {value: numCard} pairs for each +const cardTypes = { + "INSTRUCTION": {1: 9, 2: 12, 3: 9}, + "GROUP": {2: 1, 3: 2, 4: 3, 5: 2, 6: 1}, + "REPEAT": {1: 5, 2: 3, 3: 5, 4: 3}, + "VARIABLE": {3: 2, 4: 2, 5: 2, 6: 1}, + "VIRUS": {0: 3}, + "RANSOM": {0: 3}, + "SPYWARE": {0: 3}, + "TROJAN": {0: 3}, + "ANTIVIRUS": {0: 1}, + "FIREWALL": {0: 2}, + "SCAN": {0: 5}, +} -// Constants to determine how many of a card type and value to add to the deck - -const instruction1 = 9 -const instruction2 = 9 -const instruction3 = 9 - -const group2 = 1 -const group3 = 2 -const group4 = 3 -const group5 = 2 -const group6 = 1 - -const repetition2 = 3 -const repetition3 = 3 -const repetition4 = 3 -const repetitionX = 5 - -const variable3 = 2 -const variable4 = 2 -const variable5 = 2 -const variable6 = 1 - -const hack = 3 -const malware = 3 - -const overClock = 3 - -const antiVirus = 1 -const firewall = 1 - -// A list of object of card information used to setup the deck. -const cardDeck = [ - {type: 'INSTRUCTION', cardValue: 1, imgSrc: 'static/cardImages/Instruction1.png', howMany: instruction1}, - {type: 'INSTRUCTION', cardValue: 2, imgSrc: 'static/cardImages/Instruction2.png', howMany: instruction2}, - {type: 'INSTRUCTION', cardValue: 3, imgSrc: 'static/cardImages/Instruction3.png', howMany: instruction3}, - - {type: 'REPEAT', cardValue: 2, imgSrc: 'static/cardImages/Repetition2.png', howMany: repetition2}, - {type: 'REPEAT', cardValue: 3, imgSrc: 'static/cardImages/Repetition3.png', howMany: repetition3}, - {type: 'REPEAT', cardValue: 4, imgSrc: 'static/cardImages/Repetition4.png', howMany: repetition4}, - {type: 'REPEAT', cardValue: 1, imgSrc: 'static/cardImages/RepetitionX.png', howMany: repetitionX}, - - {type: 'GROUP', cardValue: 2, imgSrc: 'static/cardImages/Group2.png', howMany: group2}, - {type: 'GROUP', cardValue: 3, imgSrc: 'static/cardImages/Group3.png', howMany: group3}, - {type: 'GROUP', cardValue: 4, imgSrc: 'static/cardImages/Group4.png', howMany: group4}, - {type: 'GROUP', cardValue: 5, imgSrc: 'static/cardImages/Group5.png', howMany: group5}, - {type: 'GROUP', cardValue: 6, imgSrc: 'static/cardImages/Group6.png', howMany: group6}, - - {type: 'VARIABLE', cardValue: 3, imgSrc: 'static/cardImages/Variable3.png', howMany: variable3}, - {type: 'VARIABLE', cardValue: 4, imgSrc: 'static/cardImages/Variable4.png', howMany: variable4}, - {type: 'VARIABLE', cardValue: 5, imgSrc: 'static/cardImages/Variable5.png', howMany: variable5}, - {type: 'VARIABLE', cardValue: 6, imgSrc: 'static/cardImages/Variable6.png', howMany: variable6}, - - {type: 'HACK', cardValue: 0, imgSrc: 'static/cardImages/Hacker.png', howMany: hack}, - {type: 'VIRUS', cardValue: 0, imgSrc: 'static/cardImages/Malware.png', howMany: malware}, - - {type: 'OVERCLOCK', cardValue: 0, imgSrc: 'static/cardImages/OverClock.png', howMany: overClock}, - - {type: 'FIREWALL', cardValue: 0, imgSrc: 'static/cardImages/Firewall.png', howMany: firewall}, - {type: 'ANTIVIRUS', cardValue: 0, imgSrc: 'static/cardImages/AntiVirus.png', howMany: antiVirus} +// cards to add in when the deck is refreshed +const refreshCards = { + "GROUP": {4: 1, 5: 1, 6: 1}, + "REPEAT": {1: 2, 3: 2, 4: 2}, + "VARIABLE": {4: 1, 5: 1, 6: 1}, +} -] /** * A deck for a program wars game. @@ -76,42 +36,29 @@ const cardDeck = [ export default class Deck { /** * Constructor for the Deck class. - * @param {int} numPlayers The number of players using the deck. */ - constructor (numPlayers) { + constructor () { this.cards = [] this.discard = [] - this.initDeck(numPlayers) + this.addCards(cardTypes, 4) } /** * Initializes the deck with a pre determined number and type of cards. * Shuffles the deck. - * @param {int} numPlayers The number of players using the deck. */ - initDeck (numPlayers) { - let cardId = 0 - for (let k = 0; k < numPlayers; k++) { - for (let i = 0; i < cardDeck.length; i++) { - for (let j = 0; j < cardDeck[i].howMany; j++) { - if (cardDeck[i].type === 'FIREWALL' && (k === 1 || k === 3)) { - this.cards.push(new Card(cardId, cardDeck[i].cardValue, - cardDeck[i].type, cardDeck[i].imgSrc)) - cardId++ - } else if (cardDeck[i].type === 'ANTIVIRUS' && (k === 1 || k === 3)) { - this.cards.push(new Card(cardId, cardDeck[i].cardValue, - cardDeck[i].type, cardDeck[i].imgSrc)) - cardId++ - } else if (cardDeck[i].type !== 'ANTIVIRUS' - && cardDeck[i].type !== 'FIREWALL') { - this.cards.push(new Card(cardId, cardDeck[i].cardValue, - cardDeck[i].type, cardDeck[i].imgSrc)) - cardId++ - } + addCards (cardsToAdd, shuffles) { + for (let [type, values] of Object.entries(cardsToAdd)) { + for (let [value, number] of Object.entries(values)) { + for (let i = 0; i < number; i++) { + this.cards.push(new Card(type, parseInt(value))) } } } - this.shuffle(this.cards) + // Shuffle a few times to try and get a good random order + for (let i = 0; i < shuffles; i++) { + this.shuffle(this.cards) + } } /** @@ -140,10 +87,16 @@ export default class Deck { /** * Refreshes the deck by adding back the discard pile and shuffling. + * Also, adds some more group, variable, and repeat cards to keep the game + * moving. Especially in 4 player games these cards are moslty used up by + * the time the deck runs out, so we add some more in to ensure players + * can still play. */ refresh () { this.cards = this.cards.concat(this.discard) this.discard = [] - this.shuffle(this.cards) + if (this.cards.length < 80) { + this.addCards(refreshCards) + } } } diff --git a/src/classes/game/Objectives.js b/src/classes/game/Objectives.js index 20cd8a26..1cab879c 100644 --- a/src/classes/game/Objectives.js +++ b/src/classes/game/Objectives.js @@ -5,14 +5,15 @@ // Bonuses for each card played const GROUP_BONUS = 5 -const REPEAT_BONUS = 2 -const VAR_BONUS = 3 -const SAFETY_BONUS = 10 +const REPEAT_BONUS = 3 +const VAR_BONUS = 2 +const SAFETY_BONUS = 3 // Objective Bonuses -const DEFENSIVE_BONUS = 10 +const ANTIVIRUS_BONUS = 10 +const FIREWALL_BONUS = 5 const CLEAN_BONUS = 10 -const COMPLETE_BONUS = 10 +const COMPLETE_BONUS = 5 /** @@ -55,7 +56,7 @@ export default class Objectives { */ getSafetyBonus () { let safetyCards = this.cardsPlayed.filter((c) => { - return c.type === "ANTIVIRUS" || c.type === "FIREWALL" + return c.type === "ANTIVIRUS" || c.type === "FIREWALL" || c.type === "SCAN" }) return SAFETY_BONUS * safetyCards.length } @@ -66,8 +67,10 @@ export default class Objectives { * Is 0 if the player does not meet the requirements for the bonus. */ getDefensiveBonus () { - if (this.player.helpedBy("ANTIVIRUS") && this.player.helpedBy("FIREWALL")) { - return DEFENSIVE_BONUS + if (this.player.helpedBy("ANTIVIRUS")) { + return ANTIVIRUS_BONUS + } else if (this.player.hasPositive('FIREWALL')) { + return FIREWALL_BONUS } return 0 } @@ -77,20 +80,34 @@ export default class Objectives { * Clean bonus is given if the player has no Virus. * Is 0 if the player does not meet the requirements for the bonus. */ - getCleanBonus () { - return this.player.hurtBy("VIRUS") ? 0 : CLEAN_BONUS + getCleanBonus (playerHand, playerStacks) { + if(this.player.hurtBy('RANSOM')|| this.player.hurtBy('SPYWARE')){ + return 0 + } + // check for viruses + for (let stack of playerStacks) { + if (stack.getTop().type === 'VIRUS') { + return 0 + } + } + // check for trojans + for (let card of playerHand.cards) { + if (card.isMimic) { + return 0 + } + } + return CLEAN_BONUS } /** - * Return the bonus given if a player has at least one complete stack. + * Return a bonus for each stack that a player has with 2 repeats (Rx has to + * have variable on it). * Is 0 if the player has no complete stacks. * @param playerStacks An array of the player's stacks */ getCompleteBonus (playerStacks) { - if (playerStacks.filter(s => s.isComplete()).length > 0) { - return COMPLETE_BONUS - } - return 0 + let complete = playerStacks.filter(s => s.isComplete()) + return complete.length * COMPLETE_BONUS } /** @@ -98,13 +115,13 @@ export default class Objectives { * Requires the stacks of the player for complete program bonus. * @param stacks An array of the player's stacks */ - getBonuses (stacks) { + getBonuses (hand, stacks) { let bonuses = {} bonuses.group = this.player.objectives.getGroupBonus() bonuses.repeat = this.player.objectives.getRepeatBonus() bonuses.variable = this.player.objectives.getVariableBonus() bonuses.safety = this.player.objectives.getSafetyBonus() - bonuses.clean = this.player.objectives.getCleanBonus() + bonuses.clean = this.player.objectives.getCleanBonus(hand, stacks) bonuses.defensive = this.player.objectives.getDefensiveBonus() bonuses.complete = this.getCompleteBonus(stacks) diff --git a/src/classes/game/Player.js b/src/classes/game/Player.js index 67d5358e..f5962a35 100755 --- a/src/classes/game/Player.js +++ b/src/classes/game/Player.js @@ -4,6 +4,7 @@ */ import Objectives from '@/classes/game/Objectives' +import CyberEffect from '@/classes/game/CyberEffect' /** * A player in the game. @@ -19,67 +20,125 @@ export default class Player { constructor (id, name, isAi) { this.id = id this.name = name - this.positiveEffects = new Set() - this.negativeEffects = new Set() - this.objectives = new Objectives(this) this.isAi = isAi + this.positiveEffects = [] + this.negativeEffects = [] + this.objectives = new Objectives(this) + this.image = 'static/playerImages/player' + id + '.png' } /** - * Checks to see if the player has a positive effect. - * @param {string} effect The effect to check for. + * Checks to see if the player has the given positive effect type specificaly. */ - helpedBy (effect) { - if (effect === "OVERCLOCK" && this.positiveEffects.has("ANTIVIRUS")) { + hasPositive (type) { + return this.positiveEffects.find(e => e.type === type) !== undefined + } + + /** + * Checks to see if the player is helped by a positive effect. + * Some effects offer similar protection and will return true from this when + * they would return false from hasPositive. + * eg) A player that has ANTIVIRUS is helped by FIREWALL as antivirus protects + * against the same effects as FIREWALL plus some. + * @param {string} type The effect type to check for. + */ + helpedBy (type) { + if ((type === "SCAN" || type === "FIREWALL") && this.helpedBy('ANTIVIRUS')) { return true } - return this.positiveEffects.has(effect) + return this.hasPositive(type) } /** - * Check to see if a player has a negative effect. - * @param {string} effect The effect to check for. + * Checks to see if the player has the given negative effect type specificaly. */ - hurtBy (effect) { - return this.negativeEffects.has(effect) + hasNegative (type) { + return this.negativeEffects.find(e => e.type === type) !== undefined } + /** + * Check to see if a player is hurt by a negative effect. + * @param {string} type The effect type to check for. + */ + hurtBy (type) { + return this.hasNegative(type) + } + + /** * Find out if a player is protected from an effect. * @param {string} effect The effect to check. * @return true if the player is protected, false otherwise. */ - isProtectedFrom (effect) { - if (effect === "HACK") { + isProtectedFrom (type) { + if (type === "TROJAN" || type === "RANSOM") { return this.helpedBy("FIREWALL") - } else if (effect === "VIRUS") { - return this.helpedBy("ANTIVIRUS") } - return false + return this.helpedBy('ANTIVIRUS') } /** * Adds a positive effect and alters negative effects if necessary. */ - addPositive (effect) { - if (effect === "ANTIVIRUS") { - this.negativeEffects.delete("VIRUS") - this.positiveEffects.delete("OVERCLOCK") - } else if (effect === "OVERCLOCK" && this.hurtBy("VIRUS")) { - this.negativeEffects.delete("VIRUS") + addPositive (type) { + if (this.helpedBy(type)) { return } - this.positiveEffects.add(effect) + + if (type === "ANTIVIRUS") { + this.cleanAll() + } else if (type === "FIREWALL") { + this.removeNegative('RANSOM') + } // if we have gotten to here with SCAN it should be added + this.positiveEffects.push(new CyberEffect(type, this.id)) } /** * Adds a negative effect and alters positive effects if necessary. */ - addNegative (effect) { - if (effect === "VIRUS" && this.helpedBy("OVERCLOCK")) { - this.positiveEffects.delete("OVERCLOCK") - return + addNegative (type, attackerId) { + if (this.hurtBy(type)) { + return } - this.negativeEffects.add(effect) + + if (this.helpedBy('SCAN')) { + this.removePositive('SCAN') + return + } + + let effect = new CyberEffect(type, this.id, attackerId) + this.negativeEffects.push(effect) + } + + /** + * Removes all positive effects of a given type. + */ + removePositive (type) { + this.positiveEffects = this.positiveEffects.filter(e => e.type !== type) + } + + /** + * Removes all negative effects of a given type. + */ + removeNegative (type) { + this.negativeEffects = this.negativeEffects.filter(e => e.type !== type) + } + + /** + * Removes a specific effect from the effect list it is in. + */ + removeEffect (effect) { + this.positiveEffects = this.positiveEffects.filter(e => e !== effect) + this.negativeEffects = this.negativeEffects.filter(e => e !== effect) + } + + /** + * Removes all negative effects and weaker positive effects. + */ + cleanAll () { + this.negativeEffects = [] + this.removePositive('FIREWALL') + this.removePositive('SCAN') } } + diff --git a/src/classes/game/Stack.js b/src/classes/game/Stack.js index 878a7227..1384b01a 100755 --- a/src/classes/game/Stack.js +++ b/src/classes/game/Stack.js @@ -3,6 +3,7 @@ * @author Josh on 2017-03-13, Steven modified on 2020-05-25 */ +// Function to create a unique object id const uuidV1 = require('uuid/v1') // The maximum number of repeats allowed in a stack @@ -41,9 +42,13 @@ export default class Stack { let score = this.getBase().value for (let i = 1; i < this.cards.length; i++) { - score *= this.cards[i].value + if (this.cards[i].type === "VIRUS") { + score *= this.getBase().type === "GROUP" ? 0.5 : 0 + } else { + score *= this.cards[i].value + } } - return score + return Math.floor(score) } /** @@ -73,14 +78,6 @@ export default class Stack { return numRepeats >= MAX_REPEATS } - /** - * Checks to see if the stack is hackable or not. - * @return {bool} true if the stack can be hacked, false otherwise. - */ - isHackable () { - return !this.isEmpty() && this.getBase().type !== 'GROUP' - } - /** * Returns true if the stack has at least one variable card. */ @@ -126,6 +123,12 @@ export default class Stack { */ willAccept (card) { let top = this.getTop() + // stacks with virus on top cannot be played on, but otherwise always accept virus + if (top.type === "VIRUS") { + return false + } else if (card.type === "VIRUS") { + return true + } // Variable cards can only go on Rx cards or replace other variables if (card.type === "VARIABLE") { if (top.type === "REPEAT" && top.value === 1) { @@ -154,6 +157,9 @@ export default class Stack { * where if a repeat card is an Rx it must be matched to a variable. */ isComplete () { + if (this.getTop().type === 'VIRUS') { + return false + } // Checks to make sure there are no unpaired Rx cards for (let idx in this.cards) { let card = this.cards[idx] diff --git a/src/classes/game/Trojan.js b/src/classes/game/Trojan.js new file mode 100644 index 00000000..f6a3cbdc --- /dev/null +++ b/src/classes/game/Trojan.js @@ -0,0 +1,41 @@ +/** + * @file Trojan.js file + * @author Steve on 2020-06-17 + */ + +import Card from '@/classes/game/Card' + +/** + * A Card that will pretend to be another card but will change the cards effect + * when played. + */ +export default class Trojan extends Card { + /** + * Constructor for the Trojan class + * @constructor Trojan + * @param {Card} card The card the trojan is hiding behind. + */ + constructor (card, player) { + super(card.type, card.value) + this.card = card + this.player = player + this.isMimic = true + } + + /** + * Returns a new card to replace the one that was being mimicked. + */ + replace () { + let card + if (this.isSafety() || this.type === "GROUP" || this.type === "INSTRUCTION") { + card = new Card("RANSOM", 0) + } else if (this.isAttack() || this.type === "VIRUS") { + card = new Card("SPYWARE", 0) + } else { + card = new Card("VIRUS", 0) + } + card.isExtra = true + return card + } +} + diff --git a/src/components/game/CardStack.vue b/src/components/game/CardStack.vue index f6793f7e..f26e5b39 100644 --- a/src/components/game/CardStack.vue +++ b/src/components/game/CardStack.vue @@ -7,7 +7,7 @@ @@ -58,24 +58,22 @@ export default { * hand and the player's turn will end. */ onDrop (evt) { - let cardId = parseInt(evt.dataTransfer.getData('cardId')) + let cardId = evt.dataTransfer.getData('cardId') let hand = this.getCurrentPlayerHand let card = hand.cards.find(c => c.id === cardId) - // dropped card is a hack card and this stack can be hacked - if (card && card.type === "HACK" - && this.stack.playerId !== this.activePlayer.id - && this.stack.isHackable()) { - this.executeTurn({ - playType: "hackStack", - card: this.activeCard, - player: this.activePlayer, - target: this.stack - }) + // ensure we can play this card on the stack + if (card.type === 'VIRUS') { + let targetPlayer = this.players.find(p => p.id === this.stack.playerId) + if (this.stack.playerId === this.activePlayer.id + || targetPlayer.isProtectedFrom('VIRUS')) { + return + } + } else if (this.stack.playerId !== this.activePlayer.id) { + return + } - // dropped card is any other card and can be played on this stack - } else if (card && this.stack.playerId === this.activePlayer.id - && this.stack.willAccept(card)) { + if (this.stack.willAccept(card)) { this.executeTurn({ playType: "playCardOnStack", card: card, @@ -85,38 +83,24 @@ export default { } }, /** - * Check if this stack is a viable option for playing cards on. + * Decide what shadow the given card should have around it based on its + * type and position in the stack as well as the active card type. */ - isViableStack (isHack) { - if (!this.activeCard) { - return false - } - if (isHack) { - return this.stack.playerId !== this.activePlayer.id - } else { - return this.stack.playerId === this.activePlayer.id + shadow (card) { + let result = '' + if (!this.activeCard || this.stack.getTop() !== card) { + return result + } else if (this.activeCard.type === 'VIRUS') { + let targetPlayer = this.players.find(p => p.id === this.stack.playerId) + if (this.stack.playerId !== this.activePlayer.id + && !targetPlayer.isProtectedFrom('VIRUS')) { + result = 'attack' + } + } else if (this.stack.playerId === this.activePlayer.id) { + result = 'play' } + return this.stack.willAccept(this.activeCard) ? result : '' }, - /** - * Check if the given card is the top of the stack and that the active card can - * be played on it. - */ - canPlayOn (card) { - if (!this.isViableStack() || this.stack.getTop() !== card) { - return false - } - return this.stack.willAccept(this.activeCard) - }, - /** - * Check if the given card is the top of the stack and that the stack can be hacked. - */ - canHack (card) { - if (!this.isViableStack(true) || this.activeCard.type !== "HACK" - || this.stack.getTop() !== card) { - return false - } - return this.stack.isHackable() - } } } @@ -148,7 +132,7 @@ export default { box-shadow: 0 0 24px 10px rgba(255,255,0,1); } -.hack { +.attack { -webkit-box-shadow: 0 0 24px 10px rgba(255,0,0,1); -moz-box-shadow: 0 0 24px 10px rgba(255,0,0,1); box-shadow: 0 0 24px 10px rgba(255,0,0,1); diff --git a/src/components/game/EffectNotifications.vue b/src/components/game/EffectNotifications.vue new file mode 100644 index 00000000..ef89ab21 --- /dev/null +++ b/src/components/game/EffectNotifications.vue @@ -0,0 +1,122 @@ + + + + + + + diff --git a/src/components/game/Game.vue b/src/components/game/Game.vue index 447932a3..08cea534 100644 --- a/src/components/game/Game.vue +++ b/src/components/game/Game.vue @@ -11,12 +11,15 @@ + + - - - - diff --git a/src/components/game/PlayField.vue b/src/components/game/PlayField.vue index 08c78f0f..31c84de7 100644 --- a/src/components/game/PlayField.vue +++ b/src/components/game/PlayField.vue @@ -30,13 +30,12 @@

Group cards can be used to group stacks with total points equal to the group card's value. The group card will replace all cards that are grouped in a single new stack.

-

Hack cards can be dragged onto any opponent stack that is highlighted red - to remove that stack from the opponents Playfield.

-

The scores from each stack are added up to help the player reach the score total. - If the player or the stack is affected by a negative effect the stack score - will change to red. This means the stacks is not contributing it's full score. - An example is the Malware card which reduces the players total score to 75% of - its actual value.

+

Virus cards can be dragged onto any opponent stack that is highlighted red. + Stacks with a Virus card on top will not be able to be added to. Those + started with instructions cards will be worth 0 points and those started with + Group cards will be worth 50%. Virus cards can be removed with Antivirus and + Scan cards to restore your stacks full value

+

The scores from each stack are added up to help the player reach the score total.

@@ -93,7 +92,7 @@ export default { return false } return this.playerStacks.reduce((acc, stack) => { - return acc || stack.getScore() <= this.groupCardValue + return acc || (stack.getScore() <= this.groupCardValue && stack.getTop().type !== 'VIRUS') }, false) }, groupCardValue () { @@ -110,7 +109,7 @@ export default { * So instead we add a new stack containing the card. */ onDrop (evt) { - let cardId = parseInt(evt.dataTransfer.getData('cardId')) + let cardId = evt.dataTransfer.getData('cardId') let hand = this.getCurrentPlayerHand let card = hand.cards.find(c => c.id === cardId) @@ -142,8 +141,9 @@ export default { && stack.cards[0].value === this.groupCardValue) { return false } - return this.isGrouping && (this.grouped.hasStack(stack) - || this.grouped.score + stack.getScore() <= this.groupCardValue) + return this.isGrouping && stack.getTop().type !== 'VIRUS' + && (this.grouped.hasStack(stack) + || this.grouped.score + stack.getScore() <= this.groupCardValue) }, /** * Adds or removes the stack from the grouped stacks. diff --git a/src/components/game/PlayerInfo.vue b/src/components/game/PlayerInfo.vue index 9ceb944c..5b4c26c8 100644 --- a/src/components/game/PlayerInfo.vue +++ b/src/components/game/PlayerInfo.vue @@ -4,15 +4,30 @@ {{ player.name }} + + +
+

+ {{ player.name }}'s hand +

+ +
+ + :src="player.image">
- Score + Score: {{ getScore() }}/{{ scoreLimit }}
-
{{ getScore() }}/{{ scoreLimit }}
-
@@ -36,9 +51,9 @@
@@ -54,8 +69,8 @@

Threat prevention shows all the safety and remedy cards that are active on the player. You can mouse over them to be reminded of what their effect is.

Active Threats shows all the cyber attack cards that are active on the player. - These effects can be removed or prevented by different remedy and safety cards. - eg) Malware can be removed or prevented by Overclock and Anti-Virus cards.

+ These effects can be removed or prevented by safety cards. + eg) Antivirus removes all malware effects.

@@ -79,7 +94,8 @@ export default { props: ['player', 'side'], data () { return { - update: true + update: true, + showHand: false } }, components: { @@ -89,7 +105,8 @@ export default { ...mapState([ 'scoreLimit', 'stacks', - 'activePlayer' + 'activePlayer', + 'hands' ]), playerImagePath () { // later change to imageId to get the specific image they want @@ -100,6 +117,20 @@ export default { }, opposite () { return this.side === 'right' ? 'left' : 'right' + }, + canSpy () { + let spies = this.player.negativeEffects.filter(e => e.type === 'SPYWARE') + return spies.find(s => s.attackerId === this.activePlayer.id) !== undefined + }, + playerCards () { + let hand = this.hands.find(h => h.playerId === this.player.id) + return hand.cards + }, + spyText () { + return this.showHand ? 'End Spy' : 'Spy' + }, + spyStyle () { + return this.showHand ? 'btn-danger' : 'btn-primary' } }, methods: { @@ -119,6 +150,9 @@ export default { let scores = this.$store.getters.getPlayerScores() let scoreInfo = scores.find(scr => scr.playerId === this.player.id) return scoreInfo.score + }, + spyHand () { + this.showHand = !this.showHand } }, created () { @@ -126,6 +160,9 @@ export default { // Scores and effect lists must be updated when a card is played this.update = !this.update }) + bus.$on('end-turn', () => { + this.showHand = false + }) } } @@ -162,15 +199,6 @@ export default { height: 24px; } -#score-text { - position: absolute; - margin: 0; - left: 40%; - top: 10%; - z-index: 40; - color: black; -} - #score-meter { position: absolute; top: 0; @@ -198,9 +226,24 @@ export default { } #info-button { - position: relative; - margin-top: 1.5%; - margin-left: 35%; + position: absolute; + top: 4%; + right: 20%; +} + +#spy-button { + position: absolute; + top: 17%; +} + +#hand-box { + position: fixed; + top: 50px; + left: 27.5%; + background-color: #DFDFDF; + border: solid black 3px; + border-radius: 5px; + z-index: 200; } .left { @@ -221,7 +264,12 @@ export default { width: 30px; height: 30px; margin: 20px 5px; - border: solid black 2px; +} + +.spy-card { + margin: 10px; + width: 100px; + height: auto; } ul { @@ -230,6 +278,10 @@ ul { padding: 0; } +li { + display: inline; +} + h5 { margin: 0; } diff --git a/src/components/game/SideObjectives.vue b/src/components/game/SideObjectives.vue index f2d75a7c..07ab5d93 100644 --- a/src/components/game/SideObjectives.vue +++ b/src/components/game/SideObjectives.vue @@ -1,5 +1,5 @@ @@ -40,7 +40,7 @@ + + + + + diff --git a/src/components/landingPage/LandingPage.vue b/src/components/landingPage/LandingPage.vue index ea7fbfc2..ad15f894 100644 --- a/src/components/landingPage/LandingPage.vue +++ b/src/components/landingPage/LandingPage.vue @@ -15,7 +15,7 @@ diff --git a/src/components/modals/WinnerModal.vue b/src/components/modals/WinnerModal.vue index f4ee4d7a..9387960d 100644 --- a/src/components/modals/WinnerModal.vue +++ b/src/components/modals/WinnerModal.vue @@ -44,13 +44,13 @@ Safety Bonus {{ playerScore(player.id).bonuses.safety }} - Complete Program + Nested Loops {{ playerScore(player.id).bonuses.complete }} - Defensive Programmer (All Safeties) + Defensive Programmer {{ playerScore(player.id).bonuses.defensive }} - Clean System (No Virus) + Clean System (No Malware) {{ playerScore(player.id).bonuses.clean }} Final Score @@ -88,7 +88,8 @@ export default { computed: { ...mapState([ 'players', - 'stacks' + 'stacks', + 'hands' ]), // does not deal with ties getWinner () { @@ -111,7 +112,8 @@ export default { addBonuses (player) { let scores = this.playerScore(player.id) let stacks = this.stacks.filter(s => s.playerId === player.id) - scores.bonuses = player.objectives.getBonuses(stacks) + let hand = this.hands.find(h => h.playerId === player.id) + scores.bonuses = player.objectives.getBonuses(hand, stacks) }, finalScore (player) { let score = this.playerScore(player.id) diff --git a/src/components/shared/InfoPopup.vue b/src/components/shared/InfoPopup.vue index ca28626f..d8e1737a 100644 --- a/src/components/shared/InfoPopup.vue +++ b/src/components/shared/InfoPopup.vue @@ -1,5 +1,5 @@ @@ -86,27 +72,6 @@ export default { color: #fff; } -#tips-check { - position: absolute; - top: 10px; - right: 140px; - margin-bottom: 0; -} - -#basicUsage { - width: 70px; - position: absolute; - top: 5px; - right: 60px; - font-size: 13px; - text-decoration: blue; - padding: 2px; - margin-top: 0px; - border: solid #333333 2px; - border-radius: 5px; - vertical-align: baseline; -} - #menu { position: absolute; top: 2px; diff --git a/src/data/tooltips.js b/src/data/tooltips.js index 628b6317..c8a86154 100644 --- a/src/data/tooltips.js +++ b/src/data/tooltips.js @@ -3,9 +3,10 @@ export default { * Tooltip text for each type of card effect. */ effects: { - VIRUS: "Score reduced to 75% of total", - OVERCLOCK: "Prevents the next Malware attack", + SPYWARE: "Another player can see your hand", + RANSOM: "Another player is stealing 10 points from you", + SCAN: "Prevents the next Malware attack", ANTIVIRUS: "Prevents all Malware attacks", - FIREWALL: "Prevents all hacking attempts" + FIREWALL: "Prevents Trojan and Ransom attacks" } } diff --git a/src/store/actions.js b/src/store/actions.js index 7150fc49..6e1729cb 100755 --- a/src/store/actions.js +++ b/src/store/actions.js @@ -21,10 +21,9 @@ export default { */ newGame (context, payload) { context.commit('resetStateForGame') - context.commit('newTimer') context.commit('addPlayers', payload) context.commit('setStartingPlayer') - context.commit('createNewDeck', {numPlayers: context.state.players.length}) + context.commit('createNewDeck') for (let p of context.state.players) { context.commit('giveNewHand', {player: p}) @@ -38,19 +37,11 @@ export default { * Will reset any state information for starting a new game. */ leaveGame (context) { - context.dispatch('resetForHome') + context.commit('changeGameState', {newState: 'home'}) context.commit('seenBackstory') router.push('home') }, - /** - * Resets necessary elements before returning to landing page. - */ - resetForHome (context) { - context.commit('changeGameState', {newState: 'home'}) - context.commit('stopTimer') - }, - /** * Execute a players turn given the type of play, the card being played, * the player who's turn it is, and the target player/stack. @@ -65,7 +56,9 @@ export default { * } */ executeTurn(context, payload) { - bus.$emit('card-played') + if (context.state.gameState === 'wait') { return } + bus.$emit('card-played', payload) + context.state.turnPlays.push(payload) let draw = true if (payload.playType === "DISCARD") { @@ -73,28 +66,30 @@ export default { } else if (payload.playType === "REDRAW") { context.commit('giveNewHand', payload) draw = false + } else if (payload.card.isMimic) { + context.dispatch('playMimic', payload) } else { context.dispatch(payload.playType, payload) } + context.commit('updatePlayerEffects', payload) context.commit('addPlayedCard', payload) - if (context.state.activePlayer.isAi) { - setTimeout(() => {context.dispatch('endTurn', {draw: draw})}, 1000) - } else { - context.dispatch('endTurn', {draw: draw}) - } + + context.commit('changeGameState', {newState: 'wait'}) + setTimeout(() => { + if (draw) { + context.commit('drawCard') + } + context.commit('changeGameState', {newState: 'game'}) + context.dispatch('endTurn') + }, 1000) }, /** * Clean up after a players turn and change to the next player. * Emits events for game-over and end-turn when necessary. - * - * Payload - * { - * draw: whether the player needs to draw a new card or not - * } */ - endTurn (context, payload) { + endTurn (context) { let scores = context.getters.getPlayerScores() for (let scoreInfo of scores) { if (scoreInfo.score >= context.state.scoreLimit) { @@ -104,9 +99,6 @@ export default { } } - if (payload.draw) { - context.commit('drawCard') - } context.state.activeCard = undefined bus.$emit('end-turn') @@ -171,12 +163,20 @@ export default { }, /** - * Hacks a target stack by removing it from the target players stacks and - * discards the hack card. - * Payload same as executeTurn. + * Play a card that is mimicking another card. */ - hackStack (context, payload) { - context.commit('removeStacks', {stacks: new Set([payload.target])}) + playMimic (context, payload) { + let card = payload.card.replace() + let player = payload.card.player context.commit('discardCard', payload) + payload.card = card + + if (card.type === "VIRUS") { + context.dispatch('playCardOnStack', payload) + } else { + payload.target = payload.player + payload.player = player + context.dispatch('playSpecialCard', payload) + } } } diff --git a/src/store/getters.js b/src/store/getters.js index 7fe9bb37..4f4275e3 100755 --- a/src/store/getters.js +++ b/src/store/getters.js @@ -38,37 +38,9 @@ export default { return state.players.filter((p) => { return p.id !== state.activePlayer.id && !p.isProtectedFrom(effect) && !p.hurtBy(effect) - - }) - }, - - /** - * Get a list of hackable opponents. - */ - getHackableOpponents (state) { - return state.players.filter((p) => { - if (p.id === state.activePlayer.id || p.helpedBy('FIREWALL')) { - return false - } - let stacks = state.stacks.filter(s => s.playerId === p.id && s.isHackable()) - return stacks.length > 0 }) }, - /** - * Get current players objectives. - */ - getCurrentPlayerObjectives (state) { - return state.objectives.find(ob => ob.playerId === state.activePlayer.id) - }, - - /** - * Tell if the program is in game state. - */ - isGame (state) { - return state.gameState === 'game' - }, - /** * Get the base and adjusted scores for all players. * @@ -83,20 +55,32 @@ export default { * see https://vuex.vuejs.org/guide/getters.html#method-style-access */ getPlayerScores: (state) => () => { - let scores = [] + let scores = state.players.map((p) => { + return {playerId: p.id, score: 0, baseScore: 0} + }) + for (let player of state.players) { let stacks = state.stacks.filter(s => s.playerId === player.id) let base = stacks.reduce((acc, stack) => { return acc + stack.getScore() }, 0) - let total = player.hurtBy("VIRUS") ? base * 0.75 : base - - scores.push({ - playerId: player.id, - score: Math.floor(total), - baseScore: base - }) + // Add or subtract bonus points from the players score + let extra = 0 + if (player.hurtBy('RANSOM')) { + let penalty = 10 + let ransomEffects = player.negativeEffects.filter(e => e.type === 'RANSOM') + for (let effect of ransomEffects) { + extra -= penalty + let attackerScore = scores.find(s => s.playerId === effect.attackerId) + attackerScore.score += penalty + } + } + + let score = scores.find(s => s.playerId === player.id) + score.base = base + // += base because players may be recieving bonuses from their ransoms + score.score += base + Math.floor(extra) } return scores } diff --git a/src/store/mutations.js b/src/store/mutations.js index 3b067174..d3813647 100755 --- a/src/store/mutations.js +++ b/src/store/mutations.js @@ -1,8 +1,8 @@ -import Timer from 'easytimer' import { bus } from '@/components/shared/Bus' import Player from '@/classes/game/Player' import Deck from '@/classes/game/Deck' import Stack from '@/classes/game/Stack' +import Trojan from '@/classes/game/Trojan' import AiHandlerFactory from '@/classes/ai/AiHandlerFactory' @@ -31,8 +31,8 @@ export default { state.activePlayer = undefined state.activeCard = undefined state.scoreLimit = 75 - state.tips = {showTips: true, factIndex: 0} state.turnNumber = 0 + state.turnPlays = [] }, /** @@ -60,36 +60,8 @@ export default { /** * Create a new deck for a game with a given payload.numPlayers. */ - createNewDeck (state, payload) { - state.deck = new Deck(payload.numPlayers) - }, - - /** - * Toggle game tips on and off. - */ - toggleTips (state) { - state.tips.showTips = !state.tips.showTips - }, - - /** - * Setup a new timer. - */ - newTimer (state) { - state.timer = new Timer() - state.timer.start() - // eslint-disable-next-line no-unused-vars - state.timer.addEventListener('secondsUpdated', (e) => { - $('#basicUsage').html(state.timer.getTimeValues().toString()) - }) - }, - - /** - * Stop the current timer. - */ - stopTimer (state) { - if (state.timer) { - state.timer.stop() - } + createNewDeck (state) { + state.deck = new Deck() }, /** @@ -125,10 +97,8 @@ export default { giveNewHand (state, payload) { // discard old hand if applicable let oldHand = state.hands.find(h => h.playerId === payload.player.id) - if (oldHand !== undefined) { - for (let card of oldHand.cards) { - state.deck.discard.push(card) - } + if (oldHand) { + this.commit('discardHand', {hand: oldHand}) } // create and fill new hand @@ -143,6 +113,19 @@ export default { state.activeCard = undefined }, + /** + * Discards all the cards in a given hand. + */ + discardHand (state, payload) { + for (let card of payload.hand.cards) { + if (card.isMimic) { + state.deck.discard.push(card.card) + } else if (!card.isExtra) { + state.deck.discard.push(card) + } + } + }, + /** * Set the current active card to payload.newCard. * Emits a card-selected event. @@ -162,6 +145,18 @@ export default { if (id === 0) { state.turnNumber++ } }, + /** + * Updates the cyber effects on the given player. + */ + updatePlayerEffects (state, payload) { + let effects = payload.player.positiveEffects.concat(payload.player.negativeEffects) + for (let effect of effects) { + if (effect.takeTurn() === 0) { + payload.player.removeEffect(effect) + } + } + }, + /** * Draw a card from the deck and add it to the activePlayers hand. */ @@ -183,7 +178,11 @@ export default { discardCard (state, payload) { let hand = state.hands.find(h => h.playerId === payload.player.id) hand.cards = hand.cards.filter(c => c !== payload.card) - state.deck.discard.push(payload.card) + if (payload.card.isMimic) { + state.deck.discard.push(payload.card.card) + } else if (!payload.card.isExtra) { + state.deck.discard.push(payload.card) + } state.activeCard = undefined }, @@ -198,13 +197,95 @@ export default { * } */ addCardEffect (state, payload) { - if (payload.card.isSafety()) { + if (payload.card.type === 'SCAN') { + this.commit('playScan', payload) + } else if (payload.card.isSafety()) { payload.target.addPositive(payload.card.type) - } else { - payload.target.addNegative(payload.card.type) + this.commit('cleanMalware', payload) + } else if (payload.card.type === 'TROJAN') { + if (payload.target.helpedBy('SCAN')) { + payload.target.removePositive('SCAN') + } else { + let hand = state.hands.find(h => h.playerId === payload.target.id) + let pos = Math.floor(Math.random() * hand.cards.length) + hand.cards[pos] = new Trojan(hand.cards[pos], payload.player) + } + } else if (payload.card.isAttack()) { + payload.target.addNegative(payload.card.type, payload.player.id) } }, + /** + * Cleans up all negative effects that are in play in a players hand or + * on their stacks when adding the appropriate positiveEffect. + */ + cleanMalware (state, payload) { + if (payload.card.type === 'ANTIVIRUS' || payload.card.type === 'FIREWALL') { + // replace active trojans with the card they are mimicking + let hand = state.hands.find(h => h.playerId === payload.player.id) + for (let idx in hand.cards) { + if (hand.cards[idx].isMimic) { + hand.cards[idx] = hand.cards[idx].card + } + } + } + // discard all virus cards on player's stacks when playing antivirus + if (payload.card.type === 'ANTIVIRUS') { + let stacks = state.stacks.filter(s => s.playerId === payload.player.id) + for (let stack of stacks) { + if (stack.getTop().type === 'VIRUS') { + state.deck.discard.push(stack.cards.pop()) + } + } + } + }, + + /** + * Removes one random malware that a player has attached to them. + * Trojan cards are discarded and new cards are drawn. + */ + playScan (state, payload) { + // Remove a virus if there is one + let infectedStacks = state.stacks.filter((s) => { + return s.playerId === payload.player.id && s.getTop().type === 'VIRUS' + }) + if (infectedStacks.length > 0) { + // find the largest stack and remove it's virus first eventually + state.deck.discard.push(infectedStacks[0].cards.pop()) + bus.$emit('scan-effect', 'VIRUS') + return + } + + // Remove a mimicked card next if there is one + let hand = state.hands.find(h => h.playerId === payload.player.id) + let mimics = hand.cards.filter(c => c.isMimic) + if (mimics.length > 0) { + this.commit('discardCard', {player: payload.player, card: mimics[0]}) + this.commit('drawCard') + bus.$emit('scan-effect', 'TROJAN') + return + } + + // Remove a ransom next if there is one + let ransoms = payload.player.negativeEffects.filter(e => e.type === 'RANSOM') + if (ransoms.length > 0) { + payload.player.removeEffect(ransoms[0]) + bus.$emit('scan-effect', 'RANSOM') + return + } + + // Remove a spyware next if there is one + let spys = payload.player.negativeEffects.filter(e => e.type === 'SPYWARE') + if (spys.length > 0) { + payload.player.removeEffect(spys[0]) + bus.$emit('scan-effect', 'SPYWARE') + return + } + + // just add the scan to the player + payload.target.addPositive(payload.card.type) + }, + /** * Remove a given card from the hand with the given playerID. * @@ -230,6 +311,15 @@ export default { * } */ addToStack (state, payload) { + // Don't add virus if the player has active SCAN effect + if (payload.card.type === 'VIRUS') { + let targetPlayer = state.players.find(p => p.id === payload.target.playerId) + if (targetPlayer.helpedBy('SCAN')) { + targetPlayer.removePositive('SCAN') + return + } + } + // If we are adding a variable are we replacing one let top = payload.target.getTop() let replace = !(top.type === "REPEAT" && top.value === 1) diff --git a/src/store/store.js b/src/store/store.js index 5d9717c6..4ec67944 100755 --- a/src/store/store.js +++ b/src/store/store.js @@ -26,10 +26,9 @@ export const store = new Vuex.Store({ activePlayer: undefined, activeCard: undefined, scoreLimit: 75, - tips: {showTips: true, factIndex: 0}, - timer: undefined, showBackstory: true, - turnNumber: 0 + turnNumber: 0, + turnPlays: [] }, getters, diff --git a/tests/unit/AiHandler.spec.js b/tests/unit/AiHandler.spec.js index 315e15d1..ea2f8c4d 100644 --- a/tests/unit/AiHandler.spec.js +++ b/tests/unit/AiHandler.spec.js @@ -22,9 +22,20 @@ describe('AiHandler data object', () => { expect(mockAction.mock.calls.length).toEqual(1) expect(mockAction.mock.calls[0]).toEqual(testParams) }) + test('chooseAction defaultAction called because of error', () => { + let handlerError = { handle: jest.fn(() => { throw "action failed" }) } + let aiHandler = new AiHandler(player, [handlerError]) + let mockAction = jest.fn(() => { return "default" }) + aiHandler.defaultAction.handle = mockAction + + let move = aiHandler.chooseAction(...testParams) + expect(move).toEqual("default") + expect(mockAction.mock.calls.length).toEqual(1) + expect(mockAction.mock.calls[0]).toEqual(testParams) + }) test('chooseAction second action handles the request', () => { - let first = {handle: jest.fn(() => { return undefined }) } - let second = {handle: jest.fn(() => { return "second action" })} + let first = { handle: jest.fn(() => { return undefined }) } + let second = { handle: jest.fn(() => { return "second action" }) } let actions = [first, second] let aiHandler = new AiHandler(player, actions) diff --git a/tests/unit/AiHandlerFactory.spec.js b/tests/unit/AiHandlerFactory.spec.js index e4cb24e7..c081552f 100644 --- a/tests/unit/AiHandlerFactory.spec.js +++ b/tests/unit/AiHandlerFactory.spec.js @@ -18,6 +18,6 @@ describe('AiHandlerFactory', () => { expect(handler.player).toBe(player) expect(handler.actionHandlers.length).toEqual(1) expect(handler.actionHandlers[0].player).toBe(player) - expect(handler.actionHandlers[0].playOrder.HACK).toEqual(0) + expect(handler.actionHandlers[0].playOrder.VIRUS).toEqual(0) }) }) diff --git a/tests/unit/Card.spec.js b/tests/unit/Card.spec.js index b035feb1..4ff1e8ed 100644 --- a/tests/unit/Card.spec.js +++ b/tests/unit/Card.spec.js @@ -3,38 +3,49 @@ import Card from '@/classes/game/Card' describe('Card data object', () => { test('constructor functions properly', () => { - let card = new Card(5, 10, 'VIRUS', 'some/image/path.png') - expect(card.id).toEqual(5) - expect(card.value).toEqual(10) + let card = new Card('VIRUS', 0) + expect(card.value).toEqual(0) expect(card.type).toEqual('VIRUS') - expect(card.image).toEqual('some/image/path.png') + expect(card.image).toEqual('static/cardImages/virus0.png') }) - test('virus card is attack', () => { - let card = new Card(5, 10, 'VIRUS', 'some/image/path.png') + test('trojan card is attack', () => { + let card = new Card('TROJAN', 0) + expect(card.isAttack()).toBeTruthy() + expect(card.isSafety()).toBeFalsy() + expect(card.isSpecial()).toBeTruthy() + }) + test('ransom card is attack', () => { + let card = new Card('RANSOM', 0) + expect(card.isAttack()).toBeTruthy() + expect(card.isSafety()).toBeFalsy() + expect(card.isSpecial()).toBeTruthy() + }) + test('spyware card is attack', () => { + let card = new Card('SPYWARE', 0) expect(card.isAttack()).toBeTruthy() expect(card.isSafety()).toBeFalsy() expect(card.isSpecial()).toBeTruthy() }) test('antivirus card is safety', () => { - let card = new Card(5, 10, 'ANTIVIRUS', 'some/image/path.png') + let card = new Card('ANTIVIRUS', 0) expect(card.isAttack()).toBeFalsy() expect(card.isSafety()).toBeTruthy() expect(card.isSpecial()).toBeTruthy() }) - test('overclock card is safety', () => { - let card = new Card(5, 10, 'OVERCLOCK', 'some/image/path.png') + test('scan card is safety', () => { + let card = new Card('SCAN', 0) expect(card.isAttack()).toBeFalsy() expect(card.isSafety()).toBeTruthy() expect(card.isSpecial()).toBeTruthy() }) test('firewall card is safety', () => { - let card = new Card(5, 10, 'FIREWALL', 'some/image/path.png') + let card = new Card('FIREWALL', 0) expect(card.isAttack()).toBeFalsy() expect(card.isSafety()).toBeTruthy() expect(card.isSpecial()).toBeTruthy() }) - test('instruction card is not special', () => { - let card = new Card(5, 10, 'INSTRUCTION', 'some/image/path.png') + test('virus card is not special', () => { + let card = new Card('VIRUS', 0) expect(card.isAttack()).toBeFalsy() expect(card.isSafety()).toBeFalsy() expect(card.isSpecial()).toBeFalsy() diff --git a/tests/unit/CyberEffect.spec.js b/tests/unit/CyberEffect.spec.js new file mode 100644 index 00000000..6a5ccf82 --- /dev/null +++ b/tests/unit/CyberEffect.spec.js @@ -0,0 +1,36 @@ +import CyberEffect from '@/classes/game/CyberEffect' + +describe('CyberEffect', () => { + test('constructor functions as expected', () => { + let effect = new CyberEffect("RANSOM", 1, 2) + expect(effect.type).toEqual("RANSOM") + expect(effect.targetId).toEqual(1) + expect(effect.attackerId).toEqual(2) + expect(effect.id).toBeDefined() + expect(effect.turnsLeft).toBeUndefined() + expect(effect.image).toEqual('static/cardImages/effects/RANSOM.png') + }) + test('constructor sets up turn delay for spyware', () => { + let effect = new CyberEffect("SPYWARE", 1, 2) + expect(effect.turnsLeft).toEqual(2) + }) + test('take turn when turns left is greater than 0', () => { + let effect = new CyberEffect("SPYWARE", 1, 2) + expect(effect.takeTurn()).toEqual(1) + expect(effect.turnsLeft).toEqual(1) + }) + test('take turn when turns left is 0', () => { + let effect = new CyberEffect("SPYWARE", 1, 2) + effect.takeTurn() + effect.takeTurn() + expect(effect.takeTurn()).toEqual(0) + expect(effect.turnsLeft).toEqual(0) + expect(effect.takeTurn()).toEqual(0) + expect(effect.turnsLeft).toEqual(0) + }) + test('take turn when turns left is undefined', () => { + let effect = new CyberEffect("RANSOM", 1, 2) + expect(effect.takeTurn()).toEqual(9999) + expect(effect.turnsLeft).toBeUndefined() + }) +}) diff --git a/tests/unit/Deck.spec.js b/tests/unit/Deck.spec.js index 049b78da..1be85009 100644 --- a/tests/unit/Deck.spec.js +++ b/tests/unit/Deck.spec.js @@ -8,133 +8,71 @@ let count = function (cards, type) { describe('Deck.js', () => { - it('test if the initial discard array is empty', () => { - let testDeck = new Deck(1) + test('constructor functions as expected', () => { + let testDeck = new Deck() expect(testDeck.discard.length).toEqual(0) + expect(testDeck.cards.length).toEqual(82) }) - it('test the initDeck function for number of cards', () => { - let testDeck = new Deck(1) - expect(testDeck.cards.length).toEqual(66) - }) - it('test the initDeck function for number of cards', () => { - let testDeck = new Deck(3) - expect(testDeck.cards.length).toEqual(200) - }) - it('test refreshing the deck when trying to draw a card from empty deck', () => { + test('deck refreshes when trying to draw a card from empty deck', () => { let testDeck = new Deck(1) let numCards = testDeck.cards.length for (let i = 0; i < numCards ; i++) { let card = testDeck.draw() testDeck.discard.push(card) } - expect(testDeck.discard.length).toEqual(66) expect(testDeck.cards.length).toEqual(0) + expect(testDeck.discard.length).toEqual(82) testDeck.draw() - expect(testDeck.cards.length).toEqual(65) + expect(testDeck.cards.length).toEqual(81) expect(testDeck.discard.length).toEqual(0) }) - describe('correct setup for one player decks', () => { - let deck1 + describe('correct setup for deck', () => { + let deck beforeEach(() => { - deck1 = new Deck(1) + deck = new Deck() }) test('correct number of instructions', () => { - expect(count(deck1.cards, 'INSTRUCTION')).toEqual(27) + expect(count(deck.cards, 'INSTRUCTION')).toEqual(30) }) test('correct number of repeats', () => { - expect(count(deck1.cards, 'REPEAT')).toEqual(14) + expect(count(deck.cards, 'REPEAT')).toEqual(16) }) test('correct number of Rx', () => { - let num = deck1.cards.reduce((acc, card) => { + let num = deck.cards.reduce((acc, card) => { return card.type === 'REPEAT' && card.value === 1 ? acc + 1 : acc }, 0) expect(num).toEqual(5) }) test('correct number of variables', () => { - expect(count(deck1.cards, 'VARIABLE')).toEqual(7) + expect(count(deck.cards, 'VARIABLE')).toEqual(7) }) - it('correct number of hacks', () => { - expect(count(deck1.cards, 'HACK')).toEqual(3) + test('correct number of virus', () => { + expect(count(deck.cards, 'VIRUS')).toEqual(3) }) - it('correct number of viruses', () => { - expect(count(deck1.cards, 'VIRUS')).toEqual(3) + test('correct number of ransom', () => { + expect(count(deck.cards, 'RANSOM')).toEqual(3) }) - it('correct number of anti viruses', () => { - expect(count(deck1.cards, 'ANTIVIRUS')).toEqual(0) + test('correct number of spyware', () => { + expect(count(deck.cards, 'SPYWARE')).toEqual(3) }) - it('correct number of firewall cards', () => { - expect(count(deck1.cards, 'FIREWALL')).toEqual(0) + test('correct number of trojan', () => { + expect(count(deck.cards, 'TROJAN')).toEqual(3) }) - it('draw function works properly', () => { - // The deck shuffles itself when created, no way of know what the top card is - expect(deck1.draw()).not.toBeUndefined() - expect(deck1.cards.length).toEqual(65) - }) - // deleted shuffle test as it didn't really test anything - // could write a new one to check that shuffle changes order a bit, but this - // is inherently difficult to be sure of - }) - - describe('correct setup for two player decks', () => { - let deck2 - beforeEach(() => { - deck2 = new Deck(2) - }) - test('correct number of anti virus cards', () => { - expect(count(deck2.cards, 'ANTIVIRUS')).toEqual(1) + test('correct number of anti viruses', () => { + expect(count(deck.cards, 'ANTIVIRUS')).toEqual(1) }) test('correct number of firewall cards', () => { - expect(count(deck2.cards, 'FIREWALL')).toEqual(1) - }) - }) - - describe('correct setup for three player decks', () => { - let deck3 - beforeEach(() => { - deck3 = new Deck(3) - }) - test('correct number instructions', () => { - expect(count(deck3.cards, 'INSTRUCTION')).toEqual(81) - }) - test('correct number of repeats', () => { - expect(count(deck3.cards, 'REPEAT')).toEqual(42) + expect(count(deck.cards, 'FIREWALL')).toEqual(2) }) - test('correct number of Rx', () => { - let num = deck3.cards.reduce((acc, card) => { - return card.type === 'REPEAT' && card.value === 1 ? acc + 1 : acc - }, 0) - expect(num).toEqual(15) - }) - test('correct number of variables', () => { - expect(count(deck3.cards, 'VARIABLE')).toEqual(21) - }) - it('correct number of hacks', () => { - expect(count(deck3.cards, 'HACK')).toEqual(9) - }) - it('correct number of viruses', () => { - expect(count(deck3.cards, 'VIRUS')).toEqual(9) - }) - it('correct number of anti viruses', () => { - expect(count(deck3.cards, 'ANTIVIRUS')).toEqual(1) - }) - it('correct number of firewall cards', () => { - expect(count(deck3.cards, 'FIREWALL')).toEqual(1) + test('correct number of scan cards', () => { + expect(count(deck.cards, 'SCAN')).toEqual(5) }) - }) - - - describe('correct setup for four player decks', () => { - let deck4 - beforeEach(() => { - deck4 = new Deck(4) - }) - test('correct number of anti virus cards', () => { - expect(count(deck4.cards, 'ANTIVIRUS')).toEqual(2) - }) - test('correct number of firewall cards', () => { - expect(count(deck4.cards, 'FIREWALL')).toEqual(2) + test('draw function works properly', () => { + // The deck shuffles itself when created, no way of know what the top card is + expect(deck.draw()).not.toBeUndefined() + expect(deck.cards.length).toEqual(81) }) }) }) diff --git a/tests/unit/Getters.spec.js b/tests/unit/Getters.spec.js deleted file mode 100644 index d55f56ba..00000000 --- a/tests/unit/Getters.spec.js +++ /dev/null @@ -1,71 +0,0 @@ -import getters from '@/store/getters.js' - - -describe('vuex getters', () => { - // Mock functions - const isProtected = jest.fn(e => e || true) - const isNotProtected = jest.fn(e => e && false) - const trueFn = jest.fn().mockReturnValue(true) - const falseFn = jest.fn().mockReturnValue(false) - - // Mock state - const state = { - activeCard: {}, - players: [ - {id: 0, isProtectedFrom: isProtected, helpedBy: trueFn, hurtBy: trueFn}, - {id: 1, isProtectedFrom: isNotProtected, helpedBy: falseFn, hurtBy: trueFn}, - {id: 2, isProtectedFrom: isNotProtected, helpedBy: falseFn, hurtBy: falseFn}, - {id: 3, isProtectedFrom: isNotProtected, helpedBy: falseFn, hurtBy: trueFn} - ], - stacks: [ - {stackId: 25, playerId: 1, isHackable: trueFn}, - {stackId: 26, playerId: 0, isHackable: trueFn}, - {stackId: 27, playerId: 1, isHackable: falseFn}, - {stackId: 28, playerId: 0, isHackable: falseFn}, - {stackId: 29, playerId: 1, isHackable: falseFn}, - {stackId: 30, playerId: 2, isHackable: falseFn}, - {stackId: 31, playerId: 3, isHackable: trueFn}, - ], - hands: [{id: 300, playerId: 0}, {id: 301, playerId: 1}], - aiHandlers: [], - } - - // Set the starting player - state.activePlayer = state.players[1] - - - test('get the current players hand', () => { - let hand = getters.getCurrentPlayerHand(state) - expect(hand.id).toEqual(301) - expect(hand.playerId).toEqual(1) - }) - test('get the current players stacks', () => { - let stacks = getters.getCurrentPlayerStacks(state) - expect(stacks.length).toEqual(3) - expect(stacks[0].stackId).toEqual(25) - expect(stacks[0].playerId).toEqual(1) - expect(stacks[1].stackId).toEqual(27) - expect(stacks[1].playerId).toEqual(1) - expect(stacks[2].stackId).toEqual(29) - expect(stacks[2].playerId).toEqual(1) - }) - test('get an ai handler when current player is not an AI', () => { - let localState = Object.assign({}, state) - localState.activePlayer = state.players[0] - let handler = getters.getCurrentAiHandler(localState) - expect(handler).toBeUndefined() - }) - test('get a list of players that can be attacked with a card type', () => { - let opp = getters.getAttackableOpponents(state) - {'VIRUS'} - expect(opp.length).toEqual(1) - expect(opp[0].id).toEqual(2) - expect(isProtected.mock.calls.length).toEqual(1) - expect(isNotProtected.mock.calls.length).toEqual(2) - }) - test('get a list of players that can be hacked', () => { - let opp = getters.getHackableOpponents(state) - expect(opp.length).toEqual(1) - expect(opp[0].id).toEqual(3) - }) -}) diff --git a/tests/unit/Objectives.spec.js b/tests/unit/Objectives.spec.js index e0a42e64..379db3c5 100644 --- a/tests/unit/Objectives.spec.js +++ b/tests/unit/Objectives.spec.js @@ -4,18 +4,20 @@ import Player from '@/classes/game/Player' let group = {type: 'GROUP'} let repeat = {type: 'REPEAT'} let variable = {type: 'VARIABLE'} +let virus = {type: 'VIRUS'} let antivirus = {type: 'ANTIVIRUS'} let firewall = {type: 'FIREWALL'} +let scan = {type: 'SCAN'} let complete = {isComplete: () => {return true}} let notComplete = {isComplete: () => {return false}} +function mockValue (value) { return jest.fn(() => { return value }) } describe('Objectives.js', () => { test('all objectives when there are no cards played', () => { // Might be better to mock out the player in the future let player = new Player(0, 'steve', false) - let stacks = [] - let bonuses = player.objectives.getBonuses(stacks) + let bonuses = player.objectives.getBonuses({cards: []},[]) expect(bonuses.group).toEqual(0) expect(bonuses.repeat).toEqual(0) expect(bonuses.variable).toEqual(0) @@ -36,40 +38,68 @@ describe('Objectives.js', () => { objectives.cardsPlayed.push(variable) objectives.cardsPlayed.push(repeat) objectives.cardsPlayed.push(repeat) - expect(objectives.getRepeatBonus()).toEqual(4) + expect(objectives.getRepeatBonus()).toEqual(6) }) test('played variable cards', () => { let objectives = new Objectives({}) objectives.cardsPlayed.push(variable) objectives.cardsPlayed.push(repeat) objectives.cardsPlayed.push(variable) - expect(objectives.getVariableBonus()).toEqual(6) + expect(objectives.getVariableBonus()).toEqual(4) }) test('played safety cards', () => { let objectives = new Objectives({}) objectives.cardsPlayed.push(antivirus) objectives.cardsPlayed.push(firewall) + objectives.cardsPlayed.push(scan) objectives.cardsPlayed.push(variable) - expect(objectives.getSafetyBonus()).toEqual(20) + expect(objectives.getSafetyBonus()).toEqual(9) }) - test('player has all safety effects', () => { - let player = new Player(1, 'steve', false) - player.addPositive("ANTIVIRUS") - player.addPositive("FIREWALL") - expect(player.objectives.getDefensiveBonus()).toEqual(10) - }) - test('player has no virus', () => { - let player = new Player(1, 'steve', false) - expect(player.objectives.getCleanBonus()).toEqual(10) + + describe('defensiveBonus', () => { + test('player has antivirus', () => { + let player = new Player(1, 'steve', false) + player.addPositive("ANTIVIRUS") + expect(player.objectives.getDefensiveBonus()).toEqual(10) + }) + test('player has firewall', () => { + let player = new Player(1, 'steve', false) + player.addPositive("FIREWALL") + expect(player.objectives.getDefensiveBonus()).toEqual(5) + }) + test('player has scan', () => { + let player = new Player(1, 'steve', false) + player.addPositive("SCAN") + expect(player.objectives.getDefensiveBonus()).toEqual(0) + }) }) - test('player has virus', () => { - let player = new Player(1, 'steve', false) - player.addNegative('VIRUS') - expect(player.objectives.getCleanBonus()).toEqual(0) + + describe('cleanBonus', () => { + test('player has no malware', () => { + let player = new Player(1, 'steve', false) + let stack = {getTop: mockValue(repeat)} + expect(player.objectives.getCleanBonus({cards: [{isMimic: false}]}, [stack])).toEqual(10) + }) + test('player has virus', () => { + let player = new Player(1, 'steve', false) + let stack = {getTop: mockValue(virus)} + expect(player.objectives.getCleanBonus({cards: []}, [stack])).toEqual(0) + }) + test('player has trojan', () => { + let player = new Player(1, 'steve', false) + expect(player.objectives.getCleanBonus({cards: [{isMimic: true}]}, [])).toEqual(0) + }) + test('player has negative effect', () => { + let player = new Player(1, 'steve', false) + player.addNegative('RANSOM', 0) + expect(player.objectives.getCleanBonus({cards: []}, [])).toEqual(0) + }) + }) + test('has a complete stack', () => { let objectives = new Objectives({}) - let stacks = [complete, notComplete] + let stacks = [complete, complete, notComplete] expect(objectives.getCompleteBonus(stacks)).toEqual(10) }) test('does not have a complete stack', () => { diff --git a/tests/unit/PlayBestCardAction.spec.js b/tests/unit/PlayBestCardAction.spec.js index fab5d282..c9df7624 100644 --- a/tests/unit/PlayBestCardAction.spec.js +++ b/tests/unit/PlayBestCardAction.spec.js @@ -9,7 +9,7 @@ describe('PlayBestCardAction', () => { player = {id: 0} order = [ 'GROUP', 'VARIABLE', 'REPEAT', 'INSTRUCTION', 'ANTIVIRUS', 'FIREWALL', - 'OVERCLOCK', 'HACK', 'VIRUS' + 'OVERCLOCK', 'VIRUS', 'RANSOM' ] action = new PlayBestCardAction(player, order) }) @@ -17,7 +17,7 @@ describe('PlayBestCardAction', () => { test('constructor and play order', () => { expect(action.playOrder.GROUP).toEqual(0) expect(action.playOrder.INSTRUCTION).toEqual(3) - expect(action.playOrder.VIRUS).toEqual(8) + expect(action.playOrder.VIRUS).toEqual(7) }) describe('handle', () => { @@ -145,7 +145,7 @@ describe('PlayBestCardAction', () => { let result = action.variable('card', {hand, players, stacks, scores}) expect(result).toBeUndefined() }) - test('hack can play', () => { + test('virus can play', () => { let notEnoughCards = Object.assign({cards: ['c']}, stackScore_3) notEnoughCards.playerId = 4 @@ -161,19 +161,19 @@ describe('PlayBestCardAction', () => { const stacks = [stackNoAccept, hackStack_3, notEnoughCards, hackStack_6] - let result = action.hack('card', {hand, players, stacks, scores}) - expect(result.playType).toEqual('hackStack') + let result = action.virus('card', {hand, players, stacks, scores}) + expect(result.playType).toEqual('playCardOnStack') expect(result.card).toEqual('card') expect(result.player).toEqual(player) expect(result.target).toEqual(hackStack_6) }) - test('hack no play', () => { + test('virus no play', () => { let noHack = Object.assign(stackScore_3) noHack.cards = ['card', 'card'] noHack.isHackable = getValue(false) const stacks = [stackNoAccept, noHack] - let result = action.hack('card', {hand, players, stacks, scores}) + let result = action.virus('card', {hand, players, stacks, scores}) expect(result).toBeUndefined() }) test('group can play', () => { diff --git a/tests/unit/Player.spec.js b/tests/unit/Player.spec.js index cb1a52b3..e2f783c4 100644 --- a/tests/unit/Player.spec.js +++ b/tests/unit/Player.spec.js @@ -14,54 +14,79 @@ describe('Player.js', () => { test('if helped by antivirus true and false', () => { player.addPositive("ANTIVIRUS") expect(player.helpedBy("ANTIVIRUS")).toBeTruthy() - expect(player.helpedBy("OVERCLOCK")).toBeTruthy() - expect(player.helpedBy("FIREWALL")).toBeFalsy() + expect(player.helpedBy("FIREWALL")).toBeTruthy() + expect(player.helpedBy("SCAN")).toBeTruthy() }) test('if hurt by an effect true and false', () => { - expect(player.hurtBy("VIRUS")).toBeFalsy() - player.addNegative("VIRUS") - expect(player.hurtBy("VIRUS")).toBeTruthy() + expect(player.hurtBy("RANSOM")).toBeFalsy() + player.addNegative("RANSOM") + expect(player.hurtBy("RANSOM")).toBeTruthy() }) - test('is protected from hack true and false', () => { - expect(player.isProtectedFrom("HACK")).toBeFalsy() + test('is protected from tojan by firewall', () => { + expect(player.isProtectedFrom("TROJAN")).toBeFalsy() player.addPositive("FIREWALL") - expect(player.isProtectedFrom("HACK")).toBeTruthy() + expect(player.isProtectedFrom("TROJAN")).toBeTruthy() }) - test('is protected from virus true and false', () => { + test('is protected from ransom by firewall', () => { + expect(player.isProtectedFrom("RANSOM")).toBeFalsy() + player.addPositive("FIREWALL") + expect(player.isProtectedFrom("RANSOM")).toBeTruthy() + }) + test('is protected from everything by antivirus', () => { expect(player.isProtectedFrom("VIRUS")).toBeFalsy() player.addPositive("ANTIVIRUS") expect(player.isProtectedFrom("VIRUS")).toBeTruthy() + expect(player.isProtectedFrom("SPYWARE")).toBeTruthy() + expect(player.isProtectedFrom("RANSOM")).toBeTruthy() + expect(player.isProtectedFrom("TROJAN")).toBeTruthy() }) - test('is not protected from other effect', () => { - expect(player.isProtectedFrom("BOGUSEFFECT")).toBeFalsy() - }) - test('adding overclock removes virus and does not add overclock', () => { - player.addNegative("VIRUS") - expect(player.hurtBy("VIRUS")).toBeTruthy() - player.addPositive("OVERCLOCK") - expect(player.hurtBy("VIRUS")).toBeFalsy() - expect(player.helpedBy("OVERCLOCK")).toBeFalsy() - }) - test('adding antivirus removes virus and keeps antivurus', () => { - player.addNegative("VIRUS") - expect(player.hurtBy("VIRUS")).toBeTruthy() + test('adding antivirus removes all malware and scan', () => { + player.addNegative("RANSOM") + player.addNegative("SPYWARE") + player.addPositive("SCAN") player.addPositive("ANTIVIRUS") - expect(player.hurtBy("VIRUS")).toBeFalsy() expect(player.helpedBy("ANTIVIRUS")).toBeTruthy() + expect(player.hurtBy("RANSOM")).toBeFalsy() + expect(player.hurtBy("SPYWARE")).toBeFalsy() + expect(player.hasPositive("SCAN")).toBeFalsy() }) - test('adding antivirus removes redundant overclock', () => { - player.addPositive("OVERCLOCK") - expect(player.helpedBy("OVERCLOCK")).toBeTruthy() + test('adding antivirus removes firewall', () => { + player.addPositive("FIREWALL") player.addPositive("ANTIVIRUS") expect(player.helpedBy("ANTIVIRUS")).toBeTruthy() - expect(player.helpedBy("OVERCLOCK")).toBeTruthy() - expect(player.positiveEffects.has("OVERCLOCK")).toBeFalsy() }) - test('adding virus removes overclock and does not add virus', () => { - player.addPositive("OVERCLOCK") - expect(player.helpedBy("OVERCLOCK")).toBeTruthy() - player.addNegative("VIRUS") - expect(player.hurtBy("VIRUS")).toBeFalsy() - expect(player.helpedBy("OVERCLOCK")).toBeFalsy() + test('adding firewall removes ransom', () => { + player.addNegative("RANSOM") + player.addPositive("FIREWALL") + expect(player.helpedBy("FIREWALL")).toBeTruthy() + expect(player.hurtBy("RANSOM")).toBeFalsy() + }) + test('adding malware removes scan and does not add malware', () => { + player.addPositive("SCAN") + player.addNegative("RANSOM") + expect(player.hurtBy("RANSOM")).toBeFalsy() + expect(player.helpedBy("SCAN")).toBeFalsy() + }) + test('adding the same type of malware does not work', () => { + player.addNegative("SPYWARE", 2) + player.addNegative("SPYWARE", 3) + expect(player.hurtBy("SPYWARE")).toBeTruthy() + expect(player.negativeEffects.length).toEqual(1) + expect(player.negativeEffects[0].targetId).toEqual(1) + expect(player.negativeEffects[0].attackerId).toEqual(2) + }) + test('remove a specific positive effect', () => { + player.addPositive("SCAN") + expect(player.positiveEffects.length).toEqual(1) + let effect = player.positiveEffects[0] + player.removeEffect(effect) + expect(player.positiveEffects.length).toEqual(0) + }) + test('remove a specific positive effect', () => { + player.addNegative("RANSOM") + expect(player.negativeEffects.length).toEqual(1) + let effect = player.negativeEffects[0] + player.removeEffect(effect) + expect(player.negativeEffects.length).toEqual(0) }) }) diff --git a/tests/unit/Stacks.spec.js b/tests/unit/Stacks.spec.js index c059b421..b553d476 100644 --- a/tests/unit/Stacks.spec.js +++ b/tests/unit/Stacks.spec.js @@ -2,13 +2,14 @@ import Stack from '@/classes/game/Stack' import Card from '@/classes/game/Card' let stack -let instruction = new Card(2, 1, 'INSTRUCTION') -let group = new Card(3, 2, 'GROUP') -let repeat = new Card(4, 3, 'REPEAT') -let r_x = new Card(5, 1, 'REPEAT') -let variable = new Card(6, 4, 'VARIABLE') -let variable_sm = new Card(7, 3, 'VARIABLE') -let variable_lg = new Card(8, 6, 'VARIABLE') +let instruction = new Card('INSTRUCTION', 1) +let group = new Card('GROUP', 2) +let repeat = new Card('REPEAT', 3) +let r_x = new Card('REPEAT', 1) +let variable = new Card('VARIABLE', 4) +let variable_sm = new Card('VARIABLE', 3) +let variable_lg = new Card('VARIABLE', 6) +let virus = new Card('VIRUS', 0) describe('Stack.js', () => { @@ -45,6 +46,16 @@ describe('Stack.js', () => { stack.cards.push(repeat) expect(stack.getScore()).toEqual(18) }) + test('calculate correct score virus', () => { + stack.cards.push(instruction) + stack.cards.push(virus) + expect(stack.getScore()).toEqual(0) + }) + test('calculate correct score virus on group', () => { + stack.cards.push(group) + stack.cards.push(virus) + expect(stack.getScore()).toEqual(1) + }) test('maximum number of repeats (2)', () => { stack.cards.push(instruction) stack.cards.push(repeat) @@ -54,15 +65,6 @@ describe('Stack.js', () => { stack.cards.push(repeat) expect(stack.hasMaxRepeats()).toBeTruthy() }) - test('stack is hackable true', () => { - stack.cards.push(instruction) - expect(stack.isHackable()).toBeTruthy() - }) - test('stack is hackable false', () => { - expect(stack.isHackable()).toBeFalsy() - stack.cards.push(group) - expect(stack.isHackable()).toBeFalsy() - }) test('stack is complete true w/ no Rx', () => { stack.cards.push(instruction) stack.cards.push(repeat) @@ -87,6 +89,13 @@ describe('Stack.js', () => { stack.cards.push(r_x) expect(stack.isComplete()).toBeFalsy() }) + test('stack is complete false w/ virus', () => { + stack.cards.push(instruction) + stack.cards.push(repeat) + stack.cards.push(repeat) + stack.cards.push(virus) + expect(stack.isComplete()).toBeFalsy() + }) test('stack has variable true', () => { stack.cards.push(instruction) stack.cards.push(r_x) @@ -139,6 +148,25 @@ describe('Stack.js', () => { stack.cards.push(variable) expect(stack.willAccept(variable_sm)).toBeFalsy() }) + test('stack will accept virus', () => { + stack.cards.push(instruction) + stack.cards.push(r_x) + stack.cards.push(variable) + expect(stack.willAccept(virus)).toBeTruthy() + }) + test('stack will not accept repeat card on top of virus', () => { + stack.cards.push(instruction) + stack.cards.push(virus) + expect(stack.willAccept(repeat)).toBeFalsy() + }) + test('stack will not accept variable replacement with virus top', () => { + stack.cards.push(instruction) + stack.cards.push(r_x) + stack.cards.push(variable_sm) + expect(stack.willAccept(variable_lg)).toBeTruthy() + stack.cards.push(virus) + expect(stack.willAccept(variable_lg)).toBeFalsy() + }) test('replace lowest variable in stack', () => { stack.cards.push(instruction) stack.cards.push(r_x) @@ -163,5 +191,4 @@ describe('Stack.js', () => { expect(replaced === repeat).toBeTruthy() expect(stack.cards.find(c => c === repeat)).toBeFalsy() }) - }) diff --git a/tests/unit/Trojan.spec.js b/tests/unit/Trojan.spec.js new file mode 100644 index 00000000..59b50399 --- /dev/null +++ b/tests/unit/Trojan.spec.js @@ -0,0 +1,57 @@ +import Trojan from '@/classes/game/Trojan' +import Card from '@/classes/game/Card' + +const scan = new Card('SCAN', 0) +const ransom = new Card('RANSOM', 0) +const virus = new Card('VIRUS', 0) +const group = new Card('GROUP', 3) +const instruction = new Card('INSTRUCTION', 2) +const repeat = new Card('REPEAT', 4) + +const player = {id: 0} + +describe('Trojan', () => { + test('constructor functions properly', () => { + let trojan = new Trojan(scan, player) + expect(trojan.type).toEqual(scan.type) + expect(trojan.value).toEqual(scan.value) + expect(trojan.image).toEqual(scan.image) + expect(trojan.isExtra).toBeFalsy() + expect(trojan.card).toBe(scan) + expect(trojan.player).toBe(player) + expect(trojan.isMimic).toBeTruthy() + }) + test('replace safety is ransom card', () => { + let trojan = new Trojan(scan, player) + let replaced = trojan.replace() + expect(replaced.type).toEqual("RANSOM") + expect(replaced.isExtra).toBeTruthy() + }) + test('replace group is ransom card', () => { + let trojan = new Trojan(group, player) + let replaced = trojan.replace() + expect(replaced.type).toEqual("RANSOM") + }) + test('replace instruction is ransom card', () => { + let trojan = new Trojan(instruction, player) + let replaced = trojan.replace() + expect(replaced.type).toEqual("RANSOM") + }) + test('replace attack is spyware card', () => { + let trojan = new Trojan(ransom, player) + let replaced = trojan.replace() + expect(replaced.type).toEqual("SPYWARE") + expect(replaced.isExtra).toBeTruthy() + }) + test('replace virus is spyware card', () => { + let trojan = new Trojan(virus, player) + let replaced = trojan.replace() + expect(replaced.type).toEqual("SPYWARE") + }) + test('replace repeat (any other type) is virus card', () => { + let trojan = new Trojan(repeat, player) + let replaced = trojan.replace() + expect(replaced.type).toEqual("VIRUS") + expect(replaced.isExtra).toBeTruthy() + }) +}) diff --git a/tests/unit/actions.spec.js b/tests/unit/actions.spec.js new file mode 100644 index 00000000..8b52a52d --- /dev/null +++ b/tests/unit/actions.spec.js @@ -0,0 +1,243 @@ +import actions from '@/store/actions.js' + +function mockValue (value) { return jest.fn(() => {return value}) } + + +describe('vuex actions', () => { + let context + beforeEach(() => { + jest.useFakeTimers() + context = {commit: mockValue(), dispatch: mockValue()} + }) + + test('newGame', () => { + context.state = {players: ['player1', 'player2']} + actions.newGame(context, 'payload') + expect(context.commit.mock.calls.length).toEqual(6) + expect(context.commit.mock.calls[0]).toEqual([ 'resetStateForGame' ]) + expect(context.commit.mock.calls[1]).toEqual([ 'addPlayers', 'payload' ]) + expect(context.commit.mock.calls[2]).toEqual([ 'setStartingPlayer' ]) + expect(context.commit.mock.calls[3]).toEqual([ 'createNewDeck' ]) + expect(context.commit.mock.calls[4]).toEqual( + [ 'giveNewHand', {player: context.state.players[0]} ]) + expect(context.commit.mock.calls[5]).toEqual( + [ 'giveNewHand', {player: context.state.players[1]} ]) + // does not check router push + }) + test('leaveGame', () => { + actions.leaveGame(context) + expect(context.commit.mock.calls.length).toEqual(2) + expect(context.commit.mock.calls[0]).toEqual([ 'changeGameState', {newState: 'home'} ]) + expect(context.commit.mock.calls[1]).toEqual([ 'seenBackstory' ]) + // does not check router push + }) + + describe('executeTurn', () => { + test('game state is wait', () => { + context.state = {gameState: 'wait', turnPlays: []} + actions.executeTurn(context, 'payload') + expect(context.state.turnPlays.length).toEqual(0) + }) + test('play type is DISCARD', () => { + let payload = { + playType: 'DISCARD', + } + context.state = {gameState: 'game', turnPlays: []} + + actions.executeTurn(context, payload) + jest.runAllTimers() + + expect(context.state.turnPlays.length).toEqual(1) + expect(context.state.turnPlays[0]).toEqual(payload) + expect(context.commit.mock.calls.length).toEqual(6) + expect(context.commit.mock.calls[0]).toEqual([ 'discardCard', payload ]) + expect(context.commit.mock.calls[1]).toEqual([ 'updatePlayerEffects', payload ]) + expect(context.commit.mock.calls[2]).toEqual([ 'addPlayedCard', payload ]) + expect(context.commit.mock.calls[3]).toEqual( + [ 'changeGameState', {newState: 'wait'} ] + ) + expect(context.commit.mock.calls[4]).toEqual([ 'drawCard' ]) + + expect(setTimeout).toHaveBeenCalledTimes(1) + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000) + expect(context.commit.mock.calls[5]).toEqual( + [ 'changeGameState', {newState: 'game'} ] + ) + expect(context.dispatch.mock.calls.length).toEqual(1) + expect(context.dispatch.mock.calls[0]).toEqual([ 'endTurn' ]) + }) + test('play type is REDRAW', () => { + // will not test all the intermediate steps already tested above + let payload = { + playType: 'REDRAW', + } + context.state = {gameState: 'game', turnPlays: []} + + actions.executeTurn(context, payload) + expect(context.state.turnPlays.length).toEqual(1) + expect(context.state.turnPlays[0]).toEqual(payload) + expect(context.commit.mock.calls[0]).toEqual([ 'giveNewHand', payload ]) + expect(context.commit.mock.calls[3]).toEqual( + [ 'changeGameState', {newState: 'wait'} ] + ) + }) + test('card is a mimic card', () => { + // will not test all the intermediate steps already tested above + let payload = { + playType: 'playCardOnStack', + card: {isMimic: true} + } + context.state = {gameState: 'game', turnPlays: []} + + actions.executeTurn(context, payload) + expect(context.state.turnPlays.length).toEqual(1) + expect(context.state.turnPlays[0]).toEqual(payload) + expect(context.dispatch.mock.calls[0]).toEqual([ 'playMimic', payload ]) + }) + test('all other turn types', () => { + // will not test all the intermediate steps already tested above + let payload = { + playType: 'playCardOnStack', + card: {isMimic: false} + } + context.state = {gameState: 'game', turnPlays: []} + + actions.executeTurn(context, payload) + expect(context.state.turnPlays.length).toEqual(1) + expect(context.state.turnPlays[0]).toEqual(payload) + expect(context.dispatch.mock.calls[0]).toEqual([ 'playCardOnStack', payload ]) + }) + }) + + describe('endTurn', () => { + test('game not over and next player not AI', () => { + context.getters = { + getPlayerScores: mockValue([ {score: 10}, {score: 20} ]) + } + context.state = { + scoreLimit: 75, + activeCard: 'card', + activePlayer: {isAi: false} + } + actions.endTurn(context) + expect(context.getters.getPlayerScores.mock.calls.length).toEqual(1) + expect(context.state.activeCard).toBeUndefined() + expect(context.commit.mock.calls.length).toEqual(1) + expect(context.commit.mock.calls[0]).toEqual([ 'nextPlayer' ]) + }) + test('game not over and next player is AI', () => { + context.getters = { + getPlayerScores: mockValue([ {score: 10}, {score: 20} ]) + } + context.state = { + scoreLimit: 75, + activeCard: 'card', + activePlayer: {isAi: true} + } + actions.endTurn(context) + expect(context.getters.getPlayerScores.mock.calls.length).toEqual(1) + expect(context.state.activeCard).toBeUndefined() + expect(context.commit.mock.calls.length).toEqual(1) + expect(context.commit.mock.calls[0]).toEqual([ 'nextPlayer' ]) + expect(setTimeout).toHaveBeenCalledTimes(1) + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 500) + + jest.runAllTimers() + expect(context.dispatch.mock.calls.length).toEqual(1) + expect(context.dispatch.mock.calls[0]).toEqual([ 'takeAiTurn' ]) + }) + test('game is over', () => { + context.getters = { + getPlayerScores: mockValue([ {score: 60}, {score: 80} ]) + } + context.state = { + scoreLimit: 75, + activeCard: 'card', + activePlayer: {isAi: true} + } + actions.endTurn(context) + expect(context.getters.getPlayerScores.mock.calls.length).toEqual(1) + expect(context.commit.mock.calls.length).toEqual(1) + expect(context.commit.mock.calls[0]).toEqual( + [ 'changeGameState', {newState: 'winner'} ] + ) + expect(context.state.activeCard).toEqual('card') + }) + + }) + + test('takeAiTurn', () => { + let handler = {chooseAction: mockValue('move')} + context.getters = { + getCurrentAiHandler: handler, + getCurrentPlayerHand: 'hand', + getPlayerScores: mockValue('scores') + } + context.state = {players: 'players', stacks: 'stacks'} + actions.takeAiTurn(context) + expect(handler.chooseAction.mock.calls.length).toEqual(1) + expect(handler.chooseAction.mock.calls[0]).toEqual( + ['hand', 'players', 'stacks', 'scores'] + ) + expect(context.dispatch.mock.calls.length).toEqual(1) + expect(context.dispatch.mock.calls[0]).toEqual([ 'executeTurn', 'move' ]) + // does not check bus.$emit + }) + test('playCardOnStack', () => { + actions.playCardOnStack(context, 'payload') + expect(context.commit.mock.calls.length).toEqual(2) + expect(context.commit.mock.calls[0]).toEqual([ 'removeFromHand', 'payload' ]) + expect(context.commit.mock.calls[1]).toEqual([ 'addToStack', 'payload' ]) + }) + test('startNewStack', () => { + actions.startNewStack(context, 'payload') + expect(context.commit.mock.calls.length).toEqual(2) + expect(context.commit.mock.calls[0]).toEqual([ 'removeFromHand', 'payload' ]) + expect(context.commit.mock.calls[1]).toEqual([ 'newStack', 'payload' ]) + }) + test('playSpecialCard', () => { + actions.playSpecialCard(context, 'payload') + expect(context.commit.mock.calls.length).toEqual(2) + expect(context.commit.mock.calls[0]).toEqual([ 'addCardEffect', 'payload' ]) + expect(context.commit.mock.calls[1]).toEqual([ 'discardCard', 'payload' ]) + }) + test('groupStacks', () => { + let payload = {target: 'stacks'} + actions.groupStacks(context, payload) + expect(context.commit.mock.calls.length).toEqual(3) + expect(context.commit.mock.calls[0]).toEqual([ 'removeStacks', {stacks: payload.target} ]) + expect(context.commit.mock.calls[1]).toEqual([ 'newStack', payload ]) + expect(context.commit.mock.calls[2]).toEqual([ 'removeFromHand', payload ]) + }) + + describe('playMimic', () => { + test('mimicked card is a virus', () => { + let payload = { + card: {replace: mockValue({type: 'VIRUS'}), card: {type: 'test'}, player: 'p1'}, + player: 'test', + } + actions.playMimic(context, payload) + expect(context.commit.mock.calls.length).toEqual(1) + expect(context.commit.mock.calls[0]).toEqual([ 'discardCard', payload ]) + expect(context.dispatch.mock.calls.length).toEqual(1) + expect(context.dispatch.mock.calls[0]).toEqual([ 'playCardOnStack', payload ]) + expect(payload.card.type).toEqual('VIRUS') + expect(payload.player).toEqual('test') + }) + test('mimicked card is any other card', () => { + let payload = { + card: {replace: mockValue({type: 'GROUP'}), card: {type: 'test'}, player: 'p1'}, + player: 'test' + } + actions.playMimic(context, payload) + expect(context.commit.mock.calls.length).toEqual(1) + expect(context.commit.mock.calls[0]).toEqual([ 'discardCard', payload ]) + expect(payload.card.type).toEqual('GROUP') + expect(payload.target).toEqual('test') + expect(payload.player).toEqual('p1') + expect(context.dispatch.mock.calls.length).toEqual(1) + expect(context.dispatch.mock.calls[0]).toEqual([ 'playSpecialCard', payload ]) + }) + }) + +}) diff --git a/tests/unit/getters.spec.js b/tests/unit/getters.spec.js new file mode 100644 index 00000000..b4408dcb --- /dev/null +++ b/tests/unit/getters.spec.js @@ -0,0 +1,106 @@ +import getters from '@/store/getters.js' + + +describe('vuex getters', () => { + // Mock functions + function mockValue (v) { return jest.fn(() => { return v }) } + + // Mock players + const m_players = [ + {id: 0, isProtectedFrom: mockValue(false), hurtBy: mockValue(false)}, + {id: 1, isProtectedFrom: mockValue(true), hurtBy: mockValue(false)}, + {id: 2, isProtectedFrom: mockValue(false), hurtBy: mockValue(true)}, + {id: 3, isProtectedFrom: mockValue(false), hurtBy: mockValue(false)} + ] + + // Mock Stacks + const m_stacks = [ + {stackId: 25, playerId: 1}, + {stackId: 26, playerId: 0}, + {stackId: 27, playerId: 1}, + {stackId: 28, playerId: 0}, + {stackId: 29, playerId: 1}, + ] + + test('get the current players hand', () => { + const state = { activePlayer: m_players[1], hands: [{playerId: m_players[1].id}] } + let hand = getters.getCurrentPlayerHand(state) + expect(hand.playerId).toEqual(1) + }) + test('get the current players stacks', () => { + const state = { activePlayer: m_players[1], stacks: m_stacks } + let stacks = getters.getCurrentPlayerStacks(state) + expect(stacks.length).toEqual(3) + expect(stacks[0].stackId).toEqual(25) + expect(stacks[1].stackId).toEqual(27) + expect(stacks[2].stackId).toEqual(29) + }) + test('get an ai handler when current player is AI', () => { + const state = { activePlayer: m_players[1], aiHandlers: [{player: m_players[1]}] } + let handler = getters.getCurrentAiHandler(state) + expect(handler.player).toBe(m_players[1]) + }) + test('get an ai handler when current player is not an AI', () => { + const state = { activePlayer: m_players[0], aiHandlers: [{player: m_players[1]}] } + let handler = getters.getCurrentAiHandler(state) + expect(handler).toBeUndefined() + }) + test('list of attackable players', () => { + const state = { + activeCard: {type: 'RANSOM'}, + activePlayer: m_players[0], + players: m_players, + } + let opps = getters.getAttackableOpponents(state) + expect(opps.length).toEqual(1) + expect(opps[0]).toBe(m_players[3]) + expect(opps[0].isProtectedFrom.mock.calls).toEqual([ ["RANSOM"] ]) + expect(opps[0].hurtBy.mock.calls).toEqual([ ["RANSOM"] ]) + }) + + describe('getPlayerScores', () => { + test('no special effects', () => { + let mockStacks = [ + {playerId: 1, getScore: mockValue(6)}, + {playerId: 0, getScore: mockValue(15)}, + {playerId: 1, getScore: mockValue(3)}, + ] + let state = { + players: [{id: 0, hurtBy: mockValue(false)}, {id: 1, hurtBy: mockValue(false)}], + stacks: mockStacks, + } + let scoreFunction = getters.getPlayerScores(state) // returns a function + let scores = scoreFunction() + expect(scores.length).toEqual(2) + expect(scores[0].playerId).toEqual(0) + expect(scores[0].base).toEqual(15) + expect(scores[0].score).toEqual(15) + expect(scores[1].playerId).toEqual(1) + expect(scores[1].base).toEqual(9) + expect(scores[1].score).toEqual(9) + }) + test('ransomware on 1 player', () => { + let mockStacks = [ + {playerId: 1, getScore: mockValue(6)}, + {playerId: 0, getScore: mockValue(15)}, + {playerId: 1, getScore: mockValue(3)}, + ] + let state = { + players: [ + {id: 0, hurtBy: mockValue(true), negativeEffects: [{type: 'RANSOM', attackerId: 1}]}, + {id: 1, hurtBy: mockValue(false)}], + stacks: mockStacks, + } + let scoreFunction = getters.getPlayerScores(state) // returns a function + let scores = scoreFunction() + expect(scores.length).toEqual(2) + expect(scores[0].playerId).toEqual(0) + expect(scores[0].base).toEqual(15) + expect(scores[0].score).toEqual(5) + expect(scores[1].playerId).toEqual(1) + expect(scores[1].base).toEqual(9) + expect(scores[1].score).toEqual(19) + }) + }) +}) + diff --git a/tests/unit/mutations.spec.js b/tests/unit/mutations.spec.js new file mode 100644 index 00000000..963ba2f3 --- /dev/null +++ b/tests/unit/mutations.spec.js @@ -0,0 +1,538 @@ +import mutations from '@/store/mutations' + +function mockValue (value) { return jest.fn(() => {return value}) } + + +describe('mutations', () => { + beforeEach(() => { + const commit = jest.fn(() => { return true }) + mutations.commit = commit + }) + + test('resetStateForGame', () => { + let state = {} + mutations.resetStateForGame(state) + expect(state.players.length).toEqual(0) + expect(state.hands.length).toEqual(0) + expect(state.stacks.length).toEqual(0) + expect(state.aiHandlers.length).toEqual(0) + expect(state.deck.cards.length).toEqual(82) + expect(state.gameState).toEqual('game') + expect(state.activePlayer).toBeUndefined() + expect(state.activeCard).toBeUndefined() + expect(state.scoreLimit).toEqual(75) + expect(state.turnNumber).toEqual(0) + }) + test('seenBackstory', () => { + let state = {showBackstory: true} + mutations.seenBackstory(state) + expect(state.showBackstory).toBeFalsy() + }) + test('setStartingPlayer', () => { + let state = {players: [{id: 0}, {id: 1}]} + mutations.setStartingPlayer(state) + expect(state.activePlayer.id).toEqual(0) + }) + test('changeGameState', () => { + let state = {gameState: 'game'} + mutations.changeGameState(state, {newState: 'newState'}) + expect(state.gameState).toEqual('newState') + }) + test('createNewDeck', () => { + let mockDeck = {cards: []} + let state = {deck: mockDeck} + mutations.createNewDeck(state, {newState: 'newState'}) + expect(state.deck).not.toEqual(mockDeck) + expect(state.deck.cards.length).toEqual(82) + }) + test('add human and AI player', () => { + let playerInfo = [{name:'steve', ai: false}, {name:'n00b_b0t', ai: true}] + let state = {players: [], aiHandlers: []} + mutations.addPlayers(state, {players: playerInfo}) + expect(state.players.length).toEqual(2) + expect(state.players[0].name).toEqual('steve') + expect(state.players[1].name).toEqual('n00b_b0t') + expect(state.aiHandlers.length).toEqual(1) + expect(state.aiHandlers[0].player.name).toEqual('n00b_b0t') + }) + + describe('giveNewHand', () => { + test('player has a hand already', () => { + let state = {} + mutations.resetStateForGame(state) + let mockHand = {playerId: 1} + state.hands.push(mockHand) + + mutations.giveNewHand(state, {player: {id: 1}}) + expect(state.hands.length).toEqual(1) + expect(state.hands[0].cards.length).toEqual(5) + expect(mutations.commit.mock.calls.length).toEqual(1) + expect(mutations.commit.mock.calls[0]).toEqual(['discardHand', {hand: mockHand}]) + expect(state.activeCard).toBeUndefined() + }) + test('player has no hand', () => { + let state = {} + mutations.resetStateForGame(state) + + mutations.giveNewHand(state, {player: {id: 1}}) + expect(state.hands.length).toEqual(1) + expect(state.hands[0].cards.length).toEqual(5) + expect(mutations.commit.mock.calls.length).toEqual(0) + }) + }) + + test('discard a hand of cards', () => { + let state = {deck: {discard: []}} + let mockHand = { + cards: [ + {isMimic: true, card: "mockCard"}, + {isMimic: false, isExtra: false}, + {isMimic: false, isExtra: true} + ] + } + + mutations.discardHand(state, {hand: mockHand}) + expect(state.deck.discard.length).toEqual(2) + expect(state.deck.discard[0]).toEqual('mockCard') + expect(state.deck.discard[1]).toEqual(mockHand.cards[1]) + }) + test('setActiveCard', () => { + let state = {} + let mockCard = {id: 1} + mutations.setActiveCard(state, {newCard: mockCard}) + expect(state.activeCard).toBe(mockCard) + // Not sure how to test proper bus.$emit + }) + + describe('nextPlayer', () => { + test('player is not last player in list', () => { + let m_players = [{id: 0}, {id: 1}] + let state = {activePlayer: m_players[0], players: m_players} + mutations.nextPlayer(state) + expect(state.activePlayer.id).toEqual(1) + }) + test('player is last player in list', () => { + let m_players = [{id: 0}, {id: 1}] + let state = {activePlayer: m_players[1], players: m_players} + mutations.nextPlayer(state) + expect(state.activePlayer.id).toEqual(0) + }) + }) + + test('update player effects one removed, one not', () => { + let return0 = mockValue(0) + let return2 = mockValue(2) + let mockPlayer = { + removeEffect: mockValue(), + positiveEffects: [{takeTurn: return0}], + negativeEffects: [{takeTurn: return2}] + } + mutations.updatePlayerEffects({}, {player: mockPlayer}) + expect(return0.mock.calls.length).toEqual(1) + expect(return2.mock.calls.length).toEqual(1) + expect(mockPlayer.removeEffect.mock.calls.length).toEqual(1) + expect(mockPlayer.removeEffect.mock.calls[0]).toEqual([ {takeTurn: return0} ]) + }) + test('drawCard', () => { + let state = { + deck: {draw: mockValue('card')}, + hands: [{playerId: 1, cards: []}], + activePlayer: {id: 1} + } + mutations.drawCard(state) + expect(state.deck.draw.mock.calls.length).toEqual(1) + expect(state.hands[0].cards.length).toEqual(1) + expect(state.hands[0].cards[0]).toEqual('card') + }) + + describe('discardCard', () => { + test('normal card', () => { + let mockCard = {isMimic: false, isExtra: false} + let mockPlayer = {id: 1} + let state = { + hands: [{playerId: 1, cards: [mockCard]}], + deck: {discard: []}, + activeCard: mockCard, + } + mutations.discardCard(state, {player: mockPlayer, card: mockCard}) + expect(state.hands[0].cards.length).toEqual(0) + expect(state.deck.discard.length).toEqual(1) + expect(state.deck.discard[0]).toBe(mockCard) + expect(state.activeCard).toBeUndefined() + }) + test('mimic card', () => { + let mockCard = {isMimic: true, isExtra: false, card: 'card'} + let mockPlayer = {id: 1} + let state = { + hands: [{playerId: 1, cards: [mockCard]}], + deck: {discard: []}, + activeCard: mockCard, + } + mutations.discardCard(state, {player: mockPlayer, card: mockCard}) + expect(state.hands[0].cards.length).toEqual(0) + expect(state.deck.discard.length).toEqual(1) + expect(state.deck.discard[0]).toBe(mockCard.card) + }) + test('extra card', () => { + let mockCard = {isMimic: false, isExtra: true} + let mockPlayer = {id: 1} + let state = { + hands: [{playerId: 1, cards: [mockCard]}], + deck: {discard: []}, + activeCard: mockCard, + } + mutations.discardCard(state, {player: mockPlayer, card: mockCard}) + expect(state.hands[0].cards.length).toEqual(0) + expect(state.deck.discard.length).toEqual(0) + }) + }) + + describe('addCardEffect', () => { + test('card is SCAN', () => { + let payload = {card: {type: 'SCAN'}} + mutations.addCardEffect({}, payload) + expect(mutations.commit.mock.calls.length).toEqual(1) + expect(mutations.commit.mock.calls[0]).toEqual([ 'playScan', payload ]) + }) + test('card is safety', () => { + let payload = { + card: {type: 'ANTIVIRUS', isSafety: mockValue(true)}, + target: {addPositive: mockValue()} + } + mutations.addCardEffect({}, payload) + expect(payload.card.isSafety.mock.calls.length).toEqual(1) + expect(mutations.commit.mock.calls.length).toEqual(1) + expect(mutations.commit.mock.calls[0]).toEqual([ 'cleanMalware', payload ]) + expect(payload.target.addPositive.mock.calls.length).toEqual(1) + expect(payload.target.addPositive.mock.calls[0]).toEqual([ payload.card.type ]) + }) + test('card is TROJAN target is not protected', () => { + let state = { + hands: [{playerId: 1, cards: [{type: 'REPEAT', value: 3}]}], + } + let payload = { + card: {type: 'TROJAN', isSafety: mockValue(false)}, + player: {id: 2}, + target: {id: 1, helpedBy: mockValue(false)} + } + mutations.addCardEffect(state, payload) + expect(state.hands[0].cards.length).toEqual(1) + expect(state.hands[0].cards[0].type).toEqual('REPEAT') + expect(state.hands[0].cards[0].isMimic).toBeTruthy() + }) + test('card is TROJAN target is protected', () => { + let payload = { + card: {type: 'TROJAN', isSafety: mockValue(false)}, + target: {id: 1, helpedBy: mockValue(true), removePositive: mockValue(true)} + } + mutations.addCardEffect({}, payload) + expect(payload.target.helpedBy.mock.calls.length).toEqual(1) + expect(payload.target.helpedBy.mock.calls[0]).toEqual([ 'SCAN' ]) + expect(payload.target.removePositive.mock.calls.length).toEqual(1) + expect(payload.target.removePositive.mock.calls[0]).toEqual([ 'SCAN' ]) + }) + test('card is attack', () => { + let payload = { + card: {type: 'RANSOM', isSafety: mockValue(false), isAttack: mockValue(true)}, + player: {id: 2}, + target: {id: 1, addNegative: mockValue()} + } + mutations.addCardEffect({}, payload) + expect(payload.card.isAttack.mock.calls.length).toEqual(1) + expect(payload.target.addNegative.mock.calls.length).toEqual(1) + expect(payload.target.addNegative.mock.calls[0]).toEqual( + [ payload.card.type, payload.player.id ] + ) + }) + test('card is not an effect card', () => { + let payload = { + card: {type: 'GROUP', isSafety: mockValue(false), isAttack: mockValue(false)}, + } + mutations.addCardEffect({}, payload) + expect(mutations.commit.mock.calls.length).toEqual(0) + }) + }) + + describe('cleanMalware', () => { + test('use antivirus and clean trojans and virus', () => { + let payload = {card: {type: 'ANTIVIRUS'}, player: {id: 1}} + let m_cards = [{isMimic: true, card: 'card'}, {isMimic: false}] + let m_stacks = [{playerId: 1, getTop: mockValue({type: 'VIRUS'}), cards: ['virus']}, + {playerId: 1, getTop: mockValue({type: 'REPEAT'})}] + let state = { + hands: [{playerId: 1, cards: m_cards}], + stacks: m_stacks, + deck: {discard: []} + } + mutations.cleanMalware(state, payload) + expect(m_cards.length).toEqual(2) + expect(m_cards[0]).toEqual('card') + expect(m_stacks[0].cards.length).toEqual(0) + expect(state.deck.discard.length).toEqual(1) + expect(state.deck.discard[0]).toEqual('virus') + }) + test('use firewall and clean trojans', () => { + let m_cards = [{isMimic: true, card: 'card'}, {isMimic: false}] + let state = {hands: [{playerId: 1, cards: m_cards}]} + let payload = {card: {type: 'FIREWALL'}, player: {id: 1}} + + mutations.cleanMalware(state, payload) + expect(m_cards.length).toEqual(2) + expect(m_cards[0]).toEqual('card') + }) + }) + + describe('playScan', () => { + test('no effects to clean', () => { + let payload = { + card: {type: 'SCAN'}, + player: {id: 1, negativeEffects: []}, + target: {addPositive: mockValue()} + } + let state = { + stacks: [], hands: [{playerId: 1, cards: []}] + } + mutations.playScan(state, payload) + expect(payload.target.addPositive.mock.calls.length).toEqual(1) + expect(payload.target.addPositive.mock.calls[0]).toEqual([ 'SCAN' ]) + }) + test('VIRUS effect to clean', () => { + let payload = { + card: {type: 'SCAN'}, + player: {id: 1, negativeEffects: []}, + target: {addPositive: mockValue()} + } + let card = {type: 'VIRUS'} + let state = { + stacks: [{playerId: 1, getTop: mockValue(card), cards: [card]}], + hands: [{playerId: 1, cards: []}], + deck: {discard: []} + } + mutations.playScan(state, payload) + expect(state.deck.discard.length).toEqual(1) + expect(state.deck.discard[0]).toEqual(card) + expect(payload.target.addPositive.mock.calls.length).toEqual(0) + }) + test('mimic effect to clean', () => { + let payload = { + card: {type: 'SCAN'}, + player: {id: 1, negativeEffects: []}, + } + let card = {type: 'REPEAT', isMimic: true} + let state = { + stacks: [], + hands: [{playerId: 1, cards: [card]}] + } + mutations.playScan(state, payload) + expect(mutations.commit.mock.calls.length).toEqual(2) + expect(mutations.commit.mock.calls[0]).toEqual( + [ 'discardCard', {player: payload.player, card: card} ] + ) + expect(mutations.commit.mock.calls[1]).toEqual([ 'drawCard' ]) + }) + test('ransom effect to clean', () => { + let payload = { + card: {type: 'SCAN'}, + player: {id: 1, negativeEffects: [{type: 'RANSOM'}], removeEffect: mockValue()}, + } + let state = { + stacks: [], + hands: [{playerId: 1, cards: []}] + } + mutations.playScan(state, payload) + expect(payload.player.removeEffect.mock.calls.length).toEqual(1) + expect(payload.player.removeEffect.mock.calls[0]).toEqual([ {type: 'RANSOM'} ]) + }) + test('spyware effect to clean', () => { + let payload = { + card: {type: 'SCAN'}, + player: {id: 1, negativeEffects: [{type: 'SPYWARE'}], removeEffect: mockValue()}, + } + let state = { + stacks: [], + hands: [{playerId: 1, cards: []}] + } + mutations.playScan(state, payload) + expect(payload.player.removeEffect.mock.calls.length).toEqual(1) + expect(payload.player.removeEffect.mock.calls[0]).toEqual([ {type: 'SPYWARE'} ]) + }) + }) + + test('removeFromHand', () => { + let m_cards = [{id: 300}, {id: 301}] + let state = {hands: [{playerId: 1, cards: m_cards}]} + let payload = {card: {id: 300}, player: {id: 1}} + mutations.removeFromHand(state, payload) + expect(state.hands[0].cards.length).toEqual(1) + expect(state.hands[0].cards[0].id).toEqual(301) + }) + + describe('addToStack', () => { + test('card is virus and target is protected', () => { + let payload = {card: {type: 'VIRUS'}, target: {playerId: 1}} + let state = { + players: [{id: 1, helpedBy: mockValue(true), removePositive: mockValue()}] + } + mutations.addToStack(state, payload) + expect(state.players[0].helpedBy.mock.calls.length).toEqual(1) + expect(state.players[0].helpedBy.mock.calls[0]).toEqual([ 'SCAN' ]) + expect(state.players[0].removePositive.mock.calls.length).toEqual(1) + expect(state.players[0].removePositive.mock.calls[0]).toEqual([ 'SCAN' ]) + }) + test('card is virus and target is not protected, stack top is normal repeat', () => { + let mockStack = { + playerId: 1, + cards: [], + getTop: mockValue({type: 'REPEAT', value: 3}), + isComplete: mockValue(false) + } + let payload = {card: {type: 'VIRUS'}, target: mockStack} + let state = { + players: [{id: 1, helpedBy: mockValue(false), removePositive: mockValue()}] + } + mutations.addToStack(state, payload) + expect(state.players[0].helpedBy.mock.calls.length).toEqual(1) + expect(state.players[0].helpedBy.mock.calls[0]).toEqual([ 'SCAN' ]) + expect(state.players[0].removePositive.mock.calls.length).toEqual(0) + expect(mockStack.cards.length).toEqual(1) + expect(mockStack.cards[0]).toEqual(payload.card) + }) + test('card is variable and we are completing the stack, not replacing', () => { + let mockStack = { + stackId: 3, + playerId: 1, + cards: [], + getTop: mockValue({type: 'REPEAT', value: 1}), + isComplete: mockValue(true) + } + let payload = {card: {type: 'VARIABLE'}, target: mockStack} + let state = {players: [{id: 1}], stacks: [mockStack, {stackId: 5}]} + + mutations.addToStack(state, payload) + expect(mockStack.cards.length).toEqual(1) + expect(mockStack.cards[0]).toEqual(payload.card) + expect(state.stacks.length).toEqual(2) + expect(state.stacks[0].stackId).toEqual(5) + expect(state.stacks[1]).toEqual(mockStack) + }) + test('card is variable and we are replacing another one', () => { + let mockStack = { + stackId: 3, + playerId: 1, + cards: [], + getTop: mockValue({type: 'REPEAT', value: 4}), + isComplete: mockValue(false), + hasVariable: mockValue(true), + replaceLowestVar: mockValue('LOW_VAR') + } + let payload = {card: {type: 'VARIABLE'}, target: mockStack} + let state = { + players: [{id: 1}], + stacks: [mockStack, {stackId: 5}], + deck: {discard: []} + } + + mutations.addToStack(state, payload) + expect(mockStack.cards.length).toEqual(0) // we are mocking out the replacement + expect(mockStack.replaceLowestVar.mock.calls.length).toEqual(1) + expect(mockStack.replaceLowestVar.mock.calls[0]).toEqual([ payload.card ]) + expect(state.deck.discard.length).toEqual(1) + expect(state.deck.discard[0]).toEqual('LOW_VAR') + expect(state.stacks.length).toEqual(2) + expect(state.stacks[0]).toEqual(mockStack) + expect(state.stacks[1].stackId).toEqual(5) + }) + }) + + describe('newStack', () => { + test('new instructions stack no other stacks', () => { + let payload = { + player: {id: 1}, + card: {type: 'INSTRUCTION'} + } + let state = {stacks: []} + + mutations.newStack(state, payload) + expect(state.stacks.length).toEqual(1) + expect(state.stacks[0].playerId).toEqual(1) + expect(state.stacks[0].cards[0]).toEqual(payload.card) + }) + test('new instructions stack placed in front of complete stack', () => { + let payload = { + player: {id: 1}, + card: {type: 'INSTRUCTION'} + } + let mockStack = {cards: [{type: 'GROUP'}], isComplete: mockValue(true)} + let state = {stacks: [mockStack]} + + mutations.newStack(state, payload) + expect(state.stacks.length).toEqual(2) + expect(state.stacks[0].playerId).toEqual(1) + expect(state.stacks[0].cards[0]).toEqual(payload.card) + expect(state.stacks[1]).toBe(mockStack) + }) + test('new group stack no stacks to be placed in front of', () => { + let payload = { + player: {id: 1}, + card: {type: 'GROUP'} + } + let mockStack = {cards: [{type: 'GROUP'}], isComplete: mockValue(false)} + let state = {stacks: [mockStack]} + + mutations.newStack(state, payload) + expect(state.stacks.length).toEqual(2) + expect(state.stacks[0]).toBe(mockStack) + expect(state.stacks[1].playerId).toEqual(1) + expect(state.stacks[1].cards[0]).toEqual(payload.card) + }) + test('new group stack placed in front of single instruction stack', () => { + let payload = { + player: {id: 1}, + card: {type: 'GROUP'} + } + let mockStack = {cards: [{type: 'INSTRUCTION'}], isComplete: mockValue(false)} + let state = {stacks: [mockStack]} + + mutations.newStack(state, payload) + expect(state.stacks.length).toEqual(2) + expect(state.stacks[0].playerId).toEqual(1) + expect(state.stacks[0].cards[0]).toEqual(payload.card) + expect(state.stacks[1]).toBe(mockStack) + expect(mockStack.isComplete.mock.calls.length).toEqual(0) + }) + }) + + test('removeStacks', () => { + let state = { + stacks: [{id: 200}, {id: 201, cards: ['card']}, {id: 202}], + deck: {discard: []} + } + let payload = {stacks: new Set([state.stacks[1]])} + + mutations.removeStacks(state, payload) + expect(state.stacks.length).toEqual(2) + expect(state.stacks[0].id).toEqual(200) + expect(state.stacks[1].id).toEqual(202) + expect(state.deck.discard.length).toEqual(1) + expect(state.deck.discard[0]).toEqual('card') + }) + + describe('addPlayedCard', () => { + test('actual card is given', () => { + let payload = { + card: 'card', + player: {objectives: {cardsPlayed: []}} + } + mutations.addPlayedCard({}, payload) + expect(payload.player.objectives.cardsPlayed.length).toEqual(1) + expect(payload.player.objectives.cardsPlayed[0]).toEqual('card') + }) + test('payload.card is undefined', () => { + let payload = { + player: {objectives: {cardsPlayed: []}} + } + mutations.addPlayedCard({}, payload) + expect(payload.player.objectives.cardsPlayed.length).toEqual(0) + }) + }) +})