summaryrefslogtreecommitdiff
path: root/2026/games_submissions/cammymoop/js/dice_game.js
diff options
context:
space:
mode:
authorLLLL Colonq <llll@colonq>2026-07-21 13:53:20 -0400
committerLLLL Colonq <llll@colonq>2026-07-21 13:53:20 -0400
commit3ec03ba57474aa4320cb7901980b55706adbc111 (patch)
tree97fcf8d1825cd4bdda65c4b516853dfda60f3610 /2026/games_submissions/cammymoop/js/dice_game.js
parent47c0d930736bb5247a7f7ba05c7d41e33038102a (diff)
Update
Diffstat (limited to '2026/games_submissions/cammymoop/js/dice_game.js')
-rw-r--r--2026/games_submissions/cammymoop/js/dice_game.js622
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..23dd59e
--- /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, this.difficultyVal);
+
+ 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});
+ }
+}