Compare commits

...

6 Commits

Author SHA1 Message Date
ElementalAlchemist 75a6733362 Better align bus stop signs with the front of the bus 10 months ago
ElementalAlchemist a80f7889af Show icons for points we've driven past 10 months ago
ElementalAlchemist e440dd965c Constantize the canvas width in case we want it widened in the future 10 months ago
ElementalAlchemist 79e6e7266c Add key-out color on the ground 10 months ago
ElementalAlchemist 2d68b7805e Implement scaling 10 months ago
ElementalAlchemist 7fca10593d Implement canvas-based bus system 10 months ago

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

@ -0,0 +1,9 @@
#scale {
display: block;
margin-top: 20px;
}
#scale-disclaimer {
font-size: 75%;
font-style: italic;
}

@ -1,54 +1,19 @@
<!doctype html>
<html>
<style>
#road-container {
width: 100%;
height: 100px;
left: 0;
top: 0;
position: absolute;
// margin: 980px 0 0 -100px; // uncomment to move to bottom of screen
z-index: 3;
}
#road-container div {
height: 100px;
width: 100%;
position: absolute;
background-position: 0px 0px;
}
#timeofday-left {
z-index: 5;
}
#timeofday-right {
z-index: 4;
}
#stops {
z-index: 6;
background-image: url(stops.png);
}
#bus {
background-repeat: no-repeat;
margin-left: 27px;
z-index: 8;
}
</style>
<body>
<div id="road-container">
<div id="bus"></div>
<div id="timeofday-left"></div>
<div id="timeofday-right"></div>
<div id="stops"></div>
</div>
<script src="drive.js"></script>
</body>
<head>
<meta charset="utf-8" />
<title>Bus Progress</title>
<link rel="stylesheet" href="drive.css" />
<script src="drive.js"></script>
</head>
<body>
<canvas id="road" width="1920" height="100"></canvas>
<label id="scale">
Scale:
<input id="scale-input" type="number" value="1" min="0.1" step="0.1" />
</label>
<p id="scale-disclaimer">
Scale modifications will be applied on the next update, which occur every 10 seconds.
</p>
</body>
</html>

@ -1,122 +1,210 @@
const PAGE_WIDTH = 1920;
const MINUTES_PER_PAGE = 60;
const POINT_WIDTH = PAGE_WIDTH * 8 * 60 / MINUTES_PER_PAGE;
const MILES_PER_PAGE = 45;
const BUS_POSITION_X = 93;
const BASE_ODO = 109.3;
const UPDATE_INTERVAL_MS = 5000
const WUBLOADER_URL = "";
const SKY_URLS = {
day: "db_day.png",
dawn: "db_dawn.png",
dusk: "db_dusk.png",
night: "db_night.png",
};
const BUS_URLS = {
day: "bus_day.png",
dawn: "bus_day.png",
dusk: "bus_day.png",
night: "bus_night.png",
const COLORS = {
day: {
sky: "#41cee2",
ground: "#e5931b",
surface: "#b77616",
},
dusk: {
sky: "#db92be",
ground: "#dd926a",
surface: "#b17555",
},
night: {
sky: "#121336",
ground: "#30201a",
surface: "#261a15",
},
dawn: {
sky: "#2b2f87",
ground: "#724d41",
surface: "#5b3e34",
},
};
function setSkyElements(left, right, timeToTransition) {
const leftElement = document.getElementById("timeofday-left");
const rightElement = document.getElementById("timeofday-right");
const busElement = document.getElementById("bus");
const KEY_OUT_COLOR = "#2b6ec6";
leftElement.style.backgroundImage = `url(${SKY_URLS[left]})`;
rightElement.style.backgroundImage = `url(${SKY_URLS[right]})`;
// The width from the left side of the bus image to the front of the bus
const BUS_FRONT_OFFSET = 73;
if (left === right) {
leftElement.style.width = "100%";
} else {
const transitionPercent = timeToTransition / MINUTES_PER_PAGE;
leftElement.style.width = `${transitionPercent * 100}%`
}
// Start time of each day phase
const DAY_START_MINUTES = 450;
const DUSK_START_MINUTES = 1140;
const NIGHT_START_MINUTES = 1200;
const DAWN_START_MINUTES = 400;
bus.style.backgroundImage = `url(${BUS_URLS[left]})`;
}
const BUS_STOP_OFFSET = 8;
const POINT_OFFSET = 17;
function nextSkyTransition(timeofday, clock) {
switch (timeofday) {
case "dawn":
// Bus stop positions are recorded in miles with the 0 position
// at route start. This array can be looped every point.
const BUS_STOP_POSITIONS = [1, 55.2, 125.4, 166.3, 233.9, 295.2];
const BUS_DAY_IMAGE = new Image();
BUS_DAY_IMAGE.src = "bus_day.png";
const BUS_NIGHT_IMAGE = new Image();
BUS_NIGHT_IMAGE.src = "bus_night.png";
const BUS_STOP_IMAGE = new Image();
BUS_STOP_IMAGE.src = "db_stop.png";
const POINT_IMAGE = new Image();
POINT_IMAGE.src = "point.png";
// This should match the HTML canvas width
const CANVAS_PIXEL_WIDTH = 1920;
function nextPhase(timeOfDay) {
switch (timeOfDay) {
case "day":
return [19 * 60, "dusk"]; // 7pm
case "dawn":
return "dusk";
case "dusk":
return [20 * 60, "night"]; // 8pm
return "night";
case "night":
return [6 * 60 + 40, "dawn"]; // 6:40am
return "dawn";
}
}
function setSky(timeofday, clock) {
const [transition, newSky] = nextSkyTransition(timeofday, clock);
// 1440 minutes in 24h, this code will return time remaining even if
// the transition is in the morning and we're currently in the evening.
const timeToTransition = (1440 + transition - clock) % 1440;
if (timeToTransition < MINUTES_PER_PAGE) {
// Transition on screen
setSkyElements(timeofday, newSky, timeToTransition);
} else {
// No transition on screen
setSkyElements(timeofday, timeofday, undefined);
function phaseStartTime(timeOfDay) {
switch (timeOfDay) {
case "day":
return DAY_START_MINUTES;
case "dusk":
return DUSK_START_MINUTES;
case "night":
return NIGHT_START_MINUTES;
case "dawn":
return DAWN_START_MINUTES;
}
}
function setOdo(odo) {
const distancePixels = PAGE_WIDTH * (odo - BASE_ODO) / MILES_PER_PAGE;
const offset = (BUS_POSITION_X - distancePixels) % POINT_WIDTH;
function drawBackground(context, timeOfDay, leftX, width) {
const skyColor = COLORS[timeOfDay].sky;
const groundColor = COLORS[timeOfDay].ground;
const surfaceColor = COLORS[timeOfDay].surface;
const stopsElement = document.getElementById("stops");
stopsElement.style.backgroundPosition = `${offset}px 0px`;
context.fillStyle = KEY_OUT_COLOR;
context.fillRect(leftX, 80, width, 20);
context.fillStyle = COLORS[timeOfDay].sky;
context.fillRect(leftX, 0, width, 80);
context.fillStyle = COLORS[timeOfDay].surface;
context.fillRect(leftX, 80, width, 1);
context.fillStyle = COLORS[timeOfDay].ground;
context.fillRect(leftX, 81, width, 7);
context.fillRect(leftX, 89, width, 3);
context.fillRect(leftX, 94, width, 2);
context.fillRect(leftX, 99, width, 1);
}
async function update() {
const busDataResponse = await fetch(`${WUBLOADER_URL}/thrimshim/bus/buscam`);
async function drawRoad() {
const busDataResponse = await fetch("/thrimshim/bus/buscam");
if (!busDataResponse.ok) {
return;
}
const busData = await busDataResponse.json();
console.log("Got data:", busData);
setOdo(busData.odometer);
setSky(busData.timeofday, busData.clock_minutes);
}
// Initial conditions, before the first refresh finishes
setSky("day", 7 * 60);
setOdo(BASE_ODO);
// Testing mode. Set true to enable.
const test = false;
if (test) {
let h = 0;
// Set to how long 1h of in-game time should take in real time
const hourTimeMs = 1 * 1000;
// Set to how often to update the screen
const interval = 30;
setInterval(() => {
h += interval / hourTimeMs;
setOdo(BASE_ODO + 45 * h);
if (h < 19) {
setSky("day", 60 * h);
} else {
m = (h % 24) * 60;
let tod;
if (m < 6 * 60 + 40) {
tod = "night";
} else if (m < 19 * 60) {
tod = "dawn";
} else if (m < 20 * 60) {
tod = "dusk";
const canvas = document.getElementById("road");
if (!canvas.getContext) {
return;
}
const context = canvas.getContext("2d");
// Clear the previous canvas before starting
context.clearRect(0, 0, CANVAS_PIXEL_WIDTH, 100);
const currentTime = busData.clock_minutes;
const distance = busData.odometer;
const timeOfDay = busData.timeofday;
drawBackground(context, timeOfDay, 0, BUS_FRONT_OFFSET);
const maxWidth = CANVAS_PIXEL_WIDTH - BUS_FRONT_OFFSET;
// The default scaling factor (1) is 20 seconds per pixel at max speed.
// This gives us
// - 3px per minute
// - 4px per mile
let scaleFactor = +document.getElementById("scale-input").value;
if (scaleFactor === 0 || isNaN(scaleFactor)) {
scaleFactor = 1;
}
const startMinute = busData.clock_minutes;
const timeDuration = maxWidth / (3 * scaleFactor);
let previousTime = startMinute;
let previousTimeOfDay = timeOfDay;
let remainingDuration = timeDuration;
let x = BUS_FRONT_OFFSET;
while (remainingDuration > 0) {
const nextTimeOfDay = nextPhase(previousTimeOfDay);
const nextStartTime = phaseStartTime(nextTimeOfDay);
let thisDuration = nextStartTime - previousTime;
if (thisDuration < 0) {
thisDuration += 1440;
}
// TODO Figure out scaling factor
const pixelWidth = thisDuration * 3 * scaleFactor;
drawBackground(context, previousTimeOfDay, x, pixelWidth);
remainingDuration -= thisDuration;
previousTime = nextStartTime;
previousTimeOfDay = nextTimeOfDay;
x += pixelWidth;
}
x = 0;
const currentPointProgress = distance % 360;
let distanceToNextPoint;
if (currentPointProgress <= 109.3) {
distanceToNextPoint = 109.3 - currentPointProgress;
} else {
tod = "night";
distanceToNextPoint = 469.3 - currentPointProgress;
}
distanceToNextPoint += BUS_FRONT_OFFSET / (4 * scaleFactor);
if (distanceToNextPoint >= 360) {
distanceToNextPoint -= 360;
}
setSky(tod, m);
x += distanceToNextPoint * 4 * scaleFactor;
context.drawImage(POINT_IMAGE, x - POINT_OFFSET, 0);
while (x < CANVAS_PIXEL_WIDTH) {
x += 360 * 4 * scaleFactor;
context.drawImage(POINT_IMAGE, x - POINT_OFFSET, 0);
}
const distanceOnRoute = (distance - 109.3) % 360;
let distanceTracked = distanceOnRoute - BUS_FRONT_OFFSET / (4 * scaleFactor);
if (distanceTracked < 0) {
distanceTracked += 720;
}
x = 0;
while (x < CANVAS_PIXEL_WIDTH) {
const distanceTrackedOnRoute = distanceTracked % 360;
let nextBusStopPosition = null;
for (const busStopPosition of BUS_STOP_POSITIONS) {
if (busStopPosition >= distanceTrackedOnRoute + 1) {
nextBusStopPosition = busStopPosition;
break;
}
}
if (nextBusStopPosition === null) {
nextBusStopPosition = 360 + BUS_STOP_POSITIONS[0];
}
const nextBusStopDistance = nextBusStopPosition - distanceTrackedOnRoute;
distanceTracked += nextBusStopDistance;
x += nextBusStopDistance * 4 * scaleFactor;
context.drawImage(BUS_STOP_IMAGE, x - BUS_STOP_OFFSET, 0);
}
if (timeOfDay === "night") {
context.drawImage(BUS_NIGHT_IMAGE, 0, 0);
} else {
context.drawImage(BUS_DAY_IMAGE, 0, 0);
}
}, interval);
} else {
// Do first update immediately, then every UPDATE_INTERVAL_MS
setInterval(update, UPDATE_INTERVAL_MS);
update();
}
window.addEventListener("DOMContentLoaded", (event) => {
drawRoad();
setInterval(drawRoad, 10000);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Loading…
Cancel
Save