fixes, features
This commit is contained in:
parent
d0434ffebb
commit
3fdc4e805c
@ -12,8 +12,14 @@
|
||||
<Bubble v-if="traversing" :bubble="traversing" :radius="radius" />
|
||||
|
||||
<Shooter v-if="next" :center="next" :radius="radius" :angle="angle" />
|
||||
<ScoreDisplay :score="score" />
|
||||
<GameOver v-if="gameOver" :score="score">{{
|
||||
<ScoreDisplay
|
||||
:score="score"
|
||||
:hiscore="hiscore"
|
||||
:shots-left="shotsLeft"
|
||||
:streak="streak"
|
||||
:max-streak="maxStreak"
|
||||
/>
|
||||
<GameOver v-if="gameOver" :score="score" :streak="maxStreak">{{
|
||||
victory ? 'You won!' : 'Game over.'
|
||||
}}</GameOver>
|
||||
</div>
|
||||
@ -38,9 +44,12 @@ const radius = ref(32);
|
||||
const diameter = computed(() => radius.value * 2);
|
||||
const colors = ref(5);
|
||||
const angle = ref(90);
|
||||
const streak = ref(0);
|
||||
const maxStreak = ref(0);
|
||||
const shotsLeft = ref(4);
|
||||
const offset = ref(0);
|
||||
const score = ref(0);
|
||||
const hiscore = ref(0);
|
||||
const fitment = computed(() => Math.floor(width / diameter.value));
|
||||
|
||||
const gameOver = ref(false);
|
||||
@ -69,23 +78,36 @@ function createRow(column: number, generate = false) {
|
||||
generate ? field.value.push(...entries) : field.value.unshift(...entries);
|
||||
}
|
||||
|
||||
function addRow() {
|
||||
field.value.forEach((item) => {
|
||||
item.y += diameter.value - 8;
|
||||
});
|
||||
offset.value += 1;
|
||||
createRow(0);
|
||||
|
||||
function checkHeightLoss() {
|
||||
const lowestPart = field.value.reduce<number>(
|
||||
(last, current) => (last < current.y ? current.y : last),
|
||||
0
|
||||
);
|
||||
|
||||
if (lowestPart > height - diameter.value * 2) {
|
||||
gameOver.value = true;
|
||||
alert('Game over!');
|
||||
}
|
||||
}
|
||||
|
||||
function saveHiscore(score: number) {
|
||||
const currentHigh = Number(localStorage.getItem('highscore'));
|
||||
if ((currentHigh && currentHigh < score) || !currentHigh) {
|
||||
localStorage.setItem('highscore', score.toString());
|
||||
hiscore.value = score;
|
||||
}
|
||||
}
|
||||
|
||||
function addRow() {
|
||||
field.value.forEach((item) => {
|
||||
item.y += diameter.value - 8;
|
||||
});
|
||||
|
||||
offset.value += 1;
|
||||
|
||||
createRow(0);
|
||||
checkHeightLoss();
|
||||
}
|
||||
|
||||
function generate() {
|
||||
for (let column = 0; column < 5; column++) {
|
||||
createRow(column, true);
|
||||
@ -97,9 +119,12 @@ function reset() {
|
||||
field.value = [];
|
||||
offset.value = 0;
|
||||
generate();
|
||||
saveHiscore(score.value);
|
||||
score.value = 0;
|
||||
gameOver.value = false;
|
||||
victory.value = false;
|
||||
streak.value = 0;
|
||||
maxStreak.value = 0;
|
||||
determineNext();
|
||||
}
|
||||
|
||||
@ -220,6 +245,30 @@ function determineImmediateNeighbors(item: BubbleType) {
|
||||
return neighbors;
|
||||
}
|
||||
|
||||
function accountForAllCeilingAttached() {
|
||||
let accountedFor: BubbleType[] = [];
|
||||
|
||||
const startingPoint = field.value.filter(
|
||||
(item) => item.y === 0 && !item.exiting
|
||||
);
|
||||
const accountNeighbors = (item: BubbleType) => {
|
||||
if (!accountedFor.includes(item)) {
|
||||
accountedFor.push(item);
|
||||
const neighbors = determineImmediateNeighbors(item);
|
||||
if (neighbors.length) {
|
||||
for (const neighbor of neighbors) {
|
||||
if (accountedFor.includes(neighbor)) continue;
|
||||
accountNeighbors(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
for (const item of startingPoint) {
|
||||
accountNeighbors(item);
|
||||
}
|
||||
return accountedFor;
|
||||
}
|
||||
|
||||
function checkForPops(item: BubbleType, friendlies: BubbleType[] = []) {
|
||||
const neighbors = determineImmediateNeighbors(item);
|
||||
const same = neighbors
|
||||
@ -237,33 +286,6 @@ function checkForPops(item: BubbleType, friendlies: BubbleType[] = []) {
|
||||
return friendlies;
|
||||
}
|
||||
|
||||
function findIsolatedGroups(groups: BubbleType[][] = []) {
|
||||
for (const item of field.value) {
|
||||
if (groups.some((group) => group.includes(item))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const neighbors = determineImmediateNeighbors(item);
|
||||
if (neighbors.length) {
|
||||
let setgroup;
|
||||
for (const neighbor of neighbors) {
|
||||
if (groups.some((group) => group.includes(neighbor))) {
|
||||
setgroup = groups.find((item) => item.includes(neighbor));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (setgroup) {
|
||||
setgroup.push(item);
|
||||
} else {
|
||||
groups.push([item, ...neighbors]);
|
||||
}
|
||||
} else {
|
||||
groups.push([item]);
|
||||
}
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
function animateRemovals() {
|
||||
const toRemove = field.value.filter((item) => item.exiting);
|
||||
if (!toRemove.length) return;
|
||||
@ -285,24 +307,20 @@ function animateRemovals() {
|
||||
}
|
||||
|
||||
function determineIsolated() {
|
||||
const groups = findIsolatedGroups([]);
|
||||
let failedGroups = groups.filter(
|
||||
(group) => !group.some((item) => item.y === 0)
|
||||
);
|
||||
const accountedFor = accountForAllCeilingAttached();
|
||||
|
||||
if (failedGroups.length) {
|
||||
let fallCount = 0;
|
||||
field.value
|
||||
.filter((item) => failedGroups.some((group) => group.includes(item)))
|
||||
.forEach((i) => {
|
||||
i.exiting = true;
|
||||
fallCount++;
|
||||
});
|
||||
let fallCount = 0;
|
||||
field.value
|
||||
.filter((item) => !accountedFor.includes(item))
|
||||
.forEach((i) => {
|
||||
i.exiting = true;
|
||||
fallCount++;
|
||||
});
|
||||
|
||||
score.value += fallCount * 10;
|
||||
}
|
||||
score.value += fallCount * 10;
|
||||
|
||||
animateRemovals();
|
||||
if (fallCount > 0) animateRemovals();
|
||||
return fallCount;
|
||||
}
|
||||
|
||||
function stopDead() {
|
||||
@ -320,9 +338,15 @@ function stopDead() {
|
||||
if (chain.length >= 3) {
|
||||
score.value += chain.length;
|
||||
field.value = field.value.filter((item) => !chain.includes(item));
|
||||
determineIsolated();
|
||||
const dropped = determineIsolated();
|
||||
streak.value += chain.length + dropped;
|
||||
|
||||
if (streak.value > maxStreak.value) {
|
||||
maxStreak.value = streak.value;
|
||||
}
|
||||
} else {
|
||||
shotsLeft.value -= 1;
|
||||
streak.value = 0;
|
||||
}
|
||||
|
||||
if (shotsLeft.value < 1) {
|
||||
@ -335,7 +359,10 @@ function stopDead() {
|
||||
if (!field.value.filter((x) => !x.exiting).length) {
|
||||
victory.value = true;
|
||||
gameOver.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
checkHeightLoss();
|
||||
}
|
||||
|
||||
let steps = 0;
|
||||
@ -402,6 +429,12 @@ function normalize(vector: { x: number; y: number }) {
|
||||
onMounted(() => {
|
||||
generate();
|
||||
determineNext();
|
||||
|
||||
const score = Number(localStorage.getItem('highscore'));
|
||||
if (score) {
|
||||
hiscore.value = score;
|
||||
}
|
||||
|
||||
function determineVectors(e: MouseEvent) {
|
||||
const x = e.clientX - fieldRef.value!.offsetLeft;
|
||||
const y = e.clientY - fieldRef.value!.offsetTop;
|
||||
|
@ -2,10 +2,11 @@
|
||||
<div class="game-over">
|
||||
<h2><slot></slot></h2>
|
||||
<p>Score: {{ score }}</p>
|
||||
<p>Best streak: {{ streak }}</p>
|
||||
<small>Click to restart</small>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ score: number }>();
|
||||
defineProps<{ score: number; streak: number }>();
|
||||
</script>
|
||||
|
@ -1,7 +1,23 @@
|
||||
<template>
|
||||
<div class="game-score">Score: {{ score }}</div>
|
||||
<div class="game-score">
|
||||
<span>
|
||||
<span>Score: {{ score }}</span>
|
||||
<span>Hiscore: {{ hiscore }}</span>
|
||||
</span>
|
||||
<span>
|
||||
<span>Current streak: {{ streak }}</span>
|
||||
<span>Max streak: {{ maxStreak }}</span>
|
||||
</span>
|
||||
<span>Shots left: {{ shotsLeft }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ score: number }>();
|
||||
defineProps<{
|
||||
score: number;
|
||||
hiscore: number;
|
||||
shotsLeft: number;
|
||||
streak: number;
|
||||
maxStreak: number;
|
||||
}>();
|
||||
</script>
|
||||
|
@ -51,8 +51,15 @@ body {
|
||||
}
|
||||
|
||||
&-score {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
position: absolute;
|
||||
bottom: -1.5rem;
|
||||
bottom: -3rem;
|
||||
& > span {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
&-pointer {
|
||||
@ -88,6 +95,15 @@ body {
|
||||
font-size: 2rem;
|
||||
pointer-events: none;
|
||||
z-index: 4;
|
||||
|
||||
p:first-of-type {
|
||||
margin-bottom: 0;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
small {
|
||||
color: rgb(9, 255, 0);
|
||||
}
|
||||
}
|
||||
|
||||
&-object {
|
||||
|
Loading…
Reference in New Issue
Block a user