bugfixes, offline mode

This commit is contained in:
azykov@mail.ru 2026-02-07 21:27:29 +03:00
parent 9af9c7867a
commit 9f544a32ab
10 changed files with 71 additions and 74 deletions

View File

@ -74,6 +74,8 @@
.school { .school {
grid-area: school; grid-area: school;
padding: 1rem; padding: 1rem;
overflow-y: auto;
} }
.unit { .unit {

View File

@ -7,6 +7,8 @@ import { DebugView } from './views/DebugView'
export const App = observer(function () { export const App = observer(function () {
console.dir(performance.now());
return ( return (
<div className="App"> <div className="App">
<StatsView /> <StatsView />

View File

@ -37,6 +37,7 @@ export class Calendar {
public static deserialize(data: CalendarDto): Calendar { public static deserialize(data: CalendarDto): Calendar {
const calendar = new Calendar(); const calendar = new Calendar();
calendar.day = data.day;
calendar.progress = Progress.deserialize(data.progress, true, calendar.handleProgress.bind(calendar)); calendar.progress = Progress.deserialize(data.progress, true, calendar.handleProgress.bind(calendar));
return calendar; return calendar;

View File

@ -10,6 +10,10 @@ export class Game {
makeAutoObservable(this); makeAutoObservable(this);
} }
public restart() {
this.school = new School();
}
public save(): void { public save(): void {
const data = this.school.serialize(); const data = this.school.serialize();
localStorage.setItem('save', JSON.stringify(data)) localStorage.setItem('save', JSON.stringify(data))

View File

@ -6,7 +6,7 @@ import { Upgrades } from './upgrades';
import { PartialResourceSet, ResourceSet } from '../types/resources'; import { PartialResourceSet, ResourceSet } from '../types/resources';
import { addResourceSets, fullResourceSet, multiplyResourceSet, roundResourceSet, subtractResourceSets } from '../utils/resources'; import { addResourceSets, fullResourceSet, multiplyResourceSet, roundResourceSet, subtractResourceSets } from '../utils/resources';
import { v7 as uuid } from 'uuid'; import { v7 as uuid } from 'uuid';
import { GameClock } from '../utils/gameClock'; import { GameClock, nowTime } from '../utils/gameClock';
import { Calendar, CalendarDto } from './calendar'; import { Calendar, CalendarDto } from './calendar';
import { getRandomHumanName } from '../utils/humanNames'; import { getRandomHumanName } from '../utils/humanNames';
import { Time as Time } from './time'; import { Time as Time } from './time';
@ -50,12 +50,10 @@ export class School {
} }
public tick(time: number) { public tick(time: number) {
const scaledTime = time; this.calendar.progress.tick(time);
this.calendar.progress.tick(scaledTime);
for (let unit of this.units) for (let unit of this.units)
unit.progress.tick(scaledTime); unit.progress.tick(time);
this.tps = GameClock.tps; this.tps = GameClock.tps;
} }

View File

@ -18,10 +18,10 @@ export const upgrades: UpgradePrefab[] = [
{ {
id: 'moreStudents', id: 'moreStudents',
name: "Больше учеников", name: "Больше учеников",
description: (level) => `Увеличить количество учеников с ${level} до ${level + 1}`, description: (level) => `Увеличить количество учеников с ${level + 1} до ${level + 2}`,
costToView: (level) => fullResourceSet(), costToView: (level) => fullResourceSet(),
costToOpen: (level) => ({ reputation: 20 * (level / 10 + 1) }), costToOpen: (level) => ({ reputation: 20 * (level / 10 + 1) }),
costToBuy: (level) => ({ gold: 10 }), costToBuy: (level) => ({ gold: 10 * (level + 1) }),
execute: (level) => { execute: (level) => {
game.school.increaseStudentCount(); game.school.increaseStudentCount();
}, },

View File

@ -1,6 +1,10 @@
import { game } from "../model/game"; import { game } from "../model/game";
import { ExternalSignal } from "./mobx/externalSignal"; import { ExternalSignal } from "./mobx/externalSignal";
export function nowTime() {
return performance.timeOrigin + performance.now();
}
export class GameClock { export class GameClock {
public static lastTime = 0; public static lastTime = 0;
@ -9,27 +13,18 @@ export class GameClock {
public static readonly pausedSignal = new ExternalSignal('GameClock.paused'); public static readonly pausedSignal = new ExternalSignal('GameClock.paused');
// public static async start(): Promise<void> {
// let lastTime = performance.now();
// setInterval(() => {
// let now = performance.now();
// let delta = now - lastTime;
// school.tick(delta / 1000);
// lastTime = now;
// }, 33);
// }
public static start(): void { public static start(): void {
const loop = (now: number) => { this.lastTime = nowTime();
const loop = () => {
if (GameClock.paused)
return;
let now = nowTime();
let delta = now - GameClock.lastTime; let delta = now - GameClock.lastTime;
GameClock.lastTime = now; GameClock.lastTime = now;
if (GameClock.paused)
delta *= 0;
GameClock.frames.push(now); GameClock.frames.push(now);
while (GameClock.frames[0] < now - 1000) while (GameClock.frames[0] < now - 1000)
GameClock.frames.shift(); GameClock.frames.shift();
@ -63,12 +58,20 @@ export class GameClock {
} }
public static pause() { public static pause() {
if (GameClock.paused)
return;
GameClock.paused = true; GameClock.paused = true;
GameClock.pausedSignal.trigger(); GameClock.pausedSignal.trigger();
} }
public static resume() { public static resume() {
if (!GameClock.paused)
return;
GameClock.paused = false; GameClock.paused = false;
GameClock.pausedSignal.trigger(); GameClock.pausedSignal.trigger();
GameClock.start();
} }
} }

View File

@ -1,22 +0,0 @@
import { observer } from "mobx-react-lite";
import { school } from "../model/school";
import { Student } from "../model/student";
import { Owner } from "../model/owner";
import { OwnerUnitView } from "./OwnerUnitView";
import { IUnit } from "../types/unit";
export const UnitListView = observer(function () {
function renderUnit(unit: IUnit) {
if (unit instanceof Owner)
return <OwnerUnitView unit={unit} />
// // else if (unit instanceof Student)
// // return StudentView({ unit });
else
return '???';
}
return <ul>{
school.units.map((unit) => <li className="unit" key={unit.id}>{renderUnit(unit)}</li>)
}</ul>
});

View File

@ -7,6 +7,10 @@ export const DebugView = observer(function () {
const [skipTime, setSkipTime] = useState<number>(5); const [skipTime, setSkipTime] = useState<number>(5);
const isDebugMode = location.hostname === "localhost" ||
location.hostname === "127.0.0.1" ||
location.hostname === "[::1]";
function handleTimeScaleChange(event: ChangeEvent<HTMLInputElement>): void { function handleTimeScaleChange(event: ChangeEvent<HTMLInputElement>): void {
game.school.time.setTimeScale(Number(event.target.value)); game.school.time.setTimeScale(Number(event.target.value));
} }
@ -15,6 +19,10 @@ export const DebugView = observer(function () {
GameClock.skipTime(skipTime); GameClock.skipTime(skipTime);
} }
function handleRestartClick(): void {
game.restart();
}
function handleSaveClick(): void { function handleSaveClick(): void {
game.save(); game.save();
} }
@ -22,6 +30,7 @@ export const DebugView = observer(function () {
function handleLoadClick(): void { function handleLoadClick(): void {
game.school.time.pause(); game.school.time.pause();
game.load(); game.load();
game.school.time.resume();
} }
function handleTickClick(): void { function handleTickClick(): void {
@ -32,31 +41,40 @@ export const DebugView = observer(function () {
return <div className="debug"> return <div className="debug">
<div> <div>
Время Время
<div className="timeScale"> {
Масштаб isDebugMode
<input ? <div className="timeScale">
type="range" Масштаб
min={1} <input
max={100} type="range"
step={1} min={1}
value={game.school.time.timeScale} max={100}
onChange={handleTimeScaleChange} step={1}
style={{ width: '100%' }} value={game.school.time.timeScale}
/> onChange={handleTimeScaleChange}
x{game.school.time.timeScale} style={{ width: '100%' }}
</div> />
x{game.school.time.timeScale}
</div>
: <></>
}
{ {
game.school.time.paused game.school.time.paused
? <button onClick={() => game.school.time.resume()}>|&gt;</button> ? <button onClick={() => game.school.time.resume()}>|&gt;</button>
: <button onClick={() => game.school.time.pause()}>||</button> : <button onClick={() => game.school.time.pause()}>||</button>
}
{
isDebugMode
? <button onClick={handleSkipTime}>
<input type="number" value={skipTime} onChange={(e) => setSkipTime(Number(e.target.value))}></input>
Пропустить
</button>
: <></>
} }
<button onClick={handleSkipTime}>
<input type="number" value={skipTime} onChange={(e) => setSkipTime(Number(e.target.value))}></input>
Пропустить
</button>
<button onClick={handleTickClick}>Tick</button> <button onClick={handleTickClick}>Tick</button>
</div> </div>
<div> <div>
<button onClick={handleRestartClick}>Restart</button>
<button onClick={handleSaveClick}>Save</button> <button onClick={handleSaveClick}>Save</button>
<button onClick={handleLoadClick}>Load</button> <button onClick={handleLoadClick}>Load</button>
</div> </div>

View File

@ -8,15 +8,6 @@ import { game } from "../model/game";
export const UnitListView = observer(function () { export const UnitListView = observer(function () {
function renderUnit(unit: IUnit) {
if (unit instanceof Owner)
return <OwnerUnitView unit={unit} />
else if (unit instanceof Student)
return <StudentUnitView unit={unit} />
else
return '???';
}
return <div className="units"> return <div className="units">
<div className="owner"> <div className="owner">
<OwnerUnitView unit={game.school.owner} /> <OwnerUnitView unit={game.school.owner} />