Endless Turn-Based Combat

by .effie

A game where a group of inter-universe characters fight off the endless zerg invasion in a strategical turn-based gameplay.

endlessturn-basedstrategy
{asp[0] = (asp[0] < 5) ? asp[0] + 1 : asp[0]}\nlet decSP = (asp) => {asp[0] = (asp[0] > 0) ? asp[0] - 1 : asp[0]}\nvar power = 200 /* power spend by enemies for first wave */\nvar power_inc = 100 /* power increment each wave */\n\n/* TUNES */\nconst cursorNote = tune`\n150: C5^150,\n4650`\nconst confirmNote = tune`\n100: E5^100,\n100: G5^100,\n3000`\nconst cancelNote = tune`\n100: F5^100,\n100: D5^100,\n3000`\nconst allyHitSFX = tune`\n100: G5~100,\n100: E5~100,\n3000`\nconst allyHitCritSFX = tune`\n100: A5-100,\n100: E5-100,\n100: B4-100,\n2900`\nconst enemyHitSFX = tune`\n100: F5-100,\n100: D5-100,\n3000`\nconst enemyHitCritSFX = tune`\n100: A5/100,\n100: E5/100,\n100: B4/100,\n2900`\nconst enemyDieSFX = tune`\n100: C5^100,\n100: D5^100,\n100: F5^100,\n100: G5~100,\n2800`\nconst allyDieSFX = tune`\n100: G5~100,\n100: E5~100,\n100: C5~100,\n100: A4~100,\n2800`\nconst welcomeSFX = tune`\n100: D5^100,\n100: F5~100,\n100: E5~100,\n100: A5^100,\n100: G5~100,\n2700`\nconst waveClearSFX = tune`\n100: C5/100,\n100: E5/100,\n100: G5/100,\n100: A5-100,\n2800`\nconst theme = tune`\n250: C4/250,\n250: D4/250 + E4^250,\n250,\n250: E5^250,\n250: C4/250 + A5-250,\n250: E5^250,\n250: B5-250,\n250: E5^250,\n250: C4/250 + G5-250,\n250: D4/250 + E4^250,\n250: A5-250,\n250: E5^250,\n250: C4/250 + F5-250,\n250: F4^250,\n250: G5-250,\n250: F5^250,\n250: C4/250 + E5-250,\n250: D4/250 + F5^250,\n250: F5-250,\n250: F5^250,\n250: C4/250,\n250: E4^250,\n250,\n250: B4^250,\n250: C4/250 + F5-250,\n250: D4/250 + E4^250,\n250: F5-250,\n250: C5^250,\n250: C4/250 + F5-250,\n250: C5^250,\n250,\n250: C5^250`\n\n/* UTILITY FUNCTIONS */\nfunction sel2Locate(a1) {\n let row = (a1 % 5) * 2\n let col = 17 - (4 * Math.floor(a1 / 5))\n return [col, row]\n}\n\nfunction sel2Tile(i) {\n let row = (i % 5)\n let col = 9 - (2 * Math.floor(i / 5))\n return [col, row]\n}\n\nfunction isTargettable(attacker, victim) {\n return (!isBurrowed(victim) || isOnlyLurkerRemain()) && ((attacker.type[1] === 'B') || attacker.type[1] === victim.type[0])\n}\n\nlet isOnlyLurkerRemain = () => { return enemies.every(u => u.name === \"Lurker\") }\nlet isBurrowed = (u) => {return 'burrow' in u && u.burrow === true}\n\nfunction getAdjacentIndices(index) {\n const adj = []\n if (index % 5 !== 0) adj.push(index - 1) // up\n if ((index + 1) % 5 !== 0 && index + 1 < 20) adj.push(index + 1) // down\n if (index >= 5) adj.push(index - 5) // right\n if (index < 15) adj.push(index + 5) // left\n return adj.filter(i => i >= 0 && i < enemies.length)\n}\n\nfunction moveMenuCursor(direction) {\n if (direction === 's' && selection1 < MENU_Y_ULT) {\n selection1++\n } else if (direction === 'w' && selection1 > MENU_Y_ATK) {\n selection1--\n }\n playTune(cursorNote)\n for (let yy = MENU_Y_ATK; yy <= MENU_Y_ULT; yy++) {\n let d = (yy === selection1) ? \">\" : \" \"\n addText(d, {x: CURSOR_X, y: yy, color: color`0`})\n }\n}\n\n/* GAME LOGIC */\nlet prices = []\nfor (var enemy of enemies_class) {\n let price = 0\n for (var i = 1; i <= 5; i++) {\n price += Object.values(enemy)[i] * weight[i-1]\n }\n price += enemy.will\n prices.push(price)\n}\n\nfunction drawEnemy() {\n console.log('draw enemy')\n for (var i = 0; i < enemies.length; i++) {\n let [xx, yy] = sel2Tile(i)\n addSprite(xx, yy, (isBurrowed(enemies[i])) ? burrow : spriteMap[enemies[i].name])\n }\n}\n\nfunction genEnemies(a1) {\n let power = a1\n let result = []\n while (result.length < 20) {\n let limit = -1\n for (var i = prices.length - 1; i >= 0; i--) {\n if (prices[i] <= power) {\n limit = i + 1\n break\n }\n }\n if (limit === -1) break\n let rand = Math.floor(Math.random() * limit)\n let selected = { ...enemies_class[rand] }\n selected['av'] = 1000 /* action value */\n result.push(selected)\n power -= prices[rand]\n }\n return result\n}\n\nlet move = []\nfunction step() {\n console.log(`step ${move.length}`)\n if (move.length > 0) return\n while (true) {\n for (var team of [enemies, ally]) {\n for (var i = 0; i < team.length; i++) {\n let unit = team[i]\n if (unit.hp <= 0) continue\n unit.av -= unit.speed\n if (unit.av <= 0) {\n move.push([unit, i])\n console.log(`move push ${unit.name}`)\n unit.av += 1000\n }\n }\n }\n if (move.length > 0) return\n }\n}\n\nfunction hAtk(team, idx, atk) {\n const victim = team[idx], toAlly = ally.includes(victim)\n const isCrit = Math.random() < actor.crit\n let dmg = Math.max(atk * (isCrit ? 2 : 1) - victim.def, 0)\n dmg = isTargettable(actor, victim) ? dmg : 0\n let [xx, yy] = toAlly ? [2, idx * 2] : sel2Locate(idx)\n let shift = toAlly ? (dmg > 99 ? 1 : 0) : (dmg > 9 ? 1 : 0)\n victim.hp -= dmg\n addText(`${dmg}`, {x: xx - shift, y: yy, color: isCrit ? color`3` : color`6`})\n if (isCrit) addText(`Critical Hit!`, {y: 13, color: color`9`})\n isAtk = false\n console.log(`hAtk ${dmg} to ${idx}`)\n\n if (team === ally) playTune(isCrit ? allyHitCritSFX : allyHitSFX)\n else playTune(isCrit ? enemyHitCritSFX : enemyHitSFX)\n\n setTimeout(() => {\n shift = toAlly ? (Math.max(victim.hp, 0) > 99 ? 1 : 0) : (Math.max(victim.hp, 0) > 9 ? 1 : 0)\n addText(` `, {x: xx - 1, y: yy})\n addText(`${Math.max(victim.hp, 0)}`, {x: xx - shift, y: yy, color: color`D`})\n setTimeout(() => {\n addText(` `, {x: xx - shift, y: yy, color: color`D`})\n }, DMG_CLEAR_DELAY)\n\n if (victim.hp <= 0) {\n console.log(`killed ${toAlly} ${idx}`)\n let [xt, yt] = toAlly ? [0, idx] : sel2Tile(idx)\n for (var sprit of getTile(xt, yt)) {\n if (sprit.type != 'B') sprit.remove()\n }\n addSprite(xt, yt, toAlly ? coffin : dead)\n playTune(enemyDieSFX)\n setTimeout(() => {\n for (var ghost of getAll(dead)) ghost.remove()\n }, DMG_CLEAR_DELAY)\n }\n run = true\n }, DMG_DISPLAY_DELAY)\n}\n\nfunction hSkill(team, idx, isAdj) {\n if (idx < 0 || idx >= team.length) return\n if (isAoe && !isAdj) {\n const adjIndices = getAdjacentIndices(idx)\n adjIndices.forEach(adjIdx => {\n hSkill(team, adjIdx, true)\n })\n }\n const victim = team[idx], toAlly = team === ally\n isSelf = isFriendly && idx === actorIdx\n for (let si = 0; si < skill.li.length; si++) {\n const sk = skill.li[si]\n const [xx, yy] = toAlly ? [isSelf ? 4 : 2, idx * 2] : sel2Locate(idx)\n const val = isAdj ? sk.adj : sk.val\n const isAtk = sk.on === 'hp' && val <= 0\n const isHeal = sk.on === 'hp' && val > 0\n const isBuff = sk.on !== 'hp' && val >= 0\n const isCrit = isAtk && Math.random() < actor.crit\n let d = val\n if (isAtk) d = Math.min(val * (isCrit ? 2 : 1) + victim.def, 0)\n if (!isTargettable(actor, victim) || (isHeal && victim.hp <= 0)) d = 0\n if (isHeal && victim.hp + d > Math.floor(victim.hpm * 1.5)) d = Math.floor(victim.hpm * 1.5) - victim.hp\n let after = Math.max(victim[sk.on] + d, 0) // no stat can be negative\n const change = Math.abs(victim[sk.on] - after)\n const col = isCrit ? color`3` : (isHeal || isBuff) ? color`4` : color`6`\n isSkill = false\n const shift = toAlly ? (Math.abs(d) > 99 ? 1 : 0) : (Math.abs(d) > 9 ? 1 : 0)\n \n setTimeout(() => { clearText() }, 650)\n setTimeout(() => {\n const shift = toAlly ? (after > 99 ? 1 : 0) : (after > 9 ? 1 : 0)\n if (si === 0) { // only show first effect's result\n addText(` `, {x: xx - shift, y: yy, color: color`D`})\n addText(`${after}`, {x: xx - shift, y: yy, color: color`D`})\n setTimeout(() => { addText(` `, {x: xx - shift, y: yy, color: color`D`}) }, DMG_CLEAR_DELAY)\n }\n \n if (victim.hp <= 0) { // kill handler\n console.log(`killed ${idx}`)\n let [xt, yt] = toAlly ? [idx, 0] : sel2Tile(idx)\n getTile(xt, yt).forEach(sprit => { if (sprit.type != floor) sprit.remove() })\n addSprite(xt, yt, dead)\n playTune((toAlly) ? allyDieSFX : enemyDieSFX)\n setTimeout(() => { getAll(dead).forEach(ghost => {ghost.remove()}) }, DMG_CLEAR_DELAY)\n }\n run = true\n }, DMG_DISPLAY_DELAY + (si * 50))\n \n if (sk.tick > 0) { // effect type skill\n console.log(`hSkill effect ${skill.n} to ${toAlly} ${idx} -> len(${victim.eff.length})`)\n victim.eff.push({dur:sk.tick, val:sk.val, to:sk.to, on:sk.on, initOnly:sk.initOnly, applied:false})\n if (si === 0) addText(`${Math.abs(d)}`, {x: xx - shift, y: yy, color: col})\n after = actor[sk.on] + sk.val\n continue\n } // instant type skill\n victim[sk.on] += d\n if (toAlly) playTune(isCrit ? allyHitCritSFX : allyHitSFX)\n else playTune(isCrit ? enemyHitCritSFX : enemyHitSFX)\n if (si === 0) {\n addText(`${Math.abs(d)}`, {x: xx - shift, y: yy, color: col})\n if (isCrit) addText(`Critical Hit!`, {y: 13, color: color`9`})\n }\n console.log(`hSkill ${d} (${change}) to ${toAlly} ${idx}`)\n }\n}\n\nfunction flushEnemy() {\n console.log(`flushEnemy`)\n var idx = 0\n while (idx < enemies.length) {\n if (enemies[idx].hp <= 0) {\n console.log(`enemies splice ${idx}`)\n enemies.splice(idx, 1)\n idx--\n }\n idx++\n }\n\n for (var i = 0; i < 20; i++) {\n let [xt, yt] = sel2Tile(i)\n for (var sprit of getTile(xt, yt)) {\n if (sprit.type != 'B') sprit.remove()\n }\n }\n\n drawEnemy()\n\n for (var idx = 0; idx < ally.length; idx++) {\n if (!deadAlly[idx] && ally[idx].hp <= 0) {\n console.log(`ally died ${idx}`)\n deadAlly[idx] = true\n }\n }\n\n if (deadAlly.every(Boolean)) {\n clearText()\n addText(`Game Over!`, {y: 12, color: color`3`})\n addText(`Wave: ${wave}`, {y: 14, color: color`H`})\n run = false\n isPlayerMove = false\n return\n }\n\n if (enemies.length === 0) {\n clearText()\n power += power_inc\n wave++\n playTune(waveClearSFX)\n enemies = genEnemies(power)\n drawEnemy()\n actor = null, actorIdx = null\n addText(`Wave ${wave}`, {y: 13, color: color`H`})\n run = false, isPlayerMove = false\n for (var team of [ally, enemies]) for (var unit of team) unit.av = 1000\n move = []\n setTimeout(() => {\n run = true, isPlayerMove = true\n clearText()\n fun()\n }, 1800)\n return\n }\n}\n\n/* UI AND INPUT HANDLING */\nlet depth = 1\nlet selection1 = MENU_Y_ATK\nlet selection2 = 0\nlet isAoe = false\nlet isAtk = false\nlet isSkill = false\nlet isFriendly = false\nlet isSelf = false\nlet skill = null\nlet ski = null\nlet selectionA = 0\nlet deadAlly = [false, false, false, false]\n\nfunction drawMenu() {\n console.log(`drawMenu`)\n clearText()\n addText(`${actor.name} move${(ally.includes(actor) ? '!' : '.')}`, {y: 11, color: color`0`})\n addText(`Attack `, {x: MENU_X, y: MENU_Y_ATK, color: color`0`})\n addText(`Skill `, {x: MENU_X, y: MENU_Y_SKILL, color: color`0`})\n addText(`Ult `, {x: MENU_X, y: MENU_Y_ULT, color: color`0`})\n addText(`>`, {x: CURSOR_X, y: selection1, color: color`0`})\n showSP()\n addText(`Wave ${wave}`, {x: 0, y: 15, color: color`0`})\n}\n\nfunction drawTarget() {\n console.log(`drawTarget`)\n let [xx, yy] = sel2Locate(selection2)\n const isDmgAble = isTargettable(actor, enemies[selection2])\n addText(`>`, {x: xx, y: yy, color: isDmgAble ? color`3` : color`L`})\n if (isAoe) {\n console.log(`isAoe`)\n const adjIndices = getAdjacentIndices(selection2)\n adjIndices.forEach(adjIdx => {\n let [adjX, adjY] = sel2Locate(adjIdx)\n addText(`>`, {x: adjX, y: adjY, color: isTargettable(actor, enemies[adjIdx]) ? color`9` : color`L`})\n })\n }\n}\n\nfunction drawAlly() {\n console.log(`drawAlly`)\n if (isSelf) selectionA = actorIdx\n const col = (ally[selectionA].hp > 0) ? color`4` : color`L`\n addText(`<`, {x: (actorIdx === selectionA) ? 4 : 2, y: selectionA*2, color: col})\n}\nfunction clearAlly() {\n for (var yy = 0; yy < 10; yy+=2) {\n addText(` `, {x: 2, y: yy, color: color`4`})\n addText(` `, {x: 4, y: yy, color: color`4`})\n }\n}\nfunction clearMenu() {\n console.log(`clearMenu`)\n for (var yy = MENU_Y_ATK; yy <= MENU_Y_ULT; yy++) {\n addText(` `, {x: MENU_X, y: yy})\n }\n}\nfunction clearCursor() {\n console.log(`clearCursor`)\n for (var yy = MENU_Y_ATK; yy <= MENU_Y_ULT; yy++) {\n addText(` `, {x: CURSOR_X, y: yy})\n }\n}\nfunction clearTarget() {\n console.log(`clearTarget`)\n for (var yy = 0; yy < 10; yy++) {\n addText(` `, {x: 17, y: yy})\n addText(` `, {x: 13, y: yy})\n addText(` `, {x: 9, y: yy})\n addText(` `, {x: 5, y: yy})\n }\n}\n\nlet showSP = () => {addText(`${sp[0]}/${sp[1]}`, {x: SP_TEXT_X, y: SP_TEXT_Y, color: color`0`})}\n\nfunction depth1() {\n clearMenu()\n clearCursor()\n isFriendly = false\n if (selection1 == MENU_Y_ATK) {\n console.log(`depth1 attack`)\n isAtk = true\n drawTarget()\n previewDmg()\n return true\n } else if (selection1 == MENU_Y_SKILL) {\n console.log(`depth1 skill`)\n if (sp[0] < 1) {\n console.log(`not enough sp`)\n addText(`You need more`, {y: 12, color: color`3`})\n addText(`skill point`, {y: 13, color: color`3`})\n isPlayerMove = false\n return false\n }\n let len = skill_list[actor.name].length\n let idx = (skill_list[actor.name].length <= 1) ? 0 : (() => { return Math.floor(Math.random() * len) })\n skill = skill_list[actor.name][idx]\n ski = skill.li[0]\n isSkill = true\n isAoe = ski.adj != 0\n let to = ski.to.slice(1, 2)\n if (to === \"e\") {\n isSelf = false\n drawTarget()\n previewSkill()\n } else if (to === \"a\" || to === \"s\") {\n isFriendly = true\n isSelf = to === \"s\"\n drawAlly()\n previewSkill()\n }\n return true\n } else { // ULT\n addText(`unavailable.`, {y: 13, color: color`0`})\n return false\n }\n}\n\nfunction previewDmg() {\n console.log(`previewDmg`)\n let t = enemies[selection2], d = (isTargettable(actor, t)) ? actor.atk - t.def : 0, h = t.hp - d\n h = (h > 0) ? h : 0\n addText(`${tech[actor.name].a}`, {y: 11, color: color`0`})\n addText(`${t.name} (${t.type[0]}) `, {x: 2, y: 13, color: color`0`})\n addText(`${t.hp}->${h} (${Math.max(0, d)}) `, {x: 2, y: 14, color: color`0`})\n}\nfunction previewSkill() {\n let t = (isFriendly) \n ? ally[isSelf ? actorIdx : selectionA]\n : enemies[selection2]\n const isHeal = ski.on === 'hp' && ski.val > 0\n let be = t[ski.on], after = Math.max(0, isTargettable(actor, t) ? be + ski.val : be) // must be targettable\n if (isHeal) {\n after = Math.min(after, Math.floor(t.hpm * 1.5)) // overheal limit\n if (t.hp <= 0) after = 0 // can't heal dead ally\n } else {\n after = ski.on === 'hp' \n ? Math.max(0, be + Math.min(ski.val + t.def, 0))\n : be + ski.val // defense negation\n }\n const change = Math.abs(be - after)\n addText(`${tech[actor.name].s[skill.n]}`, {y: 11, color: color`0`})\n if (ski.tick > 0) {\n console.log(`previewSkill effect`)\n const prefix = (ski.on === 'hp') ? \"\" : `${ski.on} `\n addText(`${prefix}${be}->${after}`, {y: 13, color: color`0`})\n if (skill.n === 't') addText(`+Aggro enemies`, {y: 14, color: color`0`})\n return\n }\n console.log(`previewSkill instant`)\n let adjText = (ski.adj != 0) ? `+${Math.abs(ski.adj)}` : ''\n let toStat = (ski.on === 'hp') ? '' : `: ${ski.on}`\n addText(`${t.name} (${t.type[0]})${toStat} `, {x: 2, y: 13, color: color`0`})\n addText(`${be}->${after} (${isHeal ? change : Math.abs(ski.val)}${adjText}) `, {x: 2, y: 14, color: color`0`})\n}\nfunction pickAlly() {\n let total = 0\n ally.forEach(u => {if (u.hp > 0) total += u.aggro})\n var rand = Math.random() * total\n console.log(`pickAlly ${rand}/${total}`)\n for (var i = 0; i < ally.length; i++) {\n if (ally[i].hp <= 0) continue\n rand -= ally[i].aggro\n if (rand <= 0) return i\n }\n}\n\naddText(\"WASD for cursor\", {x: 3, y: 3, color: color`6` })\nonInput(\"s\", () => {\n if (isPlayerMove) {\n if (depth === 1) {\n console.log('s depth 1')\n moveMenuCursor('s')\n }\n if (depth === 2) {\n if (!isFriendly && selection2 < enemies.length - 1) {\n selection2++\n console.log(`s depth 2 notFriendly ${selection2}`)\n playTune(cursorNote)\n clearTarget()\n drawTarget()\n } else if (isFriendly && !isSelf && selectionA < ally.length - 1) {\n selectionA++\n console.log(`s depth 2 isFriendly ${selectionA}`)\n playTune(cursorNote)\n clearAlly()\n drawAlly()\n }\n (isSkill) ? previewSkill() : previewDmg()\n }\n }\n})\n\nonInput(\"w\", () => {\n if (isPlayerMove) {\n if (depth === 1) {\n console.log('w depth 1')\n moveMenuCursor('w')\n }\n if (depth === 2) {\n if (!isFriendly && selection2 > 0) {\n console.log('w depth 2')\n selection2--\n playTune(cursorNote)\n clearTarget()\n drawTarget()\n } else if (isFriendly && !isSelf && selectionA > 0) {\n console.log('w depth 2')\n selectionA--\n playTune(cursorNote)\n clearAlly()\n drawAlly()\n }\n (isSkill) ? previewSkill() : previewDmg()\n }\n }\n})\n\nonInput(\"a\", () => {\n if (isPlayerMove && depth === 2 && !isFriendly && selection2 + 5 < enemies.length) {\n console.log('a depth 2')\n selection2 += 5\n playTune(cursorNote)\n clearTarget()\n drawTarget()\n isSkill ? previewSkill() : previewDmg()\n }\n})\n\nonInput(\"d\", () => {\n if (isPlayerMove && depth === 2 && !isFriendly && selection2 - 5 >= 0) {\n console.log('d depth 2')\n selection2 -= 5\n playTune(cursorNote)\n clearTarget()\n drawTarget()\n isSkill ? previewSkill() : previewDmg()\n }\n})\n\n// addText(\"I for info\", {x: 6, y: 6, color: color`6` })\n// onInput(\"i\", () => {})\n\naddText(\"J to select\", {x: 6, y: 4, color: color`6` })\nonInput(\"j\", () => {\n if (!gamestarted) clearText()\n if (gamestarted && isPlayerMove) {\n playTune(confirmNote)\n if (depth === 1) {\n console.log('j depth 1')\n if (depth1()) depth++\n else setTimeout(() => {\n drawMenu()\n isPlayerMove = true\n }, DMG_CLEAR_DELAY)\n return\n }\n if (depth === 2 && isAtk) {\n console.log('j depth 2 atk')\n clearMenu()\n clearCursor()\n isPlayerMove = false\n hAtk(enemies, selection2, actor.atk)\n incSP(sp)\n showSP()\n setTimeout(() => {\n selection2 = 0\n isPlayerMove = true\n actorEndTurn()\n let yt = aName2Tile[actor.name]\n getTile(1, yt).forEach(sprit => {if (sprit.type != 'B') sprit.remove()})\n addSprite(0, yt, spriteMap[actor.name])\n flushEnemy()\n setTimeout(() => {fun()}, 100)\n }, TURN_DELAY)\n return\n }\n if (depth === 2 && isSkill) {\n console.log('j depth 2 skill')\n clearMenu()\n clearCursor()\n isPlayerMove = false\n let tTeam = ((ally.includes(actor) ^ isFriendly) == 1) ? enemies : ally\n let tIdx = ((ally.includes(actor) ^ isFriendly) == 1) ? selection2 : selectionA\n tIdx = isSelf ? actorIdx : tIdx\n hSkill(tTeam, tIdx, false)\n decSP(sp)\n showSP()\n setTimeout(() => {\n selection2 = 0\n addText(`...`, {y: 13, color: color``})\n isPlayerMove = true\n actorEndTurn()\n run = true\n ski = null\n let yt = aName2Tile[actor.name]\n for (var sprit of getTile(1, yt)) if (sprit.type != 'B') sprit.remove()\n addSprite(0, yt, spriteMap[actor.name])\n flushEnemy()\n setTimeout(() => {fun()}, 100)\n }, TURN_DELAY)\n }\n }\n})\n\naddText(\"K to back\", {x: 6, y: 5, color: color`6` })\nonInput(\"k\", () => {\n if (isPlayerMove && depth === 2) {\n console.log('k depth 2')\n depth--\n playTune(cancelNote)\n clearTarget()\n clearMenu()\n clearAlly()\n drawMenu()\n isAtk = false, isSkill = false, isAoe = false, isFriendly = false, isSelf = false\n }\n})\n\naddText('Press any key', {y: 12, color: color`0`})\naddText('to start!', {y: 13, color: color`0`})\nplayTune(welcomeSFX)\nsetTimeout(() => {playTune(theme, Infinity)}, 800)\n\nlet gamestarted = false\nlet wave = 1\nlet enemies = genEnemies(power)\ndrawEnemy()\nlet isPlayerMove = true\nlet run = false\nlet actor = null, actorIdx = null\nlet aName2Tile = {Kindler:0, Ranger:1, Paladin:2, Medic:3}\n\nfunction actorEndTurn() { // effect application start\n console.log(`endTurn ${actor.eff.length}`)\n if (actor.eff.length === 0) return\n actor.eff.forEach(eff => {\n if (eff.initOnly && eff.applied) return\n console.log(`${actor.name} applied (${eff.on}) += ${eff.val}`)\n actor[eff.on] += eff.val\n eff.applied = true\n })\n}\n\nfunction actorStartTurn() { // effect duration tick down / expiry\n console.log(`startTurn ${actor.eff.length}`)\n if (actor.eff.length !== 0) {\n actor.eff = actor.eff.filter(eff => {\n eff.dur -= 1\n console.log(`${actor.name} (${eff.on}) tick down`)\n if (eff.initOnly && eff.dur <= 0) {\n console.log(`${actor.name} ${eff.on} expire`)\n actor[eff.on] -= eff.val\n return false // remove effect\n }\n return true // keep effect\n })\n }\n}\n\nfunction fun() {\n if (!(run && isPlayerMove)) return\n console.log('run')\n run = false\n isPlayerMove = false\n isAoe = false\n isSkill = false\n step()\n console.log('move pop')\n let [unit, i] = move.pop()\n console.log(unit.name)\n actor = unit\n actorIdx = i\n actorStartTurn()\n clearText()\n\n if (ally.includes(actor)) {\n console.log('ally unit')\n drawMenu()\n let yt = aName2Tile[unit.name]\n for (var sprit of getTile(0, yt)) if (sprit.type != 'B') sprit.remove()\n addSprite(1, yt, spriteMap[unit.name])\n isPlayerMove = true\n depth = 1\n looper = false\n return\n } else {\n addText(`${unit.name} move.`, {y: 11, color: color`3`})\n let [xt, yt] = sel2Tile(i)\n clearMenu()\n isPlayerMove = false\n run = true\n setTimeout(() => {\n isPlayerMove = true\n actorEndTurn()\n for (var sprit of getTile(xt - 1, yt)) if (sprit.type != 'B') sprit.remove()\n if (actor.hp > 0) addSprite(xt, yt, spriteMap[actor.name])\n flushEnemy()\n setTimeout(() => {fun()}, 100)\n }, 2100)\n console.log(`bedacui ${actor.name}`) // delete\n if (actor.name === \"Lurker\" && !('burrow' in actor)) {\n actor.burrow = true\n getTile(xt, yt).map((sprit) => { if (sprit.type === Lurker) sprit.remove() })\n addSprite(xt, yt, burrow)\n return\n } else {\n for (var sprit of getTile(xt, yt)) if (sprit.type != 'B') sprit.remove()\n addSprite(xt - 1, yt, isBurrowed(actor) ? burrow : spriteMap[unit.name])\n }\n let idx = pickAlly()\n if (Object.keys(skill_list).includes(actor.name) && Math.random() < esp[0]/esp[1]) {\n console.log(`enemy skill`)\n decSP(esp)\n } else {\n console.log(`enemy atk ally ${idx}`)\n if (actor.name === 'Infested') {\n actor.hp = 0\n let [xt, yt] = sel2Tile(i)\n for (var sprit of getTile(xt - 1, yt)) if (sprit.type != 'B') sprit.remove()\n addSprite(xt - 1, yt, explosion)\n }\n hAtk(ally, idx, actor.atk)\n if (actor.name === 'Mutalisk') {\n let idx1 = idx, idx2 = idx\n let cnt = 0; for (var u of ally) if (u.hp > 0) cnt++\n while (cnt >= 2 && idx1 === idx) idx1 = pickAlly()\n while (cnt >= 3 && idx2 === idx1 || idx2 === idx) idx2 = pickAlly()\n if (idx1 !== idx) setTimeout(() => {hAtk(ally, idx1, 3)}, 50)\n if (idx2 !== idx) setTimeout(() => {hAtk(ally, idx2, 1)}, 100)\n }\n incSP(esp)\n }\n }\n}\n\nafterInput(() => {\n if (!gamestarted) {\n console.log('game started')\n gamestarted = true\n run = true\n }\n fun()\n})\n"],"gameName":[0,"Endless Turn-Based Combat"],"authorName":[0,".effie"],"filename":[0,"Endless-Turn-Based-Strategy"],"isLoggedIn":[0,false],"hearted":[0,false],"data-astro-cid-d4jrbbcn":[0,true]}idx2 = pickAlly()\n if (idx1 !== idx) setTimeout(() => {hAtk(ally, idx1, 3)}, 50)\n if (idx2 !== idx) setTimeout(() => {hAtk(ally, idx2, 1)}, 100)\n }\n incSP(esp)\n }\n }\n}\n\nafterInput(() => {\n if (!gamestarted) {\n console.log('game started')\n gamestarted = true\n run = true\n }\n fun()\n})\n"],"gameName":[0,"Endless Turn-Based Combat"],"authorName":[0,".effie"],"filename":[0,"Endless-Turn-Based-Strategy"],"isLoggedIn":[0,false],"hearted":[0,false],"data-astro-cid-d4jrbbcn":[0,true]}" client="load" opts="{"name":"DesktopPlayer","value":true}","value":true}" await-children="">

Endless Turn-Based Combat

by .effie