Skip to content

Commit f3c7c8d

Browse files
author
rmen527
committed
Added more levels
1 parent b7ac5db commit f3c7c8d

8 files changed

Lines changed: 362 additions & 137 deletions

File tree

public/1.png

7.46 KB
Loading

public/2.png

16.3 KB
Loading

public/3.png

17.6 KB
Loading

public/inf.png

14.6 KB
Loading

src/components/Codebox.tsx

Lines changed: 182 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { useState } from "react";
22

3-
// ---- types shared between executor and props ----
43
export type ExpectedStep =
54
| { kind: "assign"; var: string; val: number }
65
| { kind: "print"; val: number };
@@ -11,29 +10,123 @@ interface CodeboxProps {
1110
headerimage: string;
1211
onOutput: (output: string, isError: boolean) => void;
1312
expectedTrace?: ExpectedStep[];
13+
expectedOutputStr?: string;
14+
level: number;
1415
}
1516

16-
// ---- expression validator ----
17-
// Allowed: single-char vars x/y/z, literal 1 only, operators +-*^, parens.
18-
// Spaces are ignored. :) and :( must be together (enforced by caller regex).
19-
function validateExpression(expr: string): boolean {
20-
const s = expr.replace(/\s/g, "");
21-
if (s.length === 0) return false;
22-
if (s.includes("**")) return false;
17+
function approxEqual(a: number, b: number): boolean {
18+
return Math.abs(a - b) < 1e-9;
19+
}
20+
21+
type AlienStep =
22+
| { kind: "assign"; var: string; val: number; lineNum: number }
23+
| { kind: "print"; val: number; lineNum: number };
24+
25+
function checkTrace(
26+
alienTrace: AlienStep[],
27+
expectedTrace: ExpectedStep[] | undefined
28+
): string | null {
29+
if (!expectedTrace || expectedTrace.length === 0) return null;
30+
if (alienTrace.length !== expectedTrace.length)
31+
return "Alien Error: Translation incorrect";
32+
33+
for (let i = 0; i < alienTrace.length; i++) {
34+
const a = alienTrace[i];
35+
const e = expectedTrace[i];
36+
if (a.kind !== e.kind)
37+
return `Alien Error [Line ${a.lineNum}]: Translation incorrect`;
38+
if (a.kind === "assign" && e.kind === "assign") {
39+
if (a.var !== e.var || !approxEqual(a.val, e.val))
40+
return `Alien Error [Line ${a.lineNum}]: Translation incorrect`;
41+
}
42+
if (a.kind === "print" && e.kind === "print") {
43+
if (!approxEqual(a.val, e.val))
44+
return `Alien Error [Line ${a.lineNum}]: Translation incorrect`;
45+
}
46+
}
47+
return null;
48+
}
2349

50+
// ---- Level 1: HI / YAY / NO ----
51+
// HI var, value → var = value
52+
// YAY var, value → var += value
53+
// NO var, value → var -= value
54+
// value: integer literal or single variable x/y/z
55+
// Implicit print z at the end if z is defined.
56+
function runLevel1(
57+
code: string,
58+
expectedTrace?: ExpectedStep[]
59+
): { output: string; error: boolean } {
60+
const lines = code
61+
.split("\n")
62+
.map((text, i) => ({ text: text.trim(), lineNum: i + 1 }))
63+
.filter((l) => l.text.length > 0);
64+
65+
if (lines.length === 0)
66+
return { output: "Alien Error [Line 1]: Syntax Error", error: true };
67+
68+
const STMT_RE = /^(HI|YAY|NO)\s+([xyz])\s*,\s*([xyz]|[0-9]+)$/;
69+
const vars: Record<string, number> = {};
70+
const alienTrace: AlienStep[] = [];
71+
72+
for (const { text: line, lineNum } of lines) {
73+
const m = line.match(STMT_RE);
74+
if (!m)
75+
return { output: `Alien Error [Line ${lineNum}]: Syntax Error`, error: true };
76+
77+
const [, op, varName, valToken] = m;
78+
const isRef = /^[xyz]$/.test(valToken);
79+
80+
if (isRef && vars[valToken] === undefined)
81+
return { output: `Alien Error [Line ${lineNum}]: Syntax Error`, error: true };
82+
83+
const val = isRef ? vars[valToken] : Number(valToken);
84+
85+
let result: number;
86+
if (op === "HI") {
87+
result = val;
88+
} else if (op === "YAY") {
89+
if (vars[varName] === undefined)
90+
return { output: `Alien Error [Line ${lineNum}]: Syntax Error`, error: true };
91+
result = vars[varName] + val;
92+
} else {
93+
if (vars[varName] === undefined)
94+
return { output: `Alien Error [Line ${lineNum}]: Syntax Error`, error: true };
95+
result = vars[varName] - val;
96+
}
97+
98+
vars[varName] = result;
99+
alienTrace.push({ kind: "assign", var: varName, val: result, lineNum });
100+
}
101+
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+
108+
const traceErr = checkTrace(alienTrace, expectedTrace);
109+
if (traceErr) return { output: traceErr, error: true };
110+
111+
return { output: String(vars["z"]), error: false };
112+
}
113+
114+
// ---- Level 2: :) assignment, :( print ----
115+
// Expressions: only literal 1, vars x/y/z, operators +-*^, parens.
116+
function validateExprL2(expr: string): boolean {
117+
const s = expr.replace(/\s/g, "");
118+
if (s.length === 0 || s.includes("**")) return false;
24119
let i = 0;
25120
while (i < s.length) {
26121
const ch = s[i];
27122
if ("xyz".includes(ch)) {
28-
// no multi-char identifiers
29123
if (i + 1 < s.length && /[a-zA-Z]/.test(s[i + 1])) return false;
30124
i++;
31125
} else if (ch === "1") {
32-
// only 1 is a valid literal — 11, 12, etc. are invalid
33126
if (i + 1 < s.length && /[0-9]/.test(s[i + 1])) return false;
34127
i++;
35128
} else if (/[0-9]/.test(ch)) {
36-
return false; // 2, 3, … all forbidden
129+
return false;
37130
} else if ("+-*^()".includes(ch)) {
38131
i++;
39132
} else {
@@ -43,75 +136,56 @@ function validateExpression(expr: string): boolean {
43136
return true;
44137
}
45138

46-
function approxEqual(a: number, b: number): boolean {
47-
return Math.abs(a - b) < 1e-9;
48-
}
49-
50-
// ---- interpreter ----
51-
type AlienStep =
52-
| { kind: "assign"; var: string; val: number; lineNum: number }
53-
| { kind: "print"; val: number; lineNum: number };
54-
55-
function runAlienCode(code: string, expectedTrace?: ExpectedStep[]): { output: string; error: boolean } {
56-
// Trim every line, drop blanks, keep original line numbers
57-
const lines: Array<{ text: string; lineNum: number }> = code
139+
function runLevel2(
140+
code: string,
141+
expectedTrace?: ExpectedStep[]
142+
): { output: string; error: boolean } {
143+
const lines = code
58144
.split("\n")
59145
.map((text, i) => ({ text: text.trim(), lineNum: i + 1 }))
60146
.filter((l) => l.text.length > 0);
61147

62-
if (lines.length === 0) {
148+
if (lines.length === 0)
63149
return { output: "Alien Error [Line 1]: Syntax Error", error: true };
64-
}
65150

66-
// :) assignment — space allowed around :) but NOT inside it
67-
const ASSIGNMENT_RE = /^([xyz])\s*:\)\s*(.+)$/;
68-
// :( print — any of x/y/z, space allowed before :(, NOT inside it
151+
const ASSIGN_RE = /^([xyz])\s*:\)\s*(.+)$/;
69152
const PRINT_RE = /^([xyz])\s*:\($/;
70-
71153
const vars: Record<string, number> = {};
72154
const alienTrace: AlienStep[] = [];
73155
const outputLines: string[] = [];
74156

75157
for (const { text: line, lineNum } of lines) {
76-
// --- print ---
77158
const printMatch = line.match(PRINT_RE);
78159
if (printMatch) {
79160
const v = printMatch[1];
80-
if (vars[v] === undefined) {
161+
if (vars[v] === undefined)
81162
return { output: `Alien Error [Line ${lineNum}]: Syntax Error`, error: true };
82-
}
83163
const val = vars[v];
84164
outputLines.push(String(val));
85165
alienTrace.push({ kind: "print", val, lineNum });
86166
continue;
87167
}
88168

89-
// --- assignment ---
90-
const assignMatch = line.match(ASSIGNMENT_RE);
169+
const assignMatch = line.match(ASSIGN_RE);
91170
if (assignMatch) {
92171
const varName = assignMatch[1];
93172
const expr = assignMatch[2].trim();
94-
95-
if (!validateExpression(expr)) {
173+
if (!validateExprL2(expr))
96174
return { output: `Alien Error [Line ${lineNum}]: Syntax Error`, error: true };
97-
}
98175

99-
// every referenced variable must be defined
100176
const exprClean = expr.replace(/\s/g, "");
101177
for (const v of ["x", "y", "z"]) {
102-
if (exprClean.includes(v) && vars[v] === undefined) {
178+
if (exprClean.includes(v) && vars[v] === undefined)
103179
return { output: `Alien Error [Line ${lineNum}]: Syntax Error`, error: true };
104-
}
105180
}
106181

107182
try {
108183
const jsExpr = expr.replace(/\^/g, "**");
109184
const result = new Function("x", "y", "z", `"use strict"; return (${jsExpr})`)(
110185
vars["x"], vars["y"], vars["z"]
111186
);
112-
if (typeof result !== "number" || !isFinite(result)) {
187+
if (typeof result !== "number" || !isFinite(result))
113188
return { output: `Alien Error [Line ${lineNum}]: Syntax Error`, error: true };
114-
}
115189
vars[varName] = result;
116190
alienTrace.push({ kind: "assign", var: varName, val: result, lineNum });
117191
} catch {
@@ -120,43 +194,77 @@ function runAlienCode(code: string, expectedTrace?: ExpectedStep[]): { output: s
120194
continue;
121195
}
122196

123-
// --- unrecognised line ---
124197
return { output: `Alien Error [Line ${lineNum}]: Syntax Error`, error: true };
125198
}
126199

127-
if (outputLines.length === 0) {
200+
if (outputLines.length === 0)
128201
return { output: "No output", error: true };
129-
}
130202

131-
// ---- line-by-line translation check ----
132-
if (expectedTrace && expectedTrace.length > 0) {
133-
if (alienTrace.length !== expectedTrace.length) {
134-
return { output: "Alien Error: Translation incorrect", error: true };
135-
}
203+
const traceErr = checkTrace(alienTrace, expectedTrace);
204+
if (traceErr) return { output: traceErr, error: true };
136205

137-
for (let i = 0; i < alienTrace.length; i++) {
138-
const a = alienTrace[i];
139-
const e = expectedTrace[i];
206+
return { output: outputLines.join("\n"), error: false };
207+
}
140208

141-
if (a.kind !== e.kind) {
142-
return { output: `Alien Error [Line ${a.lineNum}]: Translation incorrect`, error: true };
143-
}
209+
// ---- Level 3: array init + copy ops + output ----
210+
// "[N,...] into nums" — initialise
211+
// "nums[A] into nums[B]" — copy (1-indexed)
212+
// "output nums" — print
213+
function runLevel3(
214+
code: string,
215+
expectedOutputStr?: string
216+
): { output: string; error: boolean } {
217+
const lines = code
218+
.split("\n")
219+
.map((text, i) => ({ text: text.trim(), lineNum: i + 1 }))
220+
.filter((l) => l.text.length > 0);
144221

145-
if (a.kind === "assign" && e.kind === "assign") {
146-
if (a.var !== e.var || !approxEqual(a.val, e.val)) {
147-
return { output: `Alien Error [Line ${a.lineNum}]: Translation incorrect`, error: true };
148-
}
149-
}
222+
if (lines.length === 0)
223+
return { output: "Alien Error [Line 1]: Syntax Error", error: true };
150224

151-
if (a.kind === "print" && e.kind === "print") {
152-
if (!approxEqual(a.val, e.val)) {
153-
return { output: `Alien Error [Line ${a.lineNum}]: Translation incorrect`, error: true };
154-
}
155-
}
225+
const INIT_RE = /^\[\s*(\d+(?:\s*,\s*\d+)*)\s*\]\s+into\s+nums$/;
226+
const COPY_RE = /^nums\s*\[\s*(\d+)\s*\]\s+into\s+nums\s*\[\s*(\d+)\s*\]$/;
227+
const OUTPUT_RE = /^output\s+nums$/;
228+
229+
const firstLine = lines[0];
230+
const initMatch = firstLine.text.match(INIT_RE);
231+
if (!initMatch)
232+
return { output: "Alien Error [Line 1]: Syntax Error", error: true };
233+
234+
const nums = initMatch[1].split(",").map((s) => Number(s.trim()));
235+
const len = nums.length;
236+
const outputLines: string[] = [];
237+
238+
for (let i = 1; i < lines.length; i++) {
239+
const { text: line, lineNum } = lines[i];
240+
241+
const copyMatch = line.match(COPY_RE);
242+
if (copyMatch) {
243+
const from = Number(copyMatch[1]);
244+
const to = Number(copyMatch[2]);
245+
if (from < 1 || from > len || to < 1 || to > len)
246+
return { output: `Alien Error [Line ${lineNum}]: Syntax Error`, error: true };
247+
nums[to - 1] = nums[from - 1];
248+
continue;
156249
}
250+
251+
if (OUTPUT_RE.test(line)) {
252+
outputLines.push("[" + nums.join(", ") + "]");
253+
continue;
254+
}
255+
256+
return { output: `Alien Error [Line ${lineNum}]: Syntax Error`, error: true };
157257
}
158258

159-
return { output: outputLines.join("\n"), error: false };
259+
if (outputLines.length === 0)
260+
return { output: "No output", error: true };
261+
262+
const outputStr = outputLines.join("\n");
263+
264+
if (expectedOutputStr && outputStr !== expectedOutputStr)
265+
return { output: "Alien Error: Translation incorrect", error: true };
266+
267+
return { output: outputStr, error: false };
160268
}
161269

162270
// ---- component ----
@@ -167,16 +275,19 @@ export default function Codebox(props: CodeboxProps) {
167275
const handleRun = () => {
168276
setRunning(true);
169277
setTimeout(() => {
170-
const { output, error } = runAlienCode(code, props.expectedTrace);
171-
props.onOutput(output, error);
278+
let result;
279+
if (props.level === 1) result = runLevel1(code, props.expectedTrace);
280+
else if (props.level === 2) result = runLevel2(code, props.expectedTrace);
281+
else result = runLevel3(code, props.expectedOutputStr);
282+
props.onOutput(result.output, result.error);
172283
setRunning(false);
173284
}, 300);
174285
};
175286

176287
return (
177288
<div
178289
style={{ backgroundColor: props.lightcol, boxShadow: `0 12px 0 ${props.darkcol}` }}
179-
className="rounded-2xl overflow-hidden flex flex-col mt-25"
290+
className="rounded-2xl overflow-hidden flex flex-col mt-25 flex-1 min-h-0"
180291
>
181292
<div className="flex items-center justify-center p-4">
182293
<img src={"/" + props.headerimage} alt={props.headerimage} className="relative h-12 w-auto object-contain" />
@@ -187,7 +298,7 @@ export default function Codebox(props: CodeboxProps) {
187298
value={code}
188299
onChange={(e) => setCode(e.target.value)}
189300
placeholder="Write alien code here!"
190-
className="p-6 text-2xl mx-4 rounded-lg resize-none min-h-[300px]"
301+
className="p-6 text-2xl mx-4 rounded-lg resize-none flex-1 min-h-0"
191302
/>
192303

193304
<div className="m-4 mx-4 h-14">

src/components/LevelSelect.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const LEVELS = [
2+
{ id: 1, image: "1.png" },
3+
{ id: 2, image: "2.png" },
4+
{ id: 3, image: "3.png" },
5+
];
6+
7+
interface LevelSelectProps {
8+
active: number;
9+
onSelect: (level: number) => void;
10+
}
11+
12+
export default function LevelSelect({ active, onSelect }: LevelSelectProps) {
13+
return (
14+
<div className="flex flex-col gap-4 justify-center self-stretch">
15+
{LEVELS.map(({ id, image }) => {
16+
const isActive = id === active;
17+
return (
18+
<button
19+
key={id}
20+
onClick={() => onSelect(id)}
21+
disabled={isActive}
22+
style={{
23+
backgroundColor: isActive ? "#3A3A3A" : "#5E5E5E",
24+
boxShadow: isActive ? "0 6px 0 #1A1A1A" : "0 6px 0 #2E2E2E",
25+
cursor: isActive ? "default" : "pointer",
26+
}}
27+
className={`w-20 h-20 rounded-2xl flex items-center justify-center flex-shrink-0 ${!isActive && "hover:brightness-110 active:brightness-90"}`}
28+
>
29+
<img src={`/${image}`} alt={`Level ${id}`} className="h-12 w-12 object-contain" />
30+
</button>
31+
);
32+
})}
33+
</div>
34+
);
35+
}

0 commit comments

Comments
 (0)