|
5 | 5 |
|
6 | 6 | import context from 'js-slang/context'; |
7 | 7 |
|
8 | | -const robotPos: Point = {x: 0, y: 0}; |
| 8 | +type Point = {x: number, y: number}; |
| 9 | +type Wall = {p1: Point, p2: Point}; |
9 | 10 |
|
10 | | -const DIRECTIONS = { |
11 | | - UP: 0, |
12 | | - RIGHT: 1, |
13 | | - DOWN: 2, |
14 | | - LEFT: 3 |
15 | | -}; |
| 11 | +type Polygon = Point[]; |
| 12 | + |
| 13 | +type StateData = { |
| 14 | + isInit: boolean, |
| 15 | + width: number, |
| 16 | + height: number, |
| 17 | + walls: Polygon[], |
| 18 | + movePoints: Point[], |
| 19 | + message: String, |
| 20 | + success: boolean, |
| 21 | + messages: String[] |
| 22 | +} |
16 | 23 |
|
17 | | -let robotRotation = 1; |
| 24 | +type Robot = { |
| 25 | + x: number; // the top left corner |
| 26 | + y: number; |
| 27 | + dx: number; |
| 28 | + dy: number; |
| 29 | + radius: number |
| 30 | +} |
18 | 31 |
|
19 | | -// default grid width and height is 25 |
20 | | -context.moduleContexts.robot_minigame.state = { |
| 32 | +let stateData: StateData = { |
21 | 33 | isInit: false, |
22 | | - width: 25, |
23 | | - height: 25, |
| 34 | + width: 500, |
| 35 | + height: 500, |
24 | 36 | walls: [], |
25 | 37 | movePoints: [], |
26 | 38 | message: 'moved successfully', |
27 | | - success: true |
28 | | -}; |
| 39 | + success: true, |
| 40 | + messages: [] |
| 41 | +} |
29 | 42 |
|
30 | | -type Point = {x: number, y: number}; |
31 | | -type Wall = {p1: Point, p2: Point}; |
| 43 | +let robot: Robot = { |
| 44 | + x: 25, // default start pos, puts it at the top left corner of canvas without colliding with the walls |
| 45 | + y: 25, |
| 46 | + dx: 1, |
| 47 | + dy: 0, |
| 48 | + radius: 20 //give the robot a circular hitbox |
| 49 | +} |
| 50 | + |
| 51 | +let bounds: Point[] = [] |
| 52 | + |
| 53 | +// default grid width and height is 25 |
| 54 | +context.moduleContexts.robot_minigame.state = stateData; |
32 | 55 |
|
33 | 56 | export function set_pos(x: number, y: number): void { |
34 | | - robotPos.x = x; |
35 | | - robotPos.y = y; |
| 57 | + robot.x = x; |
| 58 | + robot.y = y; |
36 | 59 | } |
37 | 60 |
|
38 | | -export function set_grid_width(width: number) { |
39 | | - context.moduleContexts.robot_minigame.state.width = width; |
| 61 | +export function set_width(width: number) { |
| 62 | + stateData.width = width; |
40 | 63 | } |
41 | 64 |
|
42 | | -export function set_grid_height(height: number) { |
43 | | - context.moduleContexts.robot_minigame.state.height = height; |
| 65 | +export function set_height(height: number) { |
| 66 | + stateData.height = height; |
44 | 67 | } |
45 | 68 |
|
46 | | -export function init(gridWidth: number, gridHeight: number, posX: number, posY: number) { |
47 | | - set_grid_width(gridWidth); |
48 | | - set_grid_height(gridHeight); |
| 69 | +export function init(width: number, height: number, posX: number, posY: number) { |
| 70 | + set_width(width); |
| 71 | + set_height(height); |
49 | 72 | set_pos(posX, posY); |
50 | | - context.moduleContexts.robot_minigame.state.movePoints.push({x: posX, y: posY}); |
51 | | - context.moduleContexts.robot_minigame.state.isInit = true; |
| 73 | + stateData.movePoints.push({x: posX, y: posY}); |
| 74 | + stateData.isInit = true; |
| 75 | + |
| 76 | + bounds = [ |
| 77 | + {x: 0, y: 0}, |
| 78 | + {x: width, y: 0}, |
| 79 | + {x: width, y: height}, |
| 80 | + {x: 0, y: height} |
| 81 | + ] |
| 82 | + |
52 | 83 | } |
53 | 84 |
|
54 | 85 | export function turn_left() { |
55 | 86 | if (alrCollided()) return; |
56 | 87 |
|
57 | | - robotRotation -= 1; |
58 | | - if (robotRotation < 0) { |
59 | | - robotRotation = 3; |
60 | | - } |
| 88 | + let currentAngle = Math.tan(robot.dy / robot.dx); |
| 89 | + currentAngle -= Math.PI / 2; |
| 90 | + |
| 91 | + robot.dx = Math.cos(currentAngle); |
| 92 | + robot.dy = Math.sin(currentAngle); |
61 | 93 | } |
62 | 94 |
|
63 | 95 | export function turn_right() { |
64 | 96 | if (alrCollided()) return; |
65 | 97 |
|
66 | | - robotRotation = (robotRotation + 1) % 4; |
| 98 | + let currentAngle = Math.tan(robot.dy / robot.dx); |
| 99 | + currentAngle += Math.PI / 2; |
| 100 | + |
| 101 | + robot.dx = Math.cos(currentAngle); |
| 102 | + robot.dy = Math.sin(currentAngle); |
67 | 103 | } |
68 | 104 |
|
69 | | -// takes the top left and bottom right corners of walls |
70 | | -// in terms of grid boxes |
71 | | -// grid starts from (0, 0) at the top left corner btw |
72 | | -export function set_wall(x1: number, y1: number, x2: number, y2: number) { |
73 | | - const wall: Wall = {p1: {x: x1, y: y1}, p2: {x: x2, y: y2}}; |
74 | | - context.moduleContexts.robot_minigame.state.walls.push(wall); |
| 105 | +export function set_rect_wall(x: number, y: number, width: number, height: number) { |
| 106 | + const polygon: Polygon = [ |
| 107 | + {x: x, y: y}, |
| 108 | + {x: x + width, y: y}, |
| 109 | + {x: x+width, y: y+height}, |
| 110 | + {x: x, y:y+height} |
| 111 | + ] |
| 112 | + |
| 113 | + stateData.walls.push(polygon); |
75 | 114 | } |
76 | 115 |
|
77 | | -export function move_forward(dist: number): void { |
| 116 | +export function move_forward(): void { |
78 | 117 | if (alrCollided()) return; |
79 | 118 |
|
80 | | - simulate(dist); |
| 119 | + const collisionPoint: Point | null = raycast(stateData.walls) |
| 120 | + if (collisionPoint !== null) { |
| 121 | + let nextPoint: Point = { |
| 122 | + x: collisionPoint.x - robot.dx * (robot.radius + 5), |
| 123 | + y: collisionPoint.y - robot.dy * (robot.radius + 5) |
| 124 | + } |
| 125 | + |
| 126 | + robot.x = nextPoint.x; |
| 127 | + robot.y = nextPoint.y; |
| 128 | + stateData.movePoints.push(nextPoint); |
| 129 | + } |
81 | 130 | } |
82 | 131 |
|
83 | 132 | export function getX():number { |
84 | | - return robotPos.x; |
| 133 | + return robot.x; |
85 | 134 | } |
86 | 135 |
|
87 | 136 | export function getY():number { |
88 | | - return robotPos.y; |
89 | | -} |
90 | | - |
91 | | -function simulate(moveDist: number) { |
92 | | - let dx: number = 0; |
93 | | - let dy: number = 0; |
94 | | - switch (robotRotation) { |
95 | | - case DIRECTIONS.UP: |
96 | | - dy = -1; |
97 | | - break; |
98 | | - case DIRECTIONS.RIGHT: |
99 | | - dx = 1; |
100 | | - break; |
101 | | - case DIRECTIONS.DOWN: |
102 | | - dy = 1; |
103 | | - break; |
104 | | - case DIRECTIONS.LEFT: |
105 | | - dx = -1; |
106 | | - break; |
107 | | - } |
| 137 | + return robot.y; |
| 138 | +} |
108 | 139 |
|
109 | | - // moves robot by one grid box and checks collision |
110 | | - for (let i = 0; i < moveDist; i++) { |
111 | | - robotPos.x += dx; |
112 | | - robotPos.y += dy; |
113 | | - |
114 | | - const walls = context.moduleContexts.robot_minigame.state.walls; |
115 | | - for (let j = 0; j < walls.length; j++) { |
116 | | - if (checkWallCollision(walls[j], robotPos)) { |
117 | | - context.moduleContexts.robot_minigame.state.success = false; |
118 | | - context.moduleContexts.robot_minigame.state.message = 'collided'; |
119 | | - context.moduleContexts.robot_minigame.state.movePoints.push({x: robotPos.x, y: robotPos.y}); |
120 | | - return; |
| 140 | +function raycast(polygons: Polygon[]): Point | null { |
| 141 | + let nearest: Point | null = null; |
| 142 | + let minDist = Infinity; |
| 143 | + |
| 144 | + for (const polygon of polygons) { |
| 145 | + stateData.messages.push("checking polygon"); |
| 146 | + const numVertices = polygon.length; |
| 147 | + |
| 148 | + for (let i = 0; i < numVertices; i++) { |
| 149 | + const x1 = polygon[i].x, y1 = polygon[i].y; |
| 150 | + const x2 = polygon[(i + 1) % numVertices].x, y2 = polygon[(i + 1) % numVertices].y; |
| 151 | + |
| 152 | + const intersection = getIntersection(robot.x, robot.y, robot.dx + robot.x, robot.dy + robot.y, x1, y1, x2, y2); |
| 153 | + |
| 154 | + if (intersection.collided && intersection.dist < minDist) { |
| 155 | + minDist = intersection.dist |
| 156 | + nearest = {x: intersection.x, y: intersection.y}; |
| 157 | + } |
121 | 158 | } |
122 | | - } |
123 | 159 | } |
124 | 160 |
|
125 | | - context.moduleContexts.robot_minigame.state.movePoints.push({x: robotPos.x, y: robotPos.y}); |
126 | | - |
127 | | - // OLD CODE |
128 | | - // let destX = robotPos.x + moveDist * Math.cos(robotAngle); |
129 | | - // let destY = robotPos.y + moveDist * Math.sin(robotAngle); |
130 | | - // let destPoint: Point = {x: destX, y: destY} |
| 161 | + // if no collisions with obstacles, check the outer bounds of map |
| 162 | + if (nearest === null) { |
| 163 | + for (let i = 0; i < bounds.length; i++) { |
| 164 | + const x1 = bounds[i].x, y1 = bounds[i].y; |
| 165 | + const x2 = bounds[(i + 1) % bounds.length].x, y2 = bounds[(i + 1) % bounds.length].y; |
131 | 166 |
|
132 | | - // for (let i = 0; i < steps; i++) { |
133 | | - // let distX: number = moveSpeed * Math.cos(robotAngle); |
134 | | - // let distY: number = moveSpeed * Math.sin(robotAngle); |
| 167 | + const intersection = getIntersection(robot.x, robot.y, robot.dx + robot.x, robot.dy + robot.y, x1, y1, x2, y2); |
135 | 168 |
|
136 | | - // robotPos.x += distX; |
137 | | - // robotPos.y += distY; |
138 | | - |
139 | | - // for (let j = 0; j < walls.length; j++) { |
140 | | - // if(checkWallCollision(walls[j], robotPos)) { |
141 | | - // addMessage("Collided with wall!!"); |
142 | | - // addMessage(`Position: (${robotPos.x.toFixed(3)}, ${robotPos.y.toFixed(3)}), Rotation: ${robotAngle}\n`); |
143 | | - // return; |
144 | | - // } |
145 | | - // } |
146 | | - |
147 | | - // if (distanceBetween(destPoint, robotPos) < robotRadius) { |
148 | | - // robotPos = destPoint; |
149 | | - // addMessage(`Robot moved forward by ${moveDist} at angle ${robotAngle} radians\n`); |
150 | | - // addMessage(`Position: (${robotPos.x.toFixed(3)}, ${robotPos.y.toFixed(3)}), Rotation: ${robotAngle}\n`); |
151 | | - // return; |
152 | | - // } |
153 | | - // } |
| 169 | + if (intersection.collided && intersection.dist < minDist) { |
| 170 | + minDist = intersection.dist |
| 171 | + nearest = {x: intersection.x, y: intersection.y}; |
| 172 | + } |
| 173 | + } |
| 174 | + } |
154 | 175 |
|
| 176 | + return nearest; // Closest intersection point |
155 | 177 | } |
156 | 178 |
|
157 | | -function checkWallCollision(wall: Wall, pos: Point): boolean { |
158 | | - // // Apply the distance formula |
159 | | - // const p1 = wall.p1; |
160 | | - // const p2 = wall.p2; |
161 | | - |
162 | | - // const numerator = Math.abs((p2.y - p1.y) * pos.x - (p2.x - p1.x) * pos.y + p2.x * p1.y - p2.y * p1.x); |
163 | | - // const denominator = Math.sqrt((p2.y - p1.y) ** 2 + (p2.x - p1.x) ** 2); |
164 | | - |
165 | | - // return numerator / denominator < robotRadius; |
166 | | - |
167 | | - const p1 = wall.p1; |
168 | | - const p2 = wall.p2; |
169 | | - |
170 | | - const minX = Math.min(p1.x, p2.x); |
171 | | - const maxX = Math.max(p1.x, p2.x); |
172 | | - const minY = Math.min(p1.y, p2.y); |
173 | | - const maxY = Math.max(p1.y, p2.y); |
174 | | - |
175 | | - return pos.x >= minX && pos.x <= maxX && pos.y >= minY && pos.y <= maxY; |
| 179 | +//Determine if a ray and a line segment intersect, and if so, determine the collision point |
| 180 | +function getIntersection(x1, y1, x2, y2, x3, y3, x4, y4){ |
| 181 | + var denom = ((x2 - x1)*(y4 - y3)-(y2 - y1)*(x4 - x3)); |
| 182 | + var r; |
| 183 | + var s; |
| 184 | + var x; |
| 185 | + var y; |
| 186 | + var b = false; |
| 187 | + |
| 188 | + //If lines not collinear or parallel |
| 189 | + if(denom != 0){ |
| 190 | + //Intersection in ray "local" coordinates |
| 191 | + r = (((y1 - y3) * (x4 - x3)) - (x1 - x3) * (y4 - y3)) / denom; |
| 192 | + |
| 193 | + //Intersection in segment "local" coordinates |
| 194 | + s = (((y1 - y3) * (x2 - x1)) - (x1 - x3) * (y2 - y1)) / denom; |
| 195 | + |
| 196 | + //The algorithm gives the intersection of two infinite lines, determine if it lies on the side that the ray is defined on |
| 197 | + if (r >= 0) |
| 198 | + { |
| 199 | + //If point along the line segment |
| 200 | + if (s >= 0 && s <= 1) |
| 201 | + { |
| 202 | + b = true; |
| 203 | + //Get point coordinates (offset by r local units from start of ray) |
| 204 | + x = x1 + r * (x2 - x1); |
| 205 | + y = y1 + r * (y2 - y1); |
| 206 | + } |
| 207 | + } |
| 208 | + } |
| 209 | + var p = {collided: b, x: x, y: y, dist: r}; |
| 210 | + return p; |
176 | 211 | } |
177 | 212 |
|
178 | 213 | function alrCollided() { |
179 | | - return !context.moduleContexts.robot_minigame.state.success; |
| 214 | + return !stateData.success; |
180 | 215 | } |
0 commit comments