/* maps.js — scenario definitions. * * All coordinates are world == canvas pixels (W x H). Heading 0 points +x * (right); +y is down (canvas convention); angle increases clockwise on screen. * * obstacle: {x,y,w,h,a?,type?} -> oriented box centered at (x,y); type colors it * goal: {x,y,heading,w,h,posTol,headTol} -> where the TRAILER must end up * start: {x,y,carHeading,trailerHeading} -> car rear-axle pose */ (function (TOW) { 'use strict'; var W = 960, H = 680; var PI = Math.PI; // a couple of headings for readability var UP = -PI / 2, DOWN = PI / 2, LEFT = PI, RIGHT = 0; var maps = [ { name: 'Loading Dock', hint: 'Warm-up. Reverse straight back to slot the trailer into the bay.', start: { x: 480, y: 470, carHeading: DOWN, trailerHeading: DOWN }, // NB: goal w == extent ALONG heading (depth), h == perpendicular (width) goal: { x: 480, y: 120, heading: DOWN, w: 150, h: 120, posTol: 34, headTol: 0.30 }, obstacles: [ { x: 250, y: 95, w: 300, h: 130, type: 'wall' }, { x: 710, y: 95, w: 300, h: 130, type: 'wall' } ] }, { name: 'Parking Lot', hint: 'Back into the empty stall between the two parked cars.', start: { x: 700, y: 470, carHeading: LEFT, trailerHeading: LEFT }, goal: { x: 360, y: 130, heading: DOWN, w: 140, h: 110, posTol: 30, headTol: 0.26 }, obstacles: [ { x: 250, y: 110, w: 70, h: 150, type: 'car' }, { x: 470, y: 110, w: 70, h: 150, type: 'car' }, { x: 690, y: 110, w: 70, h: 150, type: 'car' }, { x: 480, y: 40, w: 960, h: 24, type: 'wall' } // back curb ] }, { name: 'Roadside', hint: 'Parallel park: tuck the trailer into the gap along the curb.', start: { x: 250, y: 430, carHeading: RIGHT, trailerHeading: RIGHT }, goal: { x: 480, y: 175, heading: RIGHT, w: 210, h: 80, posTol: 36, headTol: 0.24 }, obstacles: [ { x: 480, y: 70, w: 960, h: 80, type: 'wall' }, // building / far curb { x: 215, y: 175, w: 230, h: 70, type: 'car' }, // car in front of the gap { x: 760, y: 175, w: 230, h: 70, type: 'car' } // car behind the gap ] }, { name: 'Driveway', hint: 'Back up the angled driveway between the house and the hedge.', start: { x: 615, y: 500, carHeading: 0.82, trailerHeading: 0.82 }, // driveway channel runs along a = -2.32 rad (up-left); goal sits on the // channel midline (midpoint of the two walls) up toward the house goal: { x: 223, y: 184, heading: -2.32, w: 120, h: 74, posTol: 34, headTol: 0.32 }, obstacles: [ { x: 407, y: 286, w: 340, h: 40, a: -2.32, type: 'wall' }, // house wall { x: 313, y: 374, w: 340, h: 40, a: -2.32, type: 'hedge' }, // hedge { x: 480, y: 600, w: 960, h: 40, type: 'wall' } // street curb ] } ]; // ---- procedural map #5 --------------------------------------------------- // A fresh layout is built every time the player arrives at this slot. Each // candidate is validated (start & goal collision-free, goal in bounds and // reachable) before being accepted; we retry until one passes. function rnd(a, b) { return a + Math.random() * (b - a); } function chance(p) { return Math.random() < p; } function dist(ax, ay, bx, by) { return Math.hypot(ax - bx, ay - by); } function box(o) { return { cx: o.x, cy: o.y, hw: o.w / 2, hh: o.h / 2, a: o.a || 0 }; } function validLayout(m) { var v = new TOW.Vehicle(); v.reset(m.start); var b = v.bodies(); if (TOW.outOfBounds(b.carBox, W, H, 4)) return false; if (TOW.outOfBounds(b.trailerBox, W, H, 4)) return false; var g = m.goal; var gb = { cx: g.x, cy: g.y, hw: g.w / 2, hh: g.h / 2, a: g.heading }; if (TOW.outOfBounds(gb, W, H, 2)) return false; for (var i = 0; i < m.obstacles.length; i++) { var ob = box(m.obstacles[i]); if (TOW.obbOverlap(b.carBox, ob)) return false; if (TOW.obbOverlap(b.trailerBox, ob)) return false; if (TOW.obbOverlap(gb, ob)) return false; } return true; } // back-into-a-stall, with the stall at a random x and a random gap width function buildVertical() { var gx = rnd(250, W - 250); var gap = rnd(104, 132); var carW = 70, off = gap / 2 + carW / 2; var dir = chance(0.5) ? LEFT : RIGHT; return { name: 'Random · Parking Lot', hint: 'Freshly generated. Back the trailer into the open stall.', start: { x: TOW.clamp(gx + rnd(-150, 150), 190, W - 190), y: rnd(450, 500), carHeading: dir, trailerHeading: dir }, goal: { x: gx, y: 135, heading: DOWN, w: rnd(135, 150), h: gap - 18, posTol: 32, headTol: 0.28 }, obstacles: [ { x: gx - off, y: 110, w: carW, h: 150, type: 'car' }, { x: gx + off, y: 110, w: carW, h: 150, type: 'car' }, { x: W / 2, y: 38, w: W, h: 24, type: 'wall' } ] }; } // parallel park into a random gap along the curb function buildParallel() { var gx = rnd(345, W - 345); var len = rnd(195, 225); var y = 178, carLen = 200, off = len / 2 + carLen / 2 + 10; // +clearance to gap return { name: 'Random · Roadside', hint: 'Freshly generated. Parallel-park the trailer into the gap.', start: { x: rnd(190, 320), y: rnd(420, 460), carHeading: RIGHT, trailerHeading: RIGHT }, goal: { x: gx, y: y, heading: RIGHT, w: len, h: 78, posTol: 36, headTol: 0.24 }, obstacles: [ { x: W / 2, y: 66, w: W, h: 80, type: 'wall' }, { x: gx - off, y: y, w: carLen, h: 66, type: 'car' }, { x: gx + off, y: y, w: carLen, h: 66, type: 'car' } ] }; } // sprinkle a few cones as extra hazards, kept clear of the goal & start function addCones(m) { var want = (Math.random() * 3) | 0; // 0..2 var added = 0, tries = 0; while (added < want && tries < 60) { tries++; var c = { x: rnd(120, W - 120), y: rnd(250, 430), w: rnd(26, 38), h: rnd(26, 38), type: 'cone' }; if (dist(c.x, c.y, m.goal.x, m.goal.y) < 145) continue; if (dist(c.x, c.y, m.start.x, m.start.y) < 120) continue; var probe = { start: m.start, goal: m.goal, obstacles: m.obstacles.concat([c]) }; if (!validLayout(probe)) continue; m.obstacles.push(c); added++; } } function generateRandomMap() { for (var attempt = 0; attempt < 150; attempt++) { var m = chance(0.5) ? buildVertical() : buildParallel(); if (!validLayout(m)) continue; var v = new TOW.Vehicle(); v.reset(m.start); var tb = v.bodies().trailerBox; if (dist(tb.cx, tb.cy, m.goal.x, m.goal.y) < 200) continue; // not already parked addCones(m); return m; } return buildVertical(); // extremely unlikely fallback } maps.push({ name: 'Random', hint: 'A fresh layout every time you arrive (R retries the same one).', generate: generateRandomMap }); TOW.WORLD = { W: W, H: H }; TOW.maps = maps; TOW.generateRandomMap = generateRandomMap; })(window.TOW = window.TOW || {});