@@ -87,55 +87,6 @@ let bounds: Point[];
8787// sets the context to the state obj, mostly for convenience so i dont have to type context.... everytime
8888context . moduleContexts . robot_minigame . state = state ;
8989
90- /**
91- * Teleport the robot
92- *
93- * @param x coordinate of the robot
94- * @param y coordinate of the robot
95- */
96- function set_pos (
97- x : number ,
98- y : number
99- ) {
100- // Init functions should not run after initialization
101- if ( state . isInit ) return ;
102-
103- robot . x = x ;
104- robot . y = y ;
105- }
106-
107- /**
108- * Set the rotation of the robot
109- *
110- * @param rotation in radians
111- */
112- function set_rotation (
113- rotation : number
114- ) {
115- // Init functions should not run after initialization
116- if ( state . isInit ) return ;
117-
118- robot . dx = Math . cos ( rotation ) ;
119- robot . dy = - Math . sin ( rotation ) ;
120- }
121-
122- /**
123- * Set the width and height of the map
124- *
125- * @param width of the map
126- * @param height of the map
127- */
128- function set_dimensions (
129- width : number ,
130- height : number
131- ) {
132- // Init functions should not run after initialization
133- if ( state . isInit ) return ;
134-
135- state . width = width ;
136- state . height = height ;
137- }
138-
13990// ===== //
14091// SETUP //
14192// ===== //
@@ -183,34 +134,80 @@ export function init(
183134 * @param flags any additional flags the area may have
184135 */
185136export function create_area (
186- rawVertices : number [ ] ,
137+ vertices : number [ ] ,
187138 isObstacle : boolean ,
188- flags : AreaFlags = { }
139+ flags : any [ ]
189140) {
190141 // Init functions should not run after initialization
191142 if ( state . isInit ) return ;
192143
193- if ( rawVertices . length % 2 !== 0 ) throw new Error ( 'Odd number of arguments given (expected even)' ) ;
144+ if ( vertices . length % 2 !== 0 ) throw new Error ( 'Odd number of vertice x-y coordinates given (expected even)' ) ;
145+
146+ if ( flags . length % 2 !== 0 ) throw new Error ( 'Odd number of flag arguments given (expected even)' ) ;
194147
195148 // Store vertices as Point array
196- const vertices : Point [ ] = [ ] ;
149+ const parsedVertices : Point [ ] = [ ] ;
197150
198151 // Parse x-y pairs into Points
199- for ( let i = 0 ; i < rawVertices . length / 2 ; i ++ ) {
200- vertices [ i ] = {
201- x : rawVertices [ i * 2 ] ,
202- y : rawVertices [ i * 2 + 1 ]
152+ for ( let i = 0 ; i < vertices . length / 2 ; i ++ ) {
153+ parsedVertices [ i ] = {
154+ x : vertices [ i * 2 ] ,
155+ y : vertices [ i * 2 + 1 ]
203156 } ;
204157 }
205158
159+ // Store flags as an object
160+ const parsedFlags = { } ;
161+
162+ // Parse flag-value pairs into flags
163+ for ( let i = 0 ; i < flags . length / 2 ; i ++ ) {
164+ // Retrieve flag
165+ const flag = flags [ i * 2 ] ;
166+
167+ // Check flag is string
168+ if ( typeof flag !== 'string' ) throw new Error ( `Flag arguments must be strings (${ flag } is a ${ typeof flag } )` ) ;
169+
170+ // Add flag to object
171+ parsedFlags [ flag ] = flags [ i * 2 + 1 ] ;
172+ }
173+
206174 // Store the new area
207175 state . areas . push ( {
208- vertices,
176+ vertices : parsedVertices ,
209177 isObstacle,
210- flags
178+ flags : parsedFlags
211179 } ) ;
212180}
213181
182+ /**
183+ * Creates a new rectangular, axis-aligned area
184+ *
185+ * @param x top left corner of the rectangle
186+ * @param y top right corner of the rectangle
187+ * @param width of the rectangle
188+ * @param height of the rectangle
189+ * @param isObstacle a boolean indicating if the area is an obstacle or not
190+ * @param flags any additional flags the area may have
191+ */
192+ export function create_rect_area (
193+ x : number ,
194+ y : number ,
195+ width : number ,
196+ height : number ,
197+ isObstacle : boolean ,
198+ flags : any [ ]
199+ ) {
200+ // Init functions should not run after initialization
201+ if ( state . isInit ) return ;
202+
203+ create_area ( [
204+ x , y ,
205+ x + width , y ,
206+ x + width , y + height ,
207+ x , y + height
208+ ] , isObstacle , flags ) ;
209+ }
210+
214211/**
215212 * Creates a new obstacle
216213 *
@@ -222,7 +219,7 @@ export function create_obstacle(
222219 // Init functions should not run after initialization
223220 if ( state . isInit ) return ;
224221
225- create_area ( vertices , true ) ;
222+ create_area ( vertices , true , [ ] ) ;
226223}
227224
228225/**
@@ -242,12 +239,7 @@ export function create_rect_obstacle(
242239 // Init functions should not run after initialization
243240 if ( state . isInit ) return ;
244241
245- create_obstacle ( [
246- x , y ,
247- x + width , y ,
248- x + width , y + height ,
249- x , y + height
250- ] ) ;
242+ create_rect_area ( x , y , width , height , true , [ ] ) ;
251243}
252244
253245/**
@@ -425,6 +417,15 @@ export function turn_right() {
425417// TESTING //
426418// ======= //
427419
420+ /**
421+ * Inform the simulator that the testing phase is starting
422+ */
423+ export function start_testing ( ) {
424+ if ( state . isComplete ) throw new Error ( 'May not start testing twice!' ) ;
425+
426+ state . isComplete = true ;
427+ }
428+
428429/**
429430 * Checks if the robot's entered areas satisfy the condition
430431 *
@@ -433,9 +434,74 @@ export function turn_right() {
433434export function entered_areas (
434435 callback : ( areas : Area [ ] ) => boolean
435436) : boolean {
437+ // Testing functions should only run after the simulation is complete
438+ if ( ! state . isComplete ) return false ;
439+
436440 return callback ( state . areaLog ) ;
437441}
438442
443+ /**
444+ * Check if the robot has entered different areas with the given colors in order
445+ *
446+ * @param colors in order
447+ * @returns if the robot entered the given colors in order
448+ */
449+ export function entered_colors (
450+ colors : string [ ]
451+ ) : boolean {
452+ // Testing functions should only run after the simulation is complete
453+ if ( ! state . isComplete ) return false ;
454+
455+ return state . areaLog
456+ . filter ( area => colors . includes ( area . flags . color ) ) // Filter relevant colors
457+ . filter ( filterAdjacentDuplicateAreas ) // Filter adjacent duplicates
458+ . every ( ( { flags : { color } } , i ) => color === colors [ i ] ) ; // Check if each area has the expected color
459+ }
460+
461+ // ================== //
462+ // DATA WRITE HELPERS //
463+ // ================== //
464+
465+ /**
466+ * Teleport the robot
467+ *
468+ * @param x coordinate of the robot
469+ * @param y coordinate of the robot
470+ */
471+ function set_pos (
472+ x : number ,
473+ y : number
474+ ) {
475+ robot . x = x ;
476+ robot . y = y ;
477+ }
478+
479+ /**
480+ * Set the rotation of the robot
481+ *
482+ * @param rotation in radians
483+ */
484+ function set_rotation (
485+ rotation : number
486+ ) {
487+ robot . dx = Math . cos ( rotation ) ;
488+ robot . dy = - Math . sin ( rotation ) ;
489+ }
490+
491+ /**
492+ * Set the width and height of the map
493+ *
494+ * @param width of the map
495+ * @param height of the map
496+ */
497+ function set_dimensions (
498+ width : number ,
499+ height : number
500+ ) {
501+ state . width = width ;
502+ state . height = height ;
503+ }
504+
439505// ================= //
440506// DATA READ HELPERS //
441507// ================= //
@@ -707,3 +773,15 @@ function areaEquals(a: Area, b: Area) {
707773
708774 return true ;
709775}
776+
777+ /**
778+ * Filter callback to remove adjacent duplicate areas
779+ *
780+ * @param area currently being checked
781+ * @param i index of area
782+ * @param areas the full array being filtered
783+ * @returns if the current area is not a duplicate of the previous area
784+ */
785+ const filterAdjacentDuplicateAreas = ( area : Area , i : number , areas : Area [ ] ) : boolean =>
786+ i === 0 // First one is always correct
787+ || ! areaEquals ( area , areas [ i - 1 ] ) ; // Otherwise check for equality against previous area
0 commit comments