Files
vending-machine-game/vite.config.js
T
HamedandClaude Opus 5.5 0939dac9cd Start at the factory, add PWA install and GPS real-world mode
The game now begins inside the Negin Sazan Pishgam Radmehr Atousa
vending-machine factory on the Mashhad–Chenaran road, and the world
extends ~20 km along the real driving route to Park Mellat and
Ferdowsi University.

- Factory compound: production hall with assembly line, office, yard,
  gate and company signs; first mission at its half-price demo machine
- Map data merged from four OSM extracts (factory zone, route corridor,
  region main roads, city zone), clipped to a 15 x 18 km region
- City meshes chunked and culled by distance; traffic recycled around
  the player; minimap and city map drawn from cached tiles + overview
- The Robot Market robot no longer walks with the family: it comes out
  of a machine when they arrive, greets them with expression portraits
  and goes back inside when they leave
- PWA: manifest, icons from the robot avatar, offline service worker
  generated at build time, install button / iOS steps, update prompt;
  Vazirmatn font bundled instead of Google Fonts
- Real-world mode: safety notice, GPS + compass tracking with accuracy
  smoothing, 20-40 m arrival radius, speed lock and in-person bonus

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 08:56:06 +03:30

57 lines
2.4 KiB
JavaScript

import {defineConfig} from 'vite';
import {readdirSync, statSync} from 'node:fs';
import {join, relative} from 'node:path';
import {createHash} from 'node:crypto';
const listFiles = dir => readdirSync(dir).flatMap(name => {
const path = join(dir, name);
return statSync(path).isDirectory() ? listFiles(path) : [path];
});
// Emits sw.js listing every built file, so the installed game works offline. The cache name is a
// hash of that list: a new build gets a new cache and the page offers to reload into it.
function offlineServiceWorker() {
return {
name: 'offline-service-worker',
apply: 'build',
generateBundle(_, bundle) {
const files = [...Object.keys(bundle), ...listFiles('public').map(f => relative('public', f).replaceAll('\\', '/'))]
.filter(f => f !== 'sw.js' && !f.endsWith('.map'));
const version = createHash('sha1').update(files.join('\n')).digest('hex').slice(0, 12);
this.emitFile({
type: 'asset', fileName: 'sw.js', source: `// Generated by vite.config.js — do not edit.
const CACHE = 'robot-market-${version}';
const FILES = ${JSON.stringify(['./', ...files.map(f => `./${f}`)])};
self.addEventListener('install', event => event.waitUntil(caches.open(CACHE).then(cache => cache.addAll(FILES))));
self.addEventListener('activate', event => event.waitUntil(caches.keys().then(keys =>
Promise.all(keys.filter(k => k.startsWith('robot-market-') && k !== CACHE).map(k => caches.delete(k)))).then(() => self.clients.claim())));
self.addEventListener('message', event => event.data === 'skip-waiting' && self.skipWaiting());
self.addEventListener('fetch', event => {
const request = event.request;
if (request.method !== 'GET' || new URL(request.url).origin !== location.origin) return;
// Pages: network first so updates arrive, cached page when offline. Everything else: cache first.
if (request.mode === 'navigate') {
event.respondWith(fetch(request).catch(() => caches.match('./')));
return;
}
event.respondWith(caches.match(request, {ignoreSearch: true}).then(hit => hit ?? fetch(request)));
});
`
});
}
};
}
export default defineConfig({
// Relative assets also load under a GitHub Pages repository path.
base: './',
plugins: [offlineServiceWorker()],
server: {
host: true,
allowedHosts: [
'robotmarkets.ir',
'www.robotmarkets.ir',
],
},
});