diff --git a/Input.js b/Input.js index 18dbf2e..966df42 100644 --- a/Input.js +++ b/Input.js @@ -110,6 +110,12 @@ const modifier = (text) => { else text = rawText } + if (state.stragedyTurn != null) { + text = handleStragedyTurn(text) + if (state.stragedyTurn != null) return { text } + else text = rawText + } + if (state.initialized == null || !text.includes("#")) { state.initialized = true; return { text } @@ -1364,11 +1370,102 @@ function handleStragedyShopStep(text) { } function doStragedy(command) { + var character = getCharacter() state.stragedyTurn = "intro" state.show = "stragedy" + + state.stragedyPlayerScore = 0 + state.stragedyPlayerHand = [] + state.stragedyPlayerBattlefield = [] + + state.stragedyPlayerDeck = [] + for (item of character.inventory) { + if (/stragedy ace card/gi.test(item.name)) state.stragedyPlayerDeck.push("a") + else if (/stragedy jack card/gi.test(item.name)) state.stragedyPlayerDeck.push("j") + else if (/stragedy queen card/gi.test(item.name)) state.stragedyPlayerDeck.push("q") + else if (/stragedy king card/gi.test(item.name)) state.stragedyPlayerDeck.push("k") + else if (/stragedy joker card/gi.test(item.name)) state.stragedyPlayerDeck.push("?") + else if (/stragedy witch card/gi.test(item.name)) state.stragedyPlayerDeck.push("w") + else if (/stragedy priest card/gi.test(item.name)) state.stragedyPlayerDeck.push("p") + else if (/stragedy brigand card/gi.test(item.name)) state.stragedyPlayerDeck.push("b") + else if (/stragedy \d+ card/gi.test(item.name)) { + for (var i = 0; i < item.quantity; i++) { + state.stragedyPlayerDeck.push(item.name.match(/(?<=stragedy )\d+(?= card)/gi)[0]) + } + } + } + + shuffle(state.stragedyPlayerDeck) + state.stragedyPlayerDeck.splice(20) + state.stragedyPlayerDiscard = [] + state.stragedyPlayerCursed = false + + state.stragedyEnemyScore = 0 + state.stragedyEnemyHand = [] + state.stragedyEnemyBattlefield = [] + state.stragedyEnemyDeck = ["5", "6", "7", "8", "a", "a", "9", "9", "10", "5", "5", "5", "2", "3", "4", "6", "7", "8", "9", "10"] + shuffle(state.stragedyEnemyDeck) + state.stragedyEnemyDiscard = [] + state.stragedyEnemyCursed = false + state.stragedyEnemySkipTurn = getRandomBoolean(.5) + return " " } +function handleStragedyTurn(text) { + state.show = "stragedy" + + if (/^\s*>.*says? ".*/.test(text)) { + text = text.replace(/^\s*>.*says? "/, "") + text = text.replace(/"\s*$/, "") + } else if (/^\s*>\s.*/.test(text)) { + text = text.replace(/\s*> /, "") + for (var i = 0; i < info.characters.length; i++) { + var matchString = info.characters[i] == "" ? "You " : `${info.characters[i]} ` + if (text.startsWith(matchString)) { + text = text.replace(matchString, "") + break + } + } + text = text.replace(/\.?\s*$/, "") + } else { + text = text.replace(/^\s+/, "") + } + + text = text.toLowerCase() + if (text == "f") { + state.stragedyTurn = "gameOver" + state.stragedyWinner = "forfeit" + return "You forfeit the game." + } + + switch (state.stragedyTurn) { + case "intro": + if (text == "d") { + if (state.stragedyPlayerDeck.length < 20) return "\nYou cannot play if you don't have at least 20 Stragedy cards.\n" + + state.stragedyTurn = "game" + var drawCards = state.stragedyPlayerDeck.splice(state.stragedyPlayerDeck.length - 4) + state.stragedyPlayerHand.push(...drawCards) + + drawCards = state.stragedyEnemyDeck.splice(state.stragedyEnemyDeck.length - 4) + state.stragedyEnemyHand.push(...drawCards) + + stragedyCalculateScores() + if (!state.stragedyEnemySkipTurn) stragedyEnemyTurn() + } + return "You deal the cards." + case "game": + return stragedyPlayerTurn(text) + case "gameOver": + state.show = null + state.stragedyTurn = null + return text + } + + return `\nUnexpected stragedy state. Input text: ${text}` +} + function doAddCard(command) { var arg0 = getArgument(command, 0) if (arg0 == null) { diff --git a/Library.js b/Library.js index 1902297..39012f4 100644 --- a/Library.js +++ b/Library.js @@ -21,6 +21,7 @@ function getRandomFromList(...choices) { } function shuffle(array, seed) { + if (seed == null) seed = getRandomInteger(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER) let currentIndex = array.length while (currentIndex != 0) { let randomIndex = Math.floor(getRandom(seed + currentIndex) * currentIndex) @@ -2846,6 +2847,300 @@ function findSpellCardIndex(name) { return storyCards.findIndex((element) => element.type == "spell" && element.keys == name) } +function stragedyCalculateScores() { + state.stragedyEnemyScore = 0 + state.stragedyPlayerScore = 0 + var playerHasJoker = false + var enemyHasJoker = false + var playerBlessedPoints = 0 + var enemyBlessedPoints = 0 + var doubledPoints = [] + + //check for kings + for(card of state.stragedyPlayerBattlefield) { + var points = parseInt(card.match(/(?<=.*)\d+/gi)[0]) + + if (card.includes("k")) { + doubledPoints.push(points) + } + } + + for(card of state.stragedyEnemyBattlefield) { + var points = parseInt(card.match(/(?<=.*)\d+/gi)[0]) + + if (card.includes("k")) { + doubledPoints.push(points) + } + } + + //enemy + for(card of state.stragedyEnemyBattlefield) { + var points = parseInt(card.match(/(?<=.*)\d+/gi)[0]) + + if (doubledPoints.includes(points)) { + points *= 2 + } + + if (card.includes("q")) { + state.stragedyPlayerScore += points + points = 0 + } + + if (card.includes("?")) { + enemyHasJoker = true + } + + if (card.includes("p")) { + enemyBlessedPoints += points + } + + state.stragedyEnemyScore += points + } + + if (enemyHasJoker && state.stragedyEnemyScore < 30) { + state.stragedyEnemyScore = 30 + } else if (state.stragedyEnemyScore > 30) { + state.stragedyEnemyScore = Math.max(30, state.stragedyEnemyScore - enemyBlessedPoints) + } + + //player + for(card of state.stragedyPlayerBattlefield) { + var points = parseInt(card.match(/(?<=.*)\d+/gi)[0]) + + if (doubledPoints.includes(points)) { + points *= 2 + } + + if (card.includes("q")) { + state.stragedyEnemyScore += points + points = 0 + } + + if (card.includes("?")) { + playerHasJoker = true + } + + if (card.includes("p")) { + playerBlessedPoints += points + } + + state.stragedyPlayerScore += points + } + + if (playerHasJoker && state.stragedyPlayerScore < 30) { + state.stragedyPlayerScore = 30 + } else if (state.stragedyPlayerScore > 30) { + state.stragedyPlayerScore = Math.max(30, state.stragedyPlayerScore - playerBlessedPoints) + } +} + +function stragedyEnemyTurn() { + state.stragedyEnemySkipTurn = false + state.stragedyEnemyTurnText = "" + if (state.stragedyPlayerScore > 30) { + state.stragedyEnemyTurnText = null + stragedyCheckForWin() + state.stragedyTurn = "gameOver" + return + } + + //enemy turn here + + if (state.stragedyEnemyScore > 30) { + stragedyCheckForWin() + state.stragedyTurn = "gameOver" + return + } +} + +function stragedyPlayerTurn(text) { + var character = getCharacter() + var playedWord = character.name == "You" ? "played" : "play" + + if (text.startsWith("d") && state.stragedyPlayerHand.length > 0) { + if (state.stragedyPlayerDeck.length == 0) return "\nYou cannot discard if you have 0 cards in your deck.\n" + + var targetCard = text.substring(1).toLowerCase() + if (targetCard.length == 0) return "\nYou must specify the card you wish to discard\n" + + var handIndex = state.stragedyPlayerHand.findIndex(x => x.toLowerCase() == targetCard) + if (handIndex == -1) return "\nYou cannot discard a card that is not in your hand.\n" + + state.stragedyPlayerHand.splice(handIndex, 1); + state.stragedyPlayerDiscard.push(targetCard) + + var newCards = state.stragedyPlayerDeck.splice(state.stragedyPlayerDeck.length - 2) + state.stragedyPlayerHand.push(...newCards) + + text = `You discard the "${targetCard}" card. You draw ` + if (newCards.length == 1) text += `a "${newCards[0]}" card.` + else text += `the "${newCards[0]}" and "${newCards[1]}" cards.` + + stragedyCalculateScores() + stragedyEnemyTurn() + return text + } else if (text.startsWith("d") && state.stragedyPlayerHand.length == 0) { + if (state.stragedyPlayerDeck.length == 0) return "\nYou cannot draw if you have 0 cards in your deck.\n" + + var drawCard = state.stragedyPlayerDeck.pop() + state.stragedyPlayerHand.push(drawCard) + + stragedyCalculateScores() + stragedyEnemyTurn() + return `You draw a ${drawCard}` + } else { + var isNumberedCard = /^\d+$/.test(text) + var handCard = isNumberedCard ? text : text.substring(0, 1).toLowerCase() + var targetCard = text.substring(1).toLowerCase() + + var handIndex = state.stragedyPlayerHand.findIndex(x => x.toLowerCase() == handCard) + if (handIndex == -1) return "\nYou can only play cards that are in your hand\n" + + var targetIndex = targetCard == "" ? -1 : state.stragedyPlayerBattlefield.findIndex(x => x.toLowerCase() == targetCard) + if (targetCard != "" && targetIndex == -1) return "\nYou must specify a target that is placed on your side of the battlefield.\n" + + state.stragedyEnemySkipTurn = true + switch (handCard) { + case "a": + if (targetCard == "") return "\nYou must specify a target to use the Ace (ie. a2)\n" + + state.stragedyPlayerHand.splice(handIndex, 1) + + while (targetIndex != -1) { + var discardGroups = state.stragedyPlayerBattlefield.splice(targetIndex, 1) + var discardCards = [] + for (var group of discardGroups) { + discardCards.push(...group) + } + state.stragedyPlayerDiscard.push(...discardCards) + targetIndex = state.stragedyPlayerBattlefield.findIndex(x => x.toLowerCase().endsWith(targetCard.substring(targetCard.length - 1, targetCard.length))) + } + + targetIndex = state.stragedyEnemyBattlefield.findIndex(x => x.toLowerCase().endsWith(targetCard.substring(targetCard.length - 1, targetCard.length))) + while (targetIndex != -1) { + var discardGroups = state.stragedyPlayerBattlefield.splice(targetIndex, 1) + var discardCards = [] + for (var group of discardGroups) { + discardCards.push(...group) + } + state.stragedyPlayerDiscard.push(...discardCards) + targetIndex = state.stragedyEnemyBattlefield.findIndex(x => x.toLowerCase().endsWith(targetCard.substring(targetCard.length - 1, targetCard.length))) + } + + stragedyCalculateScores() + stragedyEnemyTurn() + return `\n${toTitleCase(character.name)} ${playedWord} a ${handCard}. All ${targetCard}s are removed.\n` + case "j": + if (targetCard == "") return "\nYou must specify a target to use the Jack (ie. j2)\n" + + state.stragedyPlayerBattlefield.splice(targetIndex, 1) + var discardCards = [...targetCard] + + state.stragedyPlayerHand.splice(handIndex, 1) + discardCards.push(handCard) + + state.stragedyPlayerDiscard.push(...discardCards) + + shuffle(state.stragedyPlayerDiscard) + var addCard = state.stragedyPlayerDiscard.pop() + state.stragedyPlayerHand.push(addCard) + + stragedyCalculateScores() + stragedyEnemyTurn() + return `\n${toTitleCase(character.name)} ${playedWord} a jack on the ${targetCard}. The ${targetCard} is removed. You drew a ${addCard} from the discard pile\n` + case "q": + if (targetCard == "") return "\nYou must specify a target to use the Queen (ie. q2)\n" + + state.stragedyPlayerHand.splice(handIndex, 1) + state.stragedyPlayerBattlefield.splice(targetIndex, 1) + + state.stragedyPlayerBattlefield.push(handCard + targetCard) + + stragedyCalculateScores() + stragedyEnemyTurn() + return `\n${toTitleCase(character.name)} ${playedWord} a queen on the ${targetCard}. The value is added to the opponent.\n` + case "k": + if (targetCard == "") return "\nYou must specify a target to use the King (ie. k2)\n" + + state.stragedyPlayerHand.splice(handIndex, 1) + state.stragedyPlayerBattlefield.splice(targetIndex, 1) + + state.stragedyPlayerBattlefield.push(handCard + targetCard) + + stragedyCalculateScores() + stragedyEnemyTurn() + return `\n${toTitleCase(character.name)} ${playedWord} a king on the ${targetCard}. All ${targetCard.match(/\d+/g)} values are doubled.\n` + case "?": + if (targetCard == "") return "\nYou must specify a target to use the Joker (ie. ?2)\n" + + state.stragedyPlayerHand.splice(handIndex, 1) + state.stragedyPlayerBattlefield.splice(targetIndex, 1) + + state.stragedyPlayerBattlefield.push(handCard + targetCard) + + stragedyCalculateScores() + stragedyEnemyTurn() + return `\n${toTitleCase(character.name)} ${playedWord} a joker on the ${targetCard}. The card's value is increased to make the total score 30.\n` + case "w": + state.stragedyPlayerHand.splice(handIndex, 1) + state.stragedyPlayerDiscard.push(handCard) + + state.stragedyEnemyCursed = true + stragedyCalculateScores() + stragedyEnemyTurn() + return `\n${toTitleCase(character.name)} ${playedWord} a witch on the opponent.\n` + case "p": + if (targetCard == "") return "\nYou must specify a target to use the Priest (ie. p2)\n" + + state.stragedyPlayerHand.splice(handIndex, 1) + state.stragedyPlayerBattlefield.splice(targetIndex, 1) + + state.stragedyPlayerBattlefield.push(handCard + targetCard) + + stragedyCalculateScores() + stragedyEnemyTurn() + return `\n${toTitleCase(character.name)} ${playedWord} a priest on the ${targetCard}. This card is prevented from causing you to bust.\n` + case "b": + state.stragedyPlayerHand.splice(handIndex, 1) + state.stragedyPlayerDiscard.push(handCard) + + var i + for (i = 0; i < 5 && state.stragedyEnemyDeck.length > 0; i++) { + var card = state.stragedyEnemyDeck.pop() + state.stragedyEnemyDiscard.push(...card) + } + stragedyCalculateScores() + stragedyEnemyTurn() + return `\n${toTitleCase(character.name)} ${playedWord} a brigand on the opponent. They are forced to discard ${i} cards from their deck\n` + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + case "8": + case "9": + case "10": + state.stragedyPlayerBattlefield.push(handCard) + state.stragedyPlayerHand.splice(handIndex, 1) + stragedyCalculateScores() + stragedyEnemyTurn() + return `\n${toTitleCase(character.name)} ${playedWord} a ${handCard}.\n` + default: + return "\nUnrecognized card specified. Stop playing with counterfit cards!\n" + } + } +} + +function stragedyCheckForWin() { + if (state.stragedyEnemyScore > 30 && state.stragedyPlayerScore > 30) state.stragedyWinner = "tie" + else if (state.stragedyEnemyScore > 30) state.stragedyWinner = "player" + else if (state.stragedyPlayerScore > 30) state.stragedyWinner = "enemy" + else if (state.stragedyPlayerScore > state.stragedyEnemyScore) state.stragedyWinner = "player" + else if (state.stragedyEnemyScore > state.stragedyPlayerScore) state.stragedyWinner = "enemy" + else state.stragedyWinner = "tie" +} + String.prototype.replaceAt = function(index, replacement) { return this.substring(0, index) + replacement + this.substring(index + replacement.length); } diff --git a/Output.js b/Output.js index 191ca04..36ed38d 100644 --- a/Output.js +++ b/Output.js @@ -554,14 +554,92 @@ function mapReplace(map, x, y, character) { } function handleStragedy() { + var character = getCharacter() + var haveWord = character.name == "You" ? "have" : "has" + var possessiveName = getPossessiveName(character.name) + var text = " " switch (state.stragedyTurn) { case "intro": text = `**Stragedy** Welcome to Stragedy! A trading card game of wits, strategy, and tragic outcomes! -Please see the game manual on github for rules, tactics, and a complete tutorial: github.com/raeleus/Hashtag-DnD/ +Please see the game manual on github for rules, tactics, and a complete tutorial: +github.com/raeleus/Hashtag-DnD/ +Type d to deal the cards or press f to forfeit. ` break + case "game": + var enemyBattlefield = state.stragedyEnemyBattlefield.length > 0 ? "" : "No cards!" + for (card of state.stragedyEnemyBattlefield) { + enemyBattlefield += `${card}, ` + } + if (state.stragedyEnemyBattlefield.length > 0) enemyBattlefield = enemyBattlefield.substring(0, enemyBattlefield.length - 2) + + var enemyDeckCount = state.stragedyEnemyDeck.length + var enemyDiscardCount = state.stragedyEnemyDiscard.length + var enemyHandCount = state.stragedyEnemyHand.length + + var playerBattlefield = state.stragedyPlayerBattlefield.length > 0 ? "" : "No cards!" + for (card of state.stragedyPlayerBattlefield) { + playerBattlefield += `${card}, ` + } + if (state.stragedyPlayerBattlefield.length > 0) playerBattlefield = playerBattlefield.substring(0, playerBattlefield.length - 2) + + var playerHand = state.stragedyPlayerHand.length > 0 ? "" : "No cards!" + for (card of state.stragedyPlayerHand) { + playerHand += `${card}, ` + } + if (state.stragedyPlayerHand.length > 0) playerHand = playerHand.substring(0, playerHand.length - 2) + + var playerDeckCount = state.stragedyPlayerDeck.length + var playerDiscardCount = state.stragedyPlayerDiscard.length + + if (state.stragedyEnemySkipTurn) text = `-----The Opponent's Turn----- +The cpu does something. + +` + else text = "" + + text += `-----The Opponent's Cards----- +The opponent has ${enemyDeckCount} cards in the deck, ${enemyDiscardCount} in the discard pile, and ${enemyHandCount} in their hand. + +-----The Battlefield----- +Opponent's cards on the battlefield: ${enemyBattlefield} = ${state.stragedyEnemyScore} points +${possessiveName} cards on the battlefield: ${playerBattlefield} = ${state.stragedyPlayerScore} points + +-----${possessiveName} cards----- +${possessiveName} hand: ${playerHand} +${toTitleCase(character.name)} ${haveWord} ${playerDeckCount} cards in the deck and ${playerDiscardCount} in the discard pile. + +-----${possessiveName} Turn-----` + + if (state.stragedyPlayerHand.length > 0) text += ` +Play a number card to the battlefield by typing its number. +Play a letter card onto a card in the battlefield by typing the letter followed by the card you want to play it on. +Witches and Brigands are played directly to the discard pile. Simply type w or b. +Type d and the number or letter of a card in your hand to discard it. You will then draw 2 cards. +Type r to retire. This forces the opponent to make their last move. +Type f to forfeit. This quits the game immediately. +` + else text += ` +Type d to draw a card. +Type r to retire. This forces the opponent to make their last move. +Type f to forfeit. This quits the game immediately. +` + if (state.stragedyPlayerScore > 30) text += `WARNING: You must lower your score below 30 or you will bust!\n` + break + case "gameOver": + text = "" + + if (state.stragedyEnemyTurnText != null) text += "\n" + state.stragedyEnemyTurnText + + text += ` +The battle has concluded.${state.stragedyWinner != "forfeit" ? `\nFinal scores:\n${character.name}: ${state.stragedyPlayerScore}\nOpponent: ${state.stragedyEnemyScore}`: ""} +` + if (state.stragedyWinner == "player" || state.stragedyWinner == "forfeit") text += `${toTitleCase(character.name)} ${haveWord} won! Congratulations.` + else if (state.stragedyWinner == "enemy") text += `${toTitleCase(character.name)} ${haveWord} lost! Better luck next time.` + else text += `${toTitleCase(character.name)} and the opponent have tied! Try again.` + break } return text