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