11import { useState } from "react" ;
22
33export type ExpectedStep =
4- | { kind : "assign" ; var : string ; val : number }
4+ | { kind : "assign" ; var : string ; val : number ; srcToken ?: string }
55 | { kind : "print" ; val : number } ;
66
77interface CodeboxProps {
@@ -19,7 +19,7 @@ function approxEqual(a: number, b: number): boolean {
1919}
2020
2121type AlienStep =
22- | { kind : "assign" ; var : string ; val : number ; lineNum : number }
22+ | { kind : "assign" ; var : string ; val : number ; lineNum : number ; srcToken ?: string }
2323 | { kind : "print" ; val : number ; lineNum : number } ;
2424
2525function checkTrace (
@@ -38,6 +38,8 @@ function checkTrace(
3838 if ( a . kind === "assign" && e . kind === "assign" ) {
3939 if ( a . var !== e . var || ! approxEqual ( a . val , e . val ) )
4040 return `Alien Error [Line ${ a . lineNum } ]: Translation incorrect` ;
41+ if ( e . srcToken !== undefined && a . srcToken !== e . srcToken )
42+ return `Alien Error [Line ${ a . lineNum } ]: Translation incorrect` ;
4143 }
4244 if ( a . kind === "print" && e . kind === "print" ) {
4345 if ( ! approxEqual ( a . val , e . val ) )
@@ -96,44 +98,40 @@ function runLevel1(
9698 }
9799
98100 vars [ varName ] = result ;
99- alienTrace . push ( { kind : "assign" , var : varName , val : result , lineNum } ) ;
101+ alienTrace . push ( { kind : "assign" , var : varName , val : result , lineNum, srcToken : valToken } ) ;
100102 }
101103
102- if ( vars [ "z" ] === undefined )
103- return { output : "No output" , error : true } ;
104-
105- const lastLine = lines [ lines . length - 1 ] . lineNum ;
106- alienTrace . push ( { kind : "print" , val : vars [ "z" ] , lineNum : lastLine } ) ;
107-
108104 const traceErr = checkTrace ( alienTrace , expectedTrace ) ;
109105 if ( traceErr ) return { output : traceErr , error : true } ;
110106
111- return { output : String ( vars [ "z" ] ) , error : false } ;
107+ return { output : "Correct! Well done!" , error : false } ;
112108}
113109
114110// ---- Level 2: :) assignment, :( print ----
115111// Expressions: only literal 1, vars x/y/z, operators +-*^, parens.
116- function validateExprL2 ( expr : string ) : boolean {
112+ // Returns null if valid, or the offending token string if invalid.
113+ function validateExprL2 ( expr : string ) : string | null {
117114 const s = expr . replace ( / \s / g, "" ) ;
118- if ( s . length === 0 || s . includes ( "**" ) ) return false ;
115+ if ( s . length === 0 ) return expr . trim ( ) || "empty" ;
116+ if ( s . includes ( "**" ) ) return "**" ;
119117 let i = 0 ;
120118 while ( i < s . length ) {
121119 const ch = s [ i ] ;
122120 if ( "xyz" . includes ( ch ) ) {
123- if ( i + 1 < s . length && / [ a - z A - Z ] / . test ( s [ i + 1 ] ) ) return false ;
121+ if ( i + 1 < s . length && / [ a - z A - Z ] / . test ( s [ i + 1 ] ) ) return ch + s [ i + 1 ] ;
124122 i ++ ;
125123 } else if ( ch === "1" ) {
126- if ( i + 1 < s . length && / [ 0 - 9 ] / . test ( s [ i + 1 ] ) ) return false ;
124+ if ( i + 1 < s . length && / [ 0 - 9 ] / . test ( s [ i + 1 ] ) ) return ch + s [ i + 1 ] ;
127125 i ++ ;
128126 } else if ( / [ 0 - 9 ] / . test ( ch ) ) {
129- return false ;
127+ return ch ;
130128 } else if ( "+-*^()" . includes ( ch ) ) {
131129 i ++ ;
132130 } else {
133- return false ;
131+ return ch ;
134132 }
135133 }
136- return true ;
134+ return null ;
137135}
138136
139137function runLevel2 (
@@ -170,8 +168,9 @@ function runLevel2(
170168 if ( assignMatch ) {
171169 const varName = assignMatch [ 1 ] ;
172170 const expr = assignMatch [ 2 ] . trim ( ) ;
173- if ( ! validateExprL2 ( expr ) )
174- return { output : `Alien Error [Line ${ lineNum } ]: Syntax Error` , error : true } ;
171+ const badToken = validateExprL2 ( expr ) ;
172+ if ( badToken !== null )
173+ return { output : `Alien Error [Line ${ lineNum } ]: Syntax Error ("${ badToken } " is not defined)` , error : true } ;
175174
176175 const exprClean = expr . replace ( / \s / g, "" ) ;
177176 for ( const v of [ "x" , "y" , "z" ] ) {
@@ -194,7 +193,13 @@ function runLevel2(
194193 continue ;
195194 }
196195
197- return { output : `Alien Error [Line ${ lineNum } ]: Syntax Error` , error : true } ;
196+ const badCh = line . split ( "" ) . find ( ch => ! / [ \s x y z : ( ) + \- * ^ 1 ] / . test ( ch ) ) ;
197+ return {
198+ output : badCh
199+ ? `Alien Error [Line ${ lineNum } ]: Syntax Error ("${ badCh } " is not defined)`
200+ : `Alien Error [Line ${ lineNum } ]: Syntax Error` ,
201+ error : true ,
202+ } ;
198203 }
199204
200205 if ( outputLines . length === 0 )
@@ -203,7 +208,7 @@ function runLevel2(
203208 const traceErr = checkTrace ( alienTrace , expectedTrace ) ;
204209 if ( traceErr ) return { output : traceErr , error : true } ;
205210
206- return { output : outputLines . join ( "\n" ) , error : false } ;
211+ return { output : outputLines . join ( "\n" ) + "\n\nCorrect!" , error : false } ;
207212}
208213
209214// ---- Level 3: array init + copy ops + output ----
@@ -264,7 +269,7 @@ function runLevel3(
264269 if ( expectedOutputStr && outputStr !== expectedOutputStr )
265270 return { output : "Alien Error: Translation incorrect" , error : true } ;
266271
267- return { output : outputStr , error : false } ;
272+ return { output : outputStr + "\n\nCorrect!" , error : false } ;
268273}
269274
270275// ---- component ----
@@ -298,6 +303,7 @@ export default function Codebox(props: CodeboxProps) {
298303 value = { code }
299304 onChange = { ( e ) => setCode ( e . target . value ) }
300305 placeholder = "Write alien code here!"
306+ spellCheck = { false }
301307 className = "p-6 text-2xl mx-4 rounded-lg resize-none flex-1 min-h-0"
302308 />
303309
0 commit comments