feat: add initial game implementation
All checks were successful
deploy to cloudflare pages / deploy (push) Successful in 33s
All checks were successful
deploy to cloudflare pages / deploy (push) Successful in 33s
This commit is contained in:
parent
4919a5258d
commit
4f7d603848
4 changed files with 99 additions and 19 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
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%;
|
||||
|
|
|
@ -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;
|
||||
|
|
55
src/routes/api/data/+server.ts
Normal file
55
src/routes/api/data/+server.ts
Normal 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);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue