import React, { useEffect, useRef, useState } from 'react' // --- SIMULATION CONSTANTS --- const CANVAS_WIDTH = 1400 const CANVAS_HEIGHT = 800 const speeds = { car: 2.25, bus: 1.8, truck: 1.8, rickshaw: 2, bike: 2.5 } const stopLines = { right: 590, down: 330, left: 800, up: 535 } const defaultStop = { right: 580, down: 320, left: 810, up: 545 } const mid = { right: { x: 705, y: 445 }, down: { x: 695, y: 450 }, left: { x: 695, y: 425 }, up: { x: 695, y: 400 } } const gap = 15 const gap2 = 15 const rotationAngle = 3 const directionNumbers = { 0: 'right', 1: 'down', 2: 'left', 3: 'up' } const vehicleTypes = { 0: 'car', 1: 'bus', 2: 'truck', 3: 'rickshaw', 4: 'bike' } // Base start positions (will be cloned for each simulation instance) const baseStartX = { right: [0, 0, 0], down: [755, 727, 697], left: [1400, 1400, 1400], up: [602, 627, 657] } const baseStartY = { right: [348, 370, 398], down: [0, 0, 0], left: [498, 466, 436], up: [800, 800, 800] } const signalCoods = [[530, 230], [810, 230], [810, 570], [530, 570]] const signalTimerCoods = [[530, 210], [810, 210], [810, 550], [530, 550]] const vehicleCountCoods = [[480, 210], [880, 210], [880, 550], [480, 550]] const defaultRed = 150 const defaultYellow = 5 const defaultGreen = 20 const defaultMinimum = 10 const defaultMaximum = 60 const detectionTime = 5 const carTime = 2 const bikeTime = 1 const rickshawTime = 2.25 const busTime = 2.5 const truckTime = 2.5 class TrafficSignal { constructor(red, yellow, green) { this.red = red this.yellow = yellow this.green = green } } class Vehicle { constructor(lane, vClass, directionNum, direction, image, state) { this.lane = lane this.vehicleClass = vClass this.speed = speeds[vClass] this.direction_number = directionNum this.direction = direction this.x = state.spawnPoints.x[direction][lane] this.y = state.spawnPoints.y[direction][lane] this.crossed = 0 this.willTurn = Math.random() < 0.3 && lane === 2 ? 1 : 0 this.turned = 0 this.rotateAngle = 0 this.image = image this.width = image.width this.height = image.height state.vehiclesByDirection[direction][lane].push(this) this.index = state.vehiclesByDirection[direction][lane].length - 1 const temp = (this.direction === 'right' || this.direction === 'left') ? this.width + gap : this.height + gap // Update spawn points for next vehicle in this lane if (this.direction === 'right') { state.spawnPoints.x[this.direction][this.lane] -= temp this.stop = (this.index > 0 && state.vehiclesByDirection[this.direction][this.lane][this.index - 1].crossed === 0) ? state.vehiclesByDirection[this.direction][this.lane][this.index - 1].stop - temp : defaultStop[this.direction] } else if (this.direction === 'left') { state.spawnPoints.x[this.direction][this.lane] += temp this.stop = (this.index > 0 && state.vehiclesByDirection[this.direction][this.lane][this.index - 1].crossed === 0) ? state.vehiclesByDirection[this.direction][this.lane][this.index - 1].stop + temp : defaultStop[this.direction] } else if (this.direction === 'down') { state.spawnPoints.y[this.direction][this.lane] -= temp this.stop = (this.index > 0 && state.vehiclesByDirection[this.direction][this.lane][this.index - 1].crossed === 0) ? state.vehiclesByDirection[this.direction][this.lane][this.index - 1].stop - temp : defaultStop[this.direction] } else if (this.direction === 'up') { state.spawnPoints.y[this.direction][this.lane] += temp this.stop = (this.index > 0 && state.vehiclesByDirection[this.direction][this.lane][this.index - 1].crossed === 0) ? state.vehiclesByDirection[this.direction][this.lane][this.index - 1].stop + temp : defaultStop[this.direction] } } move(state) { const isGreen = state.currentGreen === this.direction_number && state.currentYellow === 0 const nextVeh = state.vehiclesByDirection[this.direction][this.lane][this.index - 1] const checkAhead = (dim, currentPos, targetStop, aheadVehPos, aheadVehWidth, isVertical = false) => { const isLeading = this.index === 0 const hasGap = isLeading || (isVertical ? (this.direction === 'down' ? currentPos + this.height < aheadVehPos - gap2 : currentPos > aheadVehPos + aheadVehWidth + gap2) : (this.direction === 'right' ? currentPos + this.width < aheadVehPos - gap2 : currentPos > aheadVehPos + aheadVehWidth + gap2) ) const lightAllows = this.crossed === 1 || isGreen || (isVertical ? (this.direction === 'down' ? currentPos + this.height <= targetStop : currentPos >= targetStop) : (this.direction === 'right' ? currentPos + this.width <= targetStop : currentPos >= targetStop) ) return lightAllows && hasGap } if (this.direction === 'right') { if (this.crossed === 0 && this.x + this.width > stopLines.right) { this.crossed = 1; state.vehiclesByDirection.right.crossed++ } if (checkAhead('x', this.x, this.stop, nextVeh?.x, nextVeh?.width)) { if (this.willTurn && this.x + this.width >= mid.right.x && !this.turned) { this.rotateAngle += rotationAngle; this.x += 2; this.y += 1.8 if (this.rotateAngle >= 90) this.turned = 1 } else if (this.turned) { this.y += this.speed } else { this.x += this.speed } } } else if (this.direction === 'down') { if (this.crossed === 0 && this.y + this.height > stopLines.down) { this.crossed = 1; state.vehiclesByDirection.down.crossed++ } if (checkAhead('y', this.y, this.stop, nextVeh?.y, nextVeh?.height, true)) { if (this.willTurn && this.y + this.height >= mid.down.y && !this.turned) { this.rotateAngle += rotationAngle; this.x -= 2.5; this.y += 2 if (this.rotateAngle >= 90) this.turned = 1 } else if (this.turned) { this.x -= this.speed } else { this.y += this.speed } } } else if (this.direction === 'left') { if (this.crossed === 0 && this.x < stopLines.left) { this.crossed = 1; state.vehiclesByDirection.left.crossed++ } if (checkAhead('x', this.x, this.stop, nextVeh?.x, nextVeh?.width)) { if (this.willTurn && this.x <= mid.left.x && !this.turned) { this.rotateAngle += rotationAngle; this.x -= 1.8; this.y -= 2.5 if (this.rotateAngle >= 90) this.turned = 1 } else if (this.turned) { this.y -= this.speed } else { this.x -= this.speed } } } else if (this.direction === 'up') { if (this.crossed === 0 && this.y < stopLines.up) { this.crossed = 1; state.vehiclesByDirection.up.crossed++ } if (checkAhead('y', this.y, this.stop, nextVeh?.y, nextVeh?.height, true)) { if (this.willTurn && this.y <= mid.up.y && !this.turned) { this.rotateAngle += rotationAngle; this.x += 1; this.y -= 1 if (this.rotateAngle >= 90) this.turned = 1 } else if (this.turned) { this.x += this.speed } else { this.y -= this.speed } } } } draw(ctx) { ctx.save() ctx.translate(this.x + this.width / 2, this.y + this.height / 2) ctx.rotate((this.rotateAngle * Math.PI) / 180) ctx.drawImage(this.image, -this.width / 2, -this.height / 2) ctx.restore() } } const Simulator = () => { const canvasRef = useRef(null) const [isLoaded, setIsLoaded] = useState(false) const imagesRef = useRef({}) const stateRef = useRef({ currentGreen: 0, currentYellow: 0, nextGreen: 1, signals: [], allVehicles: [], vehiclesByDirection: { right: { 0: [], 1: [], 2: [], crossed: 0 }, down: { 0: [], 1: [], 2: [], crossed: 0 }, left: { 0: [], 1: [], 2: [], crossed: 0 }, up: { 0: [], 1: [], 2: [], crossed: 0 } }, spawnPoints: { x: JSON.parse(JSON.stringify(baseStartX)), y: JSON.parse(JSON.stringify(baseStartY)) }, timeElapsed: 0, sessionActive: true, showStats: false }) useEffect(() => { const loadAssets = async () => { const images = {} const load = (src) => new Promise((res) => { const img = new Image(); img.src = src; img.onload = () => res(img) }) images.background = await load('/images/mod_int.png') images.red = await load('/images/signals/red.png') images.yellow = await load('/images/signals/yellow.png') images.green = await load('/images/signals/green.png') for (const dir of ['right', 'left', 'up', 'down']) { images[dir] = {} for (const type of ['bike', 'bus', 'car', 'rickshaw', 'truck']) { images[dir][type] = await load(`/images/${dir}/${type}.png`) } } imagesRef.current = images setIsLoaded(true) } loadAssets() const ts1 = new TrafficSignal(0, defaultYellow, defaultGreen) const ts2 = new TrafficSignal(ts1.red + ts1.yellow + ts1.green, defaultYellow, defaultGreen) const ts3 = new TrafficSignal(defaultRed, defaultYellow, defaultGreen) const ts4 = new TrafficSignal(defaultRed, defaultYellow, defaultGreen) stateRef.current.signals = [ts1, ts2, ts3, ts4] const setTime = () => { const s = stateRef.current const nextIdx = s.nextGreen const dir = directionNumbers[nextIdx] let cars = 0, bikes = 0, buses = 0, trucks = 0, rickshaws = 0 for (let i = 0; i < 3; i++) { s.vehiclesByDirection[dir][i].forEach(v => { if (v.crossed === 0) { if (v.vehicleClass === 'car') cars++ else if (v.vehicleClass === 'bike') bikes++ else if (v.vehicleClass === 'bus') buses++ else if (v.vehicleClass === 'truck') trucks++ else if (v.vehicleClass === 'rickshaw') rickshaws++ } }) } let greenTime = Math.ceil(((cars * carTime) + (rickshaws * rickshawTime) + (buses * busTime) + (trucks * truckTime) + (bikes * bikeTime)) / 3) if (greenTime < defaultMinimum) greenTime = defaultMinimum else if (greenTime > defaultMaximum) greenTime = defaultMaximum console.log(`Setting Next Green (${dir}) to ${greenTime}s`) s.signals[nextIdx].green = greenTime } const timer = setInterval(() => { const s = stateRef.current if (!s.sessionActive) return s.timeElapsed++ if (s.timeElapsed >= 300) { s.sessionActive = false s.showStats = true clearInterval(timer) return } const sig = s.signals[s.currentGreen] if (s.currentYellow === 0) { sig.green-- if (s.signals[s.nextGreen].red === detectionTime) { setTime() } if (sig.green <= 0) s.currentYellow = 1 } else { sig.yellow-- if (sig.yellow <= 0) { s.currentYellow = 0 sig.green = defaultGreen sig.yellow = defaultYellow sig.red = defaultRed s.currentGreen = s.nextGreen s.nextGreen = (s.currentGreen + 1) % 4 const curr = s.signals[s.currentGreen] s.signals[s.nextGreen].red = curr.yellow + curr.green } } s.signals.forEach((sig, i) => { if (i !== s.currentGreen) sig.red-- }) }, 1000) const spawner = setInterval(() => { if (!isLoaded) return const s = stateRef.current if (!s.sessionActive) return const typeNum = Math.floor(Math.random() * 5) const lane = typeNum === 4 ? 0 : Math.floor(Math.random() * 2) + 1 const prob = Math.random() * 1000 let dirNum = 0 if (prob < 400) dirNum = 0 // Right: 40% else if (prob < 800) dirNum = 1 // Down: 40% else if (prob < 900) dirNum = 2 // Left: 10% else dirNum = 3 // Up: 10% const dir = directionNumbers[dirNum] const type = vehicleTypes[typeNum] s.allVehicles.push(new Vehicle(lane, type, dirNum, dir, imagesRef.current[dir][type], s)) }, 750) return () => { clearInterval(timer); clearInterval(spawner) } }, [isLoaded]) useEffect(() => { if (!isLoaded) return const canvas = canvasRef.current const ctx = canvas.getContext('2d') let frameId const loop = () => { const s = stateRef.current ctx.drawImage(imagesRef.current.background, 0, 0, CANVAS_WIDTH, CANVAS_HEIGHT) s.signals.forEach((sig, i) => { let img = imagesRef.current.red let text = sig.red if (i === s.currentGreen) { if (s.currentYellow === 1) { img = imagesRef.current.yellow; text = sig.yellow } else { img = imagesRef.current.green; text = sig.green } } ctx.drawImage(img, signalCoods[i][0], signalCoods[i][1]) ctx.fillStyle = 'white'; ctx.font = 'bold 20px monospace' ctx.fillText(text > 0 ? text : (text <= -10 ? "---" : "GO"), signalTimerCoods[i][0], signalTimerCoods[i][1]) // Draw active vehicle count (CROSSED vehicles - matching simulation.py line 493) const crossedCount = s.vehiclesByDirection[directionNumbers[i]].crossed ctx.fillStyle = 'white'; ctx.fillRect(vehicleCountCoods[i][0], vehicleCountCoods[i][1] - 20, 40, 25) ctx.fillStyle = 'black'; ctx.font = 'bold 18px Arial' ctx.fillText(crossedCount, vehicleCountCoods[i][0] + 5, vehicleCountCoods[i][1]) }) s.allVehicles.forEach(v => { v.move(s); v.draw(ctx) }) s.allVehicles = s.allVehicles.filter(v => v.x > -200 && v.x < 1600 && v.y > -200 && v.y < 1000) frameId = requestAnimationFrame(loop) } frameId = requestAnimationFrame(loop) return () => cancelAnimationFrame(frameId) }, [isLoaded]) return (
Native Web Engine Portal
INITIALIZING ENGINE...
300 Second Analysis Concluded
Total Vehicles Processed