diff options
| author | LLLL Colonq <llll@colonq> | 2026-08-26 23:47:59 -0400 |
|---|---|---|
| committer | LLLL Colonq <llll@colonq> | 2026-08-26 23:47:59 -0400 |
| commit | 0d2d8cd897e94bd94e0b3ae7c6ce9aa580a44a0a (patch) | |
| tree | e9039d1c3aedca515d6cb499451d4b73aca4f5fd /2026/games_submissions/cammymoop/js/dice_game.js | |
| parent | 242943e5b023ce40eb3be826c418a478d4672f78 (diff) | |
Add a_tension_span
Diffstat (limited to '2026/games_submissions/cammymoop/js/dice_game.js')
| -rw-r--r-- | 2026/games_submissions/cammymoop/js/dice_game.js | 622 |
1 files changed, 622 insertions, 0 deletions
diff --git a/2026/games_submissions/cammymoop/js/dice_game.js b/2026/games_submissions/cammymoop/js/dice_game.js new file mode 100644 index 0000000..2810872 --- /dev/null +++ b/2026/games_submissions/cammymoop/js/dice_game.js @@ -0,0 +1,622 @@ + +function randOffset(size) { + return Math.round((Math.random() - 0.5) * 2 * size); +} + +function smoothRandOffset(size) { + return (Math.random() - 0.5) * 2 * size; +} + +M.Ease = Phaser.Math.Easing; + +const ROLL_TOTAL = 290; +const ROLL_ONCE = 60; + +const battles = [ + // difficulty tier 0 + [ + [3, "Goblin", "Goblin"], + [2, "BombGnome", "Goblin"], + [2, "BombGnome", "BombGnome"], + ], + // difficulty tier 1 + [ + [3, "Goblin", "Goblin", "BombGnome"], + [2, "Goblin", "Goblin"], + [1, "Goblin"], + [4, "ShieldGoblin"], + ], + // difficulty tier 2 + [ + [4, "ShieldGoblin", "ShieldGoblin"], + [7, "Goblin", "Goblin", "Goblin", "Goblin"], + [3, "ShieldGoblin", "BombGnome"], + [3, "Goblin", "BombGnome", "Goblin", "Goblin"], + [1, "BombGnome", "BombGnome", "BombGnome", "Goblin"], + ], + // difficulty tier 3 + [ + [2, "ShieldGoblin", "BombGnome"], + [3, "ShieldGoblin", "BombGnome", "BombGnome", "ShieldGoblin"], + [7, "Goblin", "Goblin", "Goblin", "ShieldGoblin", "Goblin"], + [7, "ShieldGoblin", "ShieldGoblin", "ShieldGoblin"], + ], + // difficulty tier 4 + [ + [3, "ShieldGoblin", "Goblin", "BombGnome"], + [6, "Goblin", "Goblin", "Goblin", "ShieldGoblin", "Goblin"], + [7, "ShieldGoblin", "ShieldGoblin", "ShieldGoblin"], + ], + // difficulty tier 5 + [ + [10, "ShieldGoblin", "ShieldGoblin", "ShieldGoblin", "ShieldGoblin", "ShieldGoblin"], + [8, "ShieldGoblin", "ShieldGoblin", "ShieldGoblin", "ShieldGoblin", "BombGnome"], + ], + +]; +const maxTier = battles.length - 1; + +const battleBags = [[],[],[],[],[], []]; + +const timerTimes = [12, 14, 16, 14, 10, 14]; + +function arrShuffle(inArr) { + return inArr.map((v) => ({v: v, r: Math.random()})).sort((a, b) => a.r - b.r).map(({v}) => v); +} + +function getBattle(tier) { + tier = Math.min(battles.length - 1, tier); + if (battleBags[tier].length < 1) { + battleBags[tier] = arrShuffle([...battles[tier]]); + } + return [...battleBags[tier].pop()]; +} + +const enemyTypes = { + "Goblin": (s) => new M.Goblin(s), + "ShieldGoblin": (s) => new M.Goblin(s, true), + "BombGnome": (s) => new M.BombGnome(s), +}; + +M.DiceGame = class extends Phaser.Scene +{ + create(data) + { + this.finished = false; + this.victory = false; + this.usedGrace = false; + + this.difficultyVal = data.difficulty; + this.tier = Math.min(maxTier, Math.max(0, Math.floor(this.difficultyVal * 0.2))); + + this.battle = getBattle(this.tier); + this.timeLimit = timerTimes[Math.min(timerTimes.length - 1, this.tier)] + // make it a bit easier for now + this.timeLimit = Math.ceil(this.timeLimit * 1.4); + + console.log("STARTED!!!!!!!!!!!!!!!!"); + M.gameStarted(); + + this.diceIsActive = false; + + this.justClickedLMB = false; + this.LMBWasClicked = false; + + this.bg = this.add.image(-30, -22, 'background').setOrigin(0, 0); + + this.stopwatch = new M.Stopwatch(this, this.timeLimit, new M.Vec2(26, 26)); + this.stopwatch.depth = 5; + + this.circle = this.add.image(-6000, 0, "red_circle"); + this.targetArrow = this.add.image(-6000, 0, "target_arrow"); + this.targetArrow.setOrigin(1, 0.5); + this.targetArrow.depth = 2; + + this.allEnemies = []; + + const enemySpacing = 50; + const enemyYOffset = -12; + const startOffset = ((this.battle.length - 2) / 2) * enemySpacing; + for (let i = 1; i < this.battle.length; i++) { + const newEnemy = enemyTypes[this.battle[i]](this); + const xOffset = ((i - 1) * enemySpacing) - startOffset; + newEnemy.setHomePos(this.screenCenterPos().add(new M.Vec2( + xOffset, enemyYOffset + ((i % 2) * 6) + ))); + newEnemy.addToDisplayUpdate(); + this.allEnemies.push(newEnemy); + } + + this.reroll = this.add.image(220, 132, "reroll"); + this.reroll.depth = 1; + + this.numDice = this.battle[0]; + this.allDice = []; + this.rollDice(this.numDice); + } + + screenCenterPos() { + const h_center = this.game.scale.gameSize.width / 2; + const v_center = this.game.scale.gameSize.height / 2; + return new M.Vec2(h_center, v_center); + } + + update(t, dt) { + let pickableDice = 0; + for (let dice of this.allDice) { + if (dice.throwing) { + this.updateThrownDice(dice, dt); + } else if (dice.rolling) { + dice.roll_timer += dt; + dice.roll_anim_timer += dt; + + if (dice.roll_anim_timer > ROLL_ONCE) { + dice.roll_anim_timer = 0; + let rand_side = Math.floor(Math.random() * 5); + let cur_side = parseInt(dice.frame.name); + if (rand_side >= cur_side) { + rand_side += 1; + } + dice.setFrame(rand_side); + } + + const progress = dice.roll_timer / dice.total_roll_time; + + const elevation = Math.sin(progress * Math.PI) * 38; + const rollVec = dice.roll_target_pos.clone().subtract(dice.roll_from_pos); + dice.setPosVec(dice.roll_from_pos); + dice.addPosVec(rollVec.scale(progress)); + dice.addPosVec(M.Vec2.UP.clone().scale(elevation)); + + if (dice.roll_timer >= dice.total_roll_time) { + this.sound.play('land_sfx'); + dice.total_roll_time = ROLL_TOTAL; + dice.rolling = false; + pickableDice += 1; + dice.setPosVec(dice.roll_target_pos); + this.setupRolledDice(dice); + } + } else { + pickableDice += 1; + } + } + + const activePointer = this.input.activePointer; + activePointer.updateWorldPoint(this.cameras.default); + const pVec = new M.Vec2(activePointer.worldX, activePointer.worldY); + + const clicked = activePointer.primaryDown + this.justClickedLMB = !this.LMBWasClicked && clicked + this.LMBWasClicked = clicked + + let closestEnemy = null; + let closestDice = null; + let minDist = 0; + for (let dice of this.allDice) { + if (dice.rolling) { + continue; + } + let pointerDist = pVec.distance(dice.getPosVec()); + if (pointerDist > 49 || (!dice.emphasis && pointerDist > 45)) { + continue; + } else if (!closestDice || pointerDist < minDist) { + minDist = pointerDist; + closestDice = dice; + } + } + for (let enemy of this.allEnemies) { + if (!enemy.alive) { + continue; + } + let pointerDist = pVec.distance(enemy.getPosVec()); + if (pointerDist > 49) { + continue; + } else if (!closestEnemy || pointerDist < minDist) { + minDist = pointerDist; + closestEnemy = enemy; + } + } + + if (this.isOverReroll(pVec)) { + closestDice = null; + closestEnemy = null; + if (this.justClickedLMB && pickableDice > 0) { + this.rerollUnused(); + } + } + + // If close enough to click dice or enemy and any dice is already selected, only consider enemy + if (closestDice && closestEnemy && this.diceIsActive) { + closestDice = null; + } + + let clickedDice = false; + this.changeEmphasisDice(closestDice); + if (this.justClickedLMB) { + if (closestDice && (!closestEnemy || !this.diceIsActive)) { + clickedDice = true; + this.changeActiveDice(closestDice); + } + } + for (let enemy of this.allEnemies) { + if (closestEnemy && enemy === closestEnemy) { + enemy.showHint(); + } else { + enemy.hideHint(); + } + } + + if (closestEnemy && this.diceIsActive && !clickedDice) { + const activeDice = this.getActiveDice(); + let diceToTargetVec = closestEnemy.getPosVec().subtract(activeDice.getPosVec()); + + this.targetArrow.setPosVec(closestEnemy.getPosVec()); + this.targetArrow.rotation = diceToTargetVec.angle(); + if (this.justClickedLMB) { + const activeDiceVal = this.getDiceVal(activeDice); + this.throwDice(activeDice, closestEnemy); + //closestEnemy.attack(activeDiceVal); + //this.removeDice(activeDice); + } + } else { + this.targetArrow.x = -6000 + } + + if (!this.finished) { + let aliveEnemies = this.aliveEnemiesCount(); + if (aliveEnemies < 1) { + this.nowFinished(true); + } else if (this.unusedDiceNum() < 1) { + this.nowFinished(false); + } + } + } + + aliveEnemiesCount() { + let aliveEnemies = 0; + for (let e of this.allEnemies) { + if (e.alive) { + aliveEnemies += 1; + } + } + return aliveEnemies; + } + + aliveEnemyNames() { + let names = []; + for (let e of this.allEnemies) { + if (e.alive) { + names.push(e.enemyName); + } + } + return names; + } + + nowFinished(won, fromTimer = false) { + if (this.finished) { + return; + } + this.victory = won; + if (!won) { + battleBags[this.tier].push([...this.battle]); + battleBags[this.teir] = arrShuffle(battleBags[this.tier]); + } + this.finished = true; + const timeLeft = this.stopwatch.getTimeLeft(); + + const waitForText = Math.min(timeLeft, 200); + if (waitForText <= 0) { + this.showEndText(won); + } else { + setTimeout( () => this.showEndText(won), waitForText); + } + + if (fromTimer) { + setTimeout(() => this.timeUp(), 300); + } else { + const continueDelay = Math.max(Math.min(2200, timeLeft), 300); + setTimeout(() => this.timeUp(), continueDelay); + } + } + + showEndText(won) { + const c = this.screenCenterPos(); + if (won) { + this.add.image(c.x, c.y, "yes"); + } else { + this.add.image(c.x, c.y, "ohno"); + } + } + + getDiceVal(dice) { + return parseInt(dice.frame.name) + 1; + } + + setupRolledDice(dice) { + dice.tint = 0xdddddd; + dice.emphasis = false; + dice.activeDice = false; + if (this.stopwatch.getTimeLeft() < 3500 && !this.usedGrace) { + this.grace(dice); + } + } + + grace(dice) { + const unusedCount = this.unusedDiceNum(); + const enemyNames = this.aliveEnemyNames(); + let rolledNumbers = []; + for (let otherDice of this.allDice) { + if (otherDice === dice || otherDice.throwing) { + continue; + } + rolledNumbers.push(this.getDiceVal(otherDice)); + } + if (enemyNames.length < 1) { + return; + } + if (enemyNames.includes("BombGnome")) { + if (enemyNames.length > unusedCount) { + this.usedGrace = true; + this.setDiceTo(dice, 6); + } else if (unusedCount === 1) { + this.usedGrace = true; + this.setDiceTo(dice, 2); + } + } else { + if (enemyNames.includes("ShieldGoblin")) { + this.usedGrace = true; + this.setDiceTo(dice, 3); + } else if (unusedCount === 1) { + this.usedGrace = true; + this.setDiceTo(dice, Math.floor(Math.random() * 4) + 3); + } else if (unusedCount - rolledNumbers.length === 1) { + let greaterThanTwo = false; + for (let rolledNum of rolledNumbers) { + if (rolledNum > 2) { + greaterThanTwo = true; + } + } + if (!greaterThanTwo) { + this.usedGrace = true; + this.setDiceTo(dice, 3); + } + } + } + + if (this.usedGrace) { + console.log("halleluyah"); + } + } + + setDiceTo(dice, toNum) { + dice.setFrame(toNum - 1); + } + + throwDice(dice, atEnemy) { + if (!atEnemy || !atEnemy.alive) { + return; + } + if (dice.activeDice) { + this.clearDiceActive(dice); + } + atEnemy.preattack(this.getDiceVal(dice)); + dice.throwing = true; + dice.throwingAt = atEnemy; + dice.throwFrom = dice.getPosVec(); + dice.throwTo = atEnemy.getPosVec(); + dice.throwTimer = 0; + dice.throwTotalTime = 80; + } + + updateThrownDice(dice, dt) { + dice.throwTimer += dt; + if (dice.throwTimer > dice.throwTotalTime) { + this.finishThrownDice(dice); + } else { + const progress = dice.throwTimer / dice.throwTotalTime; + dice.scale = Math.max(0.1, 1 - progress); + const delta = dice.throwTo.clone().subtract(dice.throwFrom); + dice.setPosVec(dice.throwFrom); + dice.addPosVec(delta.scale(M.Ease.Cubic.Out(progress))); + } + } + + finishThrownDice(dice) { + dice.throwingAt.attack(this.getDiceVal(dice)); + this.removeDice(dice); + } + + removeDice(dice) { + const diceIndex = this.allDice.indexOf(dice); + if (diceIndex < 0) { + return; + } + this.clearDiceActive(dice); + this.allDice.splice(diceIndex, 1); + dice.destroy(); + this.numDice = this.allDice.length; + } + + setDiceEmphasis(dice) { + if (dice.emphasis) { + return; + } + dice.emphasis = true; + dice.clearTint(); + dice.y -= 4; + } + clearDiceEmphasis(dice) { + if (!dice.emphasis) { + return; + } + dice.emphasis = false; + dice.y += 4; + if (!dice.activeDice) { + dice.tint = 0xdddddd; + } + } + + isOverReroll(pointerPos) { + const relPointer = pointerPos.clone().subtract(this.reroll.getPosVec()); + if (Math.abs(relPointer.x) * 2 > this.reroll.width) { + return false; + } else if (Math.abs(relPointer.y) * 2 > this.reroll.height) { + return false; + } + return true; + } + + setDiceActive(dice) { + dice.activeDice = true; + dice.clearTint(); + this.circle.setPosVec(dice.getPosVec()); + if (dice.emphasis) { + this.circle.y += 4; + } + } + clearDiceActive(dice) { + dice.activeDice = false; + this.circle.x = -6000; + this.diceIsActive = false; + } + + changeEmphasisDice(newEmphasis) { + for (let dice of this.allDice) { + if (dice === newEmphasis) { + this.setDiceEmphasis(dice); + } else if (dice.emphasis) { + this.clearDiceEmphasis(dice); + } + } + } + + changeActiveDice(newActiveDice) { + for (let dice of this.allDice) { + if (dice !== newActiveDice) { + this.clearDiceActive(dice); + } + } + this.diceIsActive = true; + this.setDiceActive(newActiveDice); + } + + enemyExploded(explodingEnemy) { + this.sound.play('boom_sfx'); + const enemyIndex = this.allEnemies.indexOf(explodingEnemy); + if (enemyIndex === -1) { + console.log("exploding enemy not found in allEnemies"); + return; + } + if (enemyIndex > 0) { + const leftEnemy = this.allEnemies[enemyIndex - 1]; + if (!leftEnemy.gone) { + leftEnemy.attack(8); + } + } + if (enemyIndex < this.allEnemies.length - 1) { + const rightEnemy = this.allEnemies[enemyIndex + 1]; + if (!rightEnemy.gone) { + rightEnemy.attack(8); + } + } + } + + preExplode(explodingEnemy) { + const enemyIndex = this.allEnemies.indexOf(explodingEnemy); + if (enemyIndex === -1) { + console.log("pre-exploding enemy not found in allEnemies"); + return; + } + if (enemyIndex > 0) { + const leftEnemy = this.allEnemies[enemyIndex - 1]; + if (leftEnemy.alive) { + leftEnemy.preattack(8); + } + } + if (enemyIndex < this.allEnemies.length - 1) { + const rightEnemy = this.allEnemies[enemyIndex + 1]; + if (rightEnemy.alive) { + rightEnemy.preattack(8); + } + } + } + + getActiveDice() { + if (!this.diceIsActive || this.allDice.length === 0) { + return null; + } + for (let dice of this.allDice) { + if (dice.activeDice) { + return dice; + } + } + return null; + } + + clearUnusedDice() { + const oldDice = this.allDice; + this.allDice = []; + for (let dice of oldDice) { + if (dice.throwing) { + this.allDice.push(dice); + continue; + } + if (dice.activeDice) { + this.clearDiceActive(dice); + } + dice.destroy(); + } + } + + unusedDiceNum() { + let num = 0; + for (let dice of this.allDice) { + if (!dice.throwing) { + num += 1; + } + } + return num; + } + + rerollUnused() { + let unusedNum = this.unusedDiceNum(); + this.clearUnusedDice(); + this.rollDice(unusedNum); + } + + rollDice(num) { + this.sound.play('fwoof_sfx'); + let diceSpacing = 52; + if (num > 1) { + diceSpacing = Math.min(diceSpacing, Math.round(154 / (num - 1))); + } + const center = this.screenCenterPos(); + for (let i = 0; i < num; i++) { + const xPos = Math.round(center.x - ((num - 1) * diceSpacing * 0.5) + (i * diceSpacing) - 10); + const dice = this.add.sprite(xPos, center.y + 52, 'dices'); + this.allDice.push(dice); + + dice.addPosVec(new M.Vec2(randOffset(3), randOffset(11))); + dice.roll_target_pos = dice.getPosVec(); + dice.addPosVec(new M.Vec2(randOffset(16), 30)); + dice.roll_from_pos = dice.getPosVec(); + + dice.rolling = true; + dice.roll_anim_timer = 0; + dice.roll_timer = 0; + dice.total_roll_time = ROLL_TOTAL + Math.random() * 400; + } + } + + timeUp() { + let difficulty = this.difficultyVal + if (battleBags[this.tier].length < 1) { + difficulty += 1; + M.gameDifficulty = difficulty + } + M.gameEnd(this.victory); + this.scene.switch("wait"); + this.scene.stop(); + //this.scene.restart({difficulty: difficulty}); + } +} |
