feat: add initial game implementation
All checks were successful
deploy to cloudflare pages / deploy (push) Successful in 33s

This commit is contained in:
uku 2024-10-24 11:58:23 +02:00
parent 4919a5258d
commit 4f7d603848
Signed by: uku
SSH key fingerprint: SHA256:4P0aN6M8ajKukNi6aPOaX0LacanGYtlfjmN+m/sHY/o
4 changed files with 99 additions and 19 deletions

View file

@ -1 +1,6 @@
// place files you want to import through the `$lib` alias in this folder.
export interface GameData {
center: [number, number];
lines: [GeoJSON.Feature, string][];
stopName: string;
stop: GeoJSON.Position;
}

View file

@ -15,14 +15,11 @@
const hidden = !$page.url.searchParams.has("debug");
const lignes = data.lines;
const point = L.latLng(45.75388713, 4.84715287);
const lignes = data.gameData.lines;
const point = L.latLng(data.gameData.stop[1], data.gameData.stop[0]);
const center = L.latLng(data.gameData.center);
const bbox = lignes.bbox || [0, 0, 0, 0];
const center = L.latLng((bbox[1] + bbox[3]) / 2, (bbox[0] + bbox[2]) / 2);
const linesJson = lignes.features.map((feature) => {
let color = "rgb(" + feature.properties?.couleur?.replaceAll(" ", ",") + ")";
const linesJson = lignes.map(([feature, color]) => {
return L.geoJSON(feature, { style: { color } });
});
@ -32,8 +29,11 @@
let lines = $state(true);
let labels = $state(false);
let submitted = $state(false);
let map: L.Map | null = $state(null);
let playerMarker: L.Marker | null = $state(null);
let tileLayer = $derived(
L.tileLayer(
`https://basemaps.cartocdn.com/light_${labels ? "all" : "nolabels"}/{z}/{x}/{y}{r}.png`,
@ -50,19 +50,21 @@
tileLayer.addTo(map);
if (lines) {
linesJson.forEach((line) => map && line.addTo(map));
linesJson.forEach((line) => line.addTo(map!));
} else {
linesJson.forEach((line) => map && line.removeFrom(map));
linesJson.forEach((line) => line.removeFrom(map!));
}
});
function createMap(node: HTMLElement) {
map = L.map(node).setView(center, 13);
playerMarker = L.marker([0, 0]).addTo(map);
const marker = L.marker([0, 0]);
map.on("click", (e) => {
if (map) marker.setLatLng(e.latlng).addTo(map);
latlng = e.latlng;
if (map && playerMarker && !submitted) {
playerMarker.setLatLng(e.latlng);
latlng = e.latlng;
}
});
}
@ -76,8 +78,23 @@
return score * multiplier;
}
function checkLocation() {
submitted = true;
if (map) {
L.marker(point).bindPopup(data.gameData.stopName).addTo(map).openPopup();
}
}
</script>
<h1 class="banner">{data.gameData.stopName}</h1>
<div class="banner" hidden={submitted}><button onclick={checkLocation}>SUBMIT</button></div>
<div class="banner results" hidden={!submitted}>
{Math.floor(points)} points! Vous etiez à {Math.floor(distance)}m.
</div>
<div class="banner" {hidden}>
<span>distance: {distance}, points: {points}</span>
</div>
@ -95,6 +112,11 @@
text-align: center;
}
.results {
color: green;
font-size: 20px;
}
#map {
height: 100%;
width: 100%;

View file

@ -1,13 +1,11 @@
import type { PageLoad } from "./$types";
const linesUrl =
"https://data.grandlyon.com/geoserver/sytral/ows?SERVICE=WFS&VERSION=2.0.0&request=GetFeature&typename=sytral:tcl_sytral.tcllignemf_2_0_0&outputFormat=application/json&SRSNAME=EPSG:4171&sortBy=gid";
import type { GameData } from "$lib";
export const load: PageLoad = async ({ fetch }) => {
const res = await fetch(linesUrl);
const lines: GeoJSON.FeatureCollection = await res.json();
const res = await fetch("/api/data");
const gameData: GameData = await res.json();
return { lines };
return { gameData };
};
export const ssr = false;

View file

@ -0,0 +1,55 @@
import type { RequestHandler } from "./$types";
import type { GameData } from "$lib";
const linesUrl =
"https://data.grandlyon.com/geoserver/sytral/ows?SERVICE=WFS&VERSION=2.0.0&request=GetFeature&typename=sytral:tcl_sytral.tcllignemf_2_0_0&outputFormat=application/json&SRSNAME=EPSG:4171&sortBy=gid";
const stopsUrl =
"https://data.grandlyon.com/geoserver/sytral/ows?SERVICE=WFS&VERSION=2.0.0&request=GetFeature&typename=sytral:tcl_sytral.tclarret&outputFormat=application/json&SRSNAME=EPSG:4171&sortBy=gid";
let lazyLines: GeoJSON.FeatureCollection | null = null;
let lazyStops: GeoJSON.FeatureCollection | null = null;
export const GET: RequestHandler = async ({ fetch }) => {
const lines: GeoJSON.FeatureCollection =
lazyLines || (lazyLines = await fetch(linesUrl).then((r) => r.json()));
const stops: GeoJSON.FeatureCollection =
lazyStops || (lazyStops = await fetch(stopsUrl).then((r) => r.json()));
const bbox = lines.bbox ?? [0, 0, 0, 0];
const centerLat = (bbox[1] + bbox[3]) / 2;
const centerLon = (bbox[0] + bbox[2]) / 2;
const lineColors: [GeoJSON.Feature, string][] = lines.features.map((f) => {
const components = f.properties!.couleur!.split(" ");
return [f, `rgb(${components.join(",")})`];
});
const lineCodes = new Set<string>(lines.features.map((f) => f.properties!.code_ligne));
const crossingStops = stops.features.filter((f) => {
if (f.properties?.desserte) {
return f.properties.desserte
.split(",")
.map((d: string) => d.split(":")[0])
.some((d: string) => lineCodes.has(d));
} else {
return false;
}
});
const uniqueStops = crossingStops.filter(
(f1, i, arr) => arr.findIndex((f2) => f2.properties?.nom === f1.properties?.nom) === i,
);
const randomStop = uniqueStops[Math.floor(Math.random() * uniqueStops.length)];
const coords = randomStop.geometry.type === "Point" ? randomStop.geometry.coordinates : [0, 0];
const data: GameData = {
center: [centerLat, centerLon],
lines: lineColors,
stopName: randomStop.properties!.nom,
stop: coords,
};
return Response.json(data);
};