wip
This commit is contained in:
parent
14aedc17ae
commit
14d4ffaa36
|
@ -58,6 +58,7 @@
|
||||||
"rollup": "4.9.1",
|
"rollup": "4.9.1",
|
||||||
"sanitize-html": "2.11.0",
|
"sanitize-html": "2.11.0",
|
||||||
"sass": "1.69.5",
|
"sass": "1.69.5",
|
||||||
|
"seedrandom": "^3.0.5",
|
||||||
"shiki": "0.14.7",
|
"shiki": "0.14.7",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
|
|
|
@ -98,6 +98,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div>MAX CHAIN: <MkNumber :value="maxCombo"/></div>
|
<div>MAX CHAIN: <MkNumber :value="maxCombo"/></div>
|
||||||
<div class="_buttonsCenter">
|
<div class="_buttonsCenter">
|
||||||
<MkButton primary rounded @click="restart">Restart</MkButton>
|
<MkButton primary rounded @click="restart">Restart</MkButton>
|
||||||
|
<MkButton primary rounded @click="replay">Replay</MkButton>
|
||||||
<MkButton primary rounded @click="share">Share</MkButton>
|
<MkButton primary rounded @click="share">Share</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -401,6 +402,7 @@ const GAME_HEIGHT = 600;
|
||||||
let viewScale = 1;
|
let viewScale = 1;
|
||||||
let game: DropAndFusionGame;
|
let game: DropAndFusionGame;
|
||||||
let containerElRect: DOMRect | null = null;
|
let containerElRect: DOMRect | null = null;
|
||||||
|
let seed: string;
|
||||||
|
|
||||||
const containerEl = shallowRef<HTMLElement>();
|
const containerEl = shallowRef<HTMLElement>();
|
||||||
const canvasEl = shallowRef<HTMLCanvasElement>();
|
const canvasEl = shallowRef<HTMLCanvasElement>();
|
||||||
|
@ -418,18 +420,21 @@ const gameOver = ref(false);
|
||||||
const gameStarted = ref(false);
|
const gameStarted = ref(false);
|
||||||
const highScore = ref<number | null>(null);
|
const highScore = ref<number | null>(null);
|
||||||
const showConfig = ref(false);
|
const showConfig = ref(false);
|
||||||
|
const replaying = ref(false);
|
||||||
const mute = ref(false);
|
const mute = ref(false);
|
||||||
const bgmVolume = ref(defaultStore.state.dropAndFusion.bgmVolume);
|
const bgmVolume = ref(defaultStore.state.dropAndFusion.bgmVolume);
|
||||||
const sfxVolume = ref(defaultStore.state.dropAndFusion.sfxVolume);
|
const sfxVolume = ref(defaultStore.state.dropAndFusion.sfxVolume);
|
||||||
|
|
||||||
function onClick(ev: MouseEvent) {
|
function onClick(ev: MouseEvent) {
|
||||||
if (!containerElRect) return;
|
if (!containerElRect) return;
|
||||||
|
if (replaying.value) return;
|
||||||
const x = (ev.clientX - containerElRect.left) / viewScale;
|
const x = (ev.clientX - containerElRect.left) / viewScale;
|
||||||
game.drop(x);
|
game.drop(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onTouchend(ev: TouchEvent) {
|
function onTouchend(ev: TouchEvent) {
|
||||||
if (!containerElRect) return;
|
if (!containerElRect) return;
|
||||||
|
if (replaying.value) return;
|
||||||
const x = (ev.changedTouches[0].clientX - containerElRect.left) / viewScale;
|
const x = (ev.changedTouches[0].clientX - containerElRect.left) / viewScale;
|
||||||
game.drop(x);
|
game.drop(x);
|
||||||
}
|
}
|
||||||
|
@ -467,6 +472,29 @@ function restart() {
|
||||||
gameStarted.value = false;
|
gameStarted.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function replay() {
|
||||||
|
replaying.value = true;
|
||||||
|
const logs = game.getLogs();
|
||||||
|
game.dispose();
|
||||||
|
game = new DropAndFusionGame({
|
||||||
|
width: GAME_WIDTH,
|
||||||
|
height: GAME_HEIGHT,
|
||||||
|
canvas: canvasEl.value!,
|
||||||
|
seed: seed,
|
||||||
|
sfxVolume: mute.value ? 0 : sfxVolume.value,
|
||||||
|
...(
|
||||||
|
gameMode.value === 'normal' ? {
|
||||||
|
monoDefinitions: NORAML_MONOS,
|
||||||
|
} : {
|
||||||
|
monoDefinitions: SQUARE_MONOS,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
});
|
||||||
|
os.promiseDialog(game.load(), async () => {
|
||||||
|
game.start(logs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function attachGameEvents() {
|
function attachGameEvents() {
|
||||||
game.addListener('changeScore', value => {
|
game.addListener('changeScore', value => {
|
||||||
score.value = value;
|
score.value = value;
|
||||||
|
@ -551,10 +579,13 @@ async function start() {
|
||||||
highScore.value = null;
|
highScore.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
seed = Date.now().toString();
|
||||||
|
|
||||||
game = new DropAndFusionGame({
|
game = new DropAndFusionGame({
|
||||||
width: GAME_WIDTH,
|
width: GAME_WIDTH,
|
||||||
height: GAME_HEIGHT,
|
height: GAME_HEIGHT,
|
||||||
canvas: canvasEl.value!,
|
canvas: canvasEl.value!,
|
||||||
|
seed: seed,
|
||||||
sfxVolume: mute.value ? 0 : sfxVolume.value,
|
sfxVolume: mute.value ? 0 : sfxVolume.value,
|
||||||
...(
|
...(
|
||||||
gameMode.value === 'normal' ? {
|
gameMode.value === 'normal' ? {
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import * as Matter from 'matter-js';
|
import * as Matter from 'matter-js';
|
||||||
|
import seedrandom from 'seedrandom';
|
||||||
import * as sound from '@/scripts/sound.js';
|
import * as sound from '@/scripts/sound.js';
|
||||||
|
|
||||||
export type Mono = {
|
export type Mono = {
|
||||||
|
@ -20,6 +21,15 @@ export type Mono = {
|
||||||
spriteScale: number;
|
spriteScale: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Log = {
|
||||||
|
frame: number;
|
||||||
|
operation: 'drop';
|
||||||
|
x: number;
|
||||||
|
} | {
|
||||||
|
frame: number;
|
||||||
|
operation: 'hold';
|
||||||
|
};
|
||||||
|
|
||||||
export class DropAndFusionGame extends EventEmitter<{
|
export class DropAndFusionGame extends EventEmitter<{
|
||||||
changeScore: (newScore: number) => void;
|
changeScore: (newScore: number) => void;
|
||||||
changeCombo: (newCombo: number) => void;
|
changeCombo: (newCombo: number) => void;
|
||||||
|
@ -35,18 +45,21 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
public readonly DROP_INTERVAL = 500;
|
public readonly DROP_INTERVAL = 500;
|
||||||
public readonly PLAYAREA_MARGIN = 25;
|
public readonly PLAYAREA_MARGIN = 25;
|
||||||
private STOCK_MAX = 4;
|
private STOCK_MAX = 4;
|
||||||
|
private METTER_ENGINE_UPDATE_DELTA = 1000 / 60; // 60fps
|
||||||
private loaded = false;
|
private loaded = false;
|
||||||
|
private frame = 0;
|
||||||
private engine: Matter.Engine;
|
private engine: Matter.Engine;
|
||||||
private render: Matter.Render;
|
private render: Matter.Render;
|
||||||
private runner: Matter.Runner;
|
private matterEngineUpdateRaf: ReturnType<typeof requestAnimationFrame> | null = null;
|
||||||
private overflowCollider: Matter.Body;
|
private overflowCollider: Matter.Body;
|
||||||
private isGameOver = false;
|
private isGameOver = false;
|
||||||
|
|
||||||
private gameWidth: number;
|
private gameWidth: number;
|
||||||
private gameHeight: number;
|
private gameHeight: number;
|
||||||
private monoDefinitions: Mono[] = [];
|
private monoDefinitions: Mono[] = [];
|
||||||
private monoTextures: Record<string, Blob> = {};
|
private monoTextures: Record<string, Blob> = {};
|
||||||
private monoTextureUrls: Record<string, string> = {};
|
private monoTextureUrls: Record<string, string> = {};
|
||||||
|
private rng: () => number;
|
||||||
|
private logs: Log[] = [];
|
||||||
|
|
||||||
private sfxVolume = 1;
|
private sfxVolume = 1;
|
||||||
|
|
||||||
|
@ -87,13 +100,17 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
monoDefinitions: Mono[];
|
monoDefinitions: Mono[];
|
||||||
|
seed: string;
|
||||||
sfxVolume?: number;
|
sfxVolume?: number;
|
||||||
}) {
|
}) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.tick = this.tick.bind(this);
|
||||||
|
|
||||||
this.gameWidth = opts.width;
|
this.gameWidth = opts.width;
|
||||||
this.gameHeight = opts.height;
|
this.gameHeight = opts.height;
|
||||||
this.monoDefinitions = opts.monoDefinitions;
|
this.monoDefinitions = opts.monoDefinitions;
|
||||||
|
this.rng = seedrandom(opts.seed);
|
||||||
|
|
||||||
if (opts.sfxVolume) {
|
if (opts.sfxVolume) {
|
||||||
this.sfxVolume = opts.sfxVolume;
|
this.sfxVolume = opts.sfxVolume;
|
||||||
|
@ -129,9 +146,6 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
|
|
||||||
Matter.Render.run(this.render);
|
Matter.Render.run(this.render);
|
||||||
|
|
||||||
this.runner = Matter.Runner.create();
|
|
||||||
Matter.Runner.run(this.runner, this.engine);
|
|
||||||
|
|
||||||
this.engine.world.bodies = [];
|
this.engine.world.bodies = [];
|
||||||
|
|
||||||
//#region walls
|
//#region walls
|
||||||
|
@ -244,7 +258,7 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
} else {
|
} else {
|
||||||
//const VELOCITY = 30;
|
//const VELOCITY = 30;
|
||||||
//for (let i = 0; i < 10; i++) {
|
//for (let i = 0; i < 10; i++) {
|
||||||
// const body = createBody(FRUITS.find(x => x.level === (1 + Math.floor(Math.random() * 3)))!, x + ((Math.random() * VELOCITY) - (VELOCITY / 2)), y + ((Math.random() * VELOCITY) - (VELOCITY / 2)));
|
// const body = createBody(FRUITS.find(x => x.level === (1 + Math.floor(this.rng() * 3)))!, x + ((this.rng() * VELOCITY) - (VELOCITY / 2)), y + ((this.rng() * VELOCITY) - (VELOCITY / 2)));
|
||||||
// Matter.Composite.add(world, body);
|
// Matter.Composite.add(world, body);
|
||||||
// bodies.push(body);
|
// bodies.push(body);
|
||||||
//}
|
//}
|
||||||
|
@ -257,7 +271,8 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
|
|
||||||
private gameOver() {
|
private gameOver() {
|
||||||
this.isGameOver = true;
|
this.isGameOver = true;
|
||||||
Matter.Runner.stop(this.runner);
|
if (this.matterEngineUpdateRaf) window.cancelAnimationFrame(this.matterEngineUpdateRaf);
|
||||||
|
this.matterEngineUpdateRaf = null;
|
||||||
this.emit('gameOver');
|
this.emit('gameOver');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,13 +307,13 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
return Promise.all(this.monoDefinitions.map(x => loadSingleMonoTexture(x, this)));
|
return Promise.all(this.monoDefinitions.map(x => loadSingleMonoTexture(x, this)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public start() {
|
public start(logs?: Log[]) {
|
||||||
if (!this.loaded) throw new Error('game is not loaded yet');
|
if (!this.loaded) throw new Error('game is not loaded yet');
|
||||||
|
|
||||||
for (let i = 0; i < this.STOCK_MAX; i++) {
|
for (let i = 0; i < this.STOCK_MAX; i++) {
|
||||||
this.stock.push({
|
this.stock.push({
|
||||||
id: Math.random().toString(),
|
id: this.rng().toString(),
|
||||||
mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)],
|
mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(this.rng() * this.monoDefinitions.filter(x => x.dropCandidate).length)],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.emit('changeStock', this.stock);
|
this.emit('changeStock', this.stock);
|
||||||
|
@ -354,6 +369,47 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
this.combo = 0;
|
this.combo = 0;
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
|
if (logs) {
|
||||||
|
let playingFrame = 0;
|
||||||
|
|
||||||
|
const playTick = () => {
|
||||||
|
playingFrame++;
|
||||||
|
const log = logs.find(x => x.frame === playingFrame - 1);
|
||||||
|
if (log) {
|
||||||
|
switch (log.operation) {
|
||||||
|
case 'drop': {
|
||||||
|
this.drop(log.x);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'hold': {
|
||||||
|
this.hold();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Matter.Engine.update(this.engine, this.METTER_ENGINE_UPDATE_DELTA);
|
||||||
|
|
||||||
|
window.requestAnimationFrame(playTick);
|
||||||
|
};
|
||||||
|
|
||||||
|
playTick();
|
||||||
|
} else {
|
||||||
|
this.tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLogs() {
|
||||||
|
return this.logs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private tick() {
|
||||||
|
this.frame++;
|
||||||
|
Matter.Engine.update(this.engine, this.METTER_ENGINE_UPDATE_DELTA);
|
||||||
|
this.matterEngineUpdateRaf = window.requestAnimationFrame(this.tick);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async load() {
|
public async load() {
|
||||||
|
@ -391,13 +447,18 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
|
|
||||||
const head = this.stock.shift()!;
|
const head = this.stock.shift()!;
|
||||||
this.stock.push({
|
this.stock.push({
|
||||||
id: Math.random().toString(),
|
id: this.rng().toString(),
|
||||||
mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)],
|
mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(this.rng() * this.monoDefinitions.filter(x => x.dropCandidate).length)],
|
||||||
});
|
});
|
||||||
this.emit('changeStock', this.stock);
|
this.emit('changeStock', this.stock);
|
||||||
|
|
||||||
const x = Math.min(this.gameWidth - this.PLAYAREA_MARGIN - (head.mono.size / 2), Math.max(this.PLAYAREA_MARGIN + (head.mono.size / 2), _x));
|
const x = Math.round(Math.min(this.gameWidth - this.PLAYAREA_MARGIN - (head.mono.size / 2), Math.max(this.PLAYAREA_MARGIN + (head.mono.size / 2), _x)));
|
||||||
const body = this.createBody(head.mono, x, 50 + head.mono.size / 2);
|
const body = this.createBody(head.mono, x, 50 + head.mono.size / 2);
|
||||||
|
this.logs.push({
|
||||||
|
frame: this.frame,
|
||||||
|
operation: 'drop',
|
||||||
|
x,
|
||||||
|
});
|
||||||
Matter.Composite.add(this.engine.world, body);
|
Matter.Composite.add(this.engine.world, body);
|
||||||
this.activeBodyIds.push(body.id);
|
this.activeBodyIds.push(body.id);
|
||||||
this.latestDroppedBodyId = body.id;
|
this.latestDroppedBodyId = body.id;
|
||||||
|
@ -416,6 +477,11 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
public hold() {
|
public hold() {
|
||||||
if (this.isGameOver) return;
|
if (this.isGameOver) return;
|
||||||
|
|
||||||
|
this.logs.push({
|
||||||
|
frame: this.frame,
|
||||||
|
operation: 'hold',
|
||||||
|
});
|
||||||
|
|
||||||
if (this.holding) {
|
if (this.holding) {
|
||||||
const head = this.stock.shift()!;
|
const head = this.stock.shift()!;
|
||||||
this.stock.unshift(this.holding);
|
this.stock.unshift(this.holding);
|
||||||
|
@ -426,8 +492,8 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
const head = this.stock.shift()!;
|
const head = this.stock.shift()!;
|
||||||
this.holding = head;
|
this.holding = head;
|
||||||
this.stock.push({
|
this.stock.push({
|
||||||
id: Math.random().toString(),
|
id: this.rng().toString(),
|
||||||
mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)],
|
mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(this.rng() * this.monoDefinitions.filter(x => x.dropCandidate).length)],
|
||||||
});
|
});
|
||||||
this.emit('changeHolding', this.holding);
|
this.emit('changeHolding', this.holding);
|
||||||
this.emit('changeStock', this.stock);
|
this.emit('changeStock', this.stock);
|
||||||
|
@ -440,8 +506,9 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
|
|
||||||
public dispose() {
|
public dispose() {
|
||||||
if (this.comboIntervalId) window.clearInterval(this.comboIntervalId);
|
if (this.comboIntervalId) window.clearInterval(this.comboIntervalId);
|
||||||
|
if (this.matterEngineUpdateRaf) window.cancelAnimationFrame(this.matterEngineUpdateRaf);
|
||||||
|
this.matterEngineUpdateRaf = null;
|
||||||
Matter.Render.stop(this.render);
|
Matter.Render.stop(this.render);
|
||||||
Matter.Runner.stop(this.runner);
|
|
||||||
Matter.World.clear(this.engine.world, false);
|
Matter.World.clear(this.engine.world, false);
|
||||||
Matter.Engine.clear(this.engine);
|
Matter.Engine.clear(this.engine);
|
||||||
}
|
}
|
||||||
|
|
|
@ -787,6 +787,9 @@ importers:
|
||||||
sass:
|
sass:
|
||||||
specifier: 1.69.5
|
specifier: 1.69.5
|
||||||
version: 1.69.5
|
version: 1.69.5
|
||||||
|
seedrandom:
|
||||||
|
specifier: ^3.0.5
|
||||||
|
version: 3.0.5
|
||||||
shiki:
|
shiki:
|
||||||
specifier: 0.14.7
|
specifier: 0.14.7
|
||||||
version: 0.14.7
|
version: 0.14.7
|
||||||
|
@ -7401,7 +7404,7 @@ packages:
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@swc/core': ^1.2.66
|
'@swc/core': ^1.2.66
|
||||||
chokidar: ^3.5.1
|
chokidar: 3.5.3
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
chokidar:
|
chokidar:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
Loading…
Reference in a new issue