Skip to content

Commit 3209cbb

Browse files
authored
Merge pull request #1670 from ccnmtl/FOL-11-check-statement
The grid game now allows for statement checks
2 parents 318d3f0 + 6843834 commit 3209cbb

File tree

4 files changed

+186
-10
lines changed

4 files changed

+186
-10
lines changed

media/js/src/firstOrderLogic/firstOrderLogic.tsx

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { shuffleArray, getRandomElement, GridItem, GridStatement,
44
import { getTemplatesByDifficulty } from './statementGenerator';
55
import { Grid } from './grid';
66
import { Options } from './options';
7+
import { StatementInput } from './statementInput';
78

89
export const STATIC_URL = LogicLearner.staticUrl;
910

@@ -16,9 +17,10 @@ export const FirstOrderLogic: React.FC = () => {
1617
const [options, setOptions] = useState<GridStatement[]>([]);
1718
const [size, setSize] = useState<number>(5);
1819
const [templateBank, setTemplateBank] = useState<Object[]>(getTemplatesByDifficulty(difficulty));
19-
const [value, setValue] = useState();
20+
const [text, setText] = useState<string>('');
2021
const [isCorrect, setIsCorrect] = useState<boolean>(false);
2122
const [selected, setSelected] = useState<number|null>()
23+
const [mode, setMode] = useState<number>(0);
2224

2325
const [correctTemplate, setCorrectTemplate] =
2426
useState<GridTemplate>(getRandomElement(templateBank));
@@ -30,8 +32,8 @@ export const FirstOrderLogic: React.FC = () => {
3032
];
3133

3234
const inputOptions = [ // [value, innerText]
33-
['mc', 'Multiple Choice'],
34-
['text', 'Text Input']
35+
[0, 'Multiple Choice'],
36+
[1, 'Text Input']
3537
];
3638

3739
function generateIncorrectStatements(templateBank, correctTemplate) {
@@ -68,13 +70,18 @@ export const FirstOrderLogic: React.FC = () => {
6870
setDifficulty(e.target.value);
6971
};
7072

73+
const handleMode = (e) => {
74+
setMode(Number(e.target.value));
75+
}
76+
7177
const handleNewGrid = () => {
7278
let newArr = getRandomElement(templateBank)
7379
while (newArr === correctTemplate) {
7480
newArr = getRandomElement(templateBank)
7581
}
7682
setCorrectTemplate(newArr);
7783
setSelected(null);
84+
setText('');
7885
}
7986

8087
const mkSelect = (options, action) => <select className='form-select mt-2'
@@ -85,7 +92,7 @@ export const FirstOrderLogic: React.FC = () => {
8592

8693
const settings = [
8794
mkSelect(diffOptions, handleDifficulty),
88-
mkSelect(inputOptions, () => alert('Needs implementation.')),
95+
mkSelect(inputOptions, handleMode),
8996
<button className='btn btn-primary mt-2' onClick={handleNewGrid}>
9097
Next Grid
9198
</button>
@@ -105,7 +112,7 @@ export const FirstOrderLogic: React.FC = () => {
105112
const generated = correctTemplate.generateGrid(true, statementData.details)
106113

107114
setGrid(generated.grid);
108-
setValue(generated.satisfies);
115+
setText('');
109116

110117
setCorrectStatement({
111118
naturalLanguageStatement: statementData.naturalLanguageStatement,
@@ -135,6 +142,10 @@ export const FirstOrderLogic: React.FC = () => {
135142
setSelected(null);
136143
}, []);
137144

145+
useEffect(() => {
146+
setIsCorrect(false);
147+
}, [mode])
148+
138149
return <section id='grid-game'
139150
className='container d-flex justify-content-center'
140151
>
@@ -148,9 +159,14 @@ export const FirstOrderLogic: React.FC = () => {
148159
</li>)}
149160
</ul>
150161
</div>
151-
<Options options={options} correctIndex={correctIndex}
162+
{mode === 0 && <Options options={options} correctIndex={correctIndex}
152163
isCorrect={isCorrect} setIsCorrect={setIsCorrect}
153-
selected={selected} setSelected={setSelected} />
164+
selected={selected} setSelected={setSelected} />}
165+
{mode === 1 && <StatementInput isCorrect={isCorrect}
166+
correctStatement={correctStatement}
167+
setIsCorrect={setIsCorrect}
168+
text={text}
169+
setText={setText} />}
154170
</div>
155171
</section>
156172
}

media/js/src/firstOrderLogic/gridTemplates/hardTemplates/hardTemplate_05.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export const hardTemplate_05 = {
5656
const formalFOLStatement = `
5757
∀x (
5858
(Shape(x, ${shape1}) ∧ Color(x, ${colorName}))
59-
→ ∃y (Shape(y, ${shape2}) ∧ Value(y) ≥ ${threshold}TopLeftDiagonalOf(y, x))
59+
→ ∃y (Shape(y, ${shape2}) ∧ Value(y) ≥ ${threshold}TopLeftOf(y, x))
6060
)
6161
`.trim();
6262

media/js/src/firstOrderLogic/gridTemplates/hardTemplates/hardTemplate_06.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@ export const hardTemplate_06 = {
4949
);
5050

5151
// FOL example:
52-
// "∀x( (Shape(x)=s1 ∧ Color(x)=c1) → ∃y( Shape(y)=s2 ∧ Value(y)<threshold ∧ TopRightDiagonalOf(y,x) ) )"
52+
// "∀x( (Shape(x)=s1 ∧ Color(x)=c1) → ∃y( Shape(y)=s2 ∧ Value(y)<threshold ∧ TopRightOf(y,x) ) )"
5353
const formalFOLStatement = `
5454
∀x (
5555
(Shape(x, ${shape1}) ∧ Color(x, ${colorName}))
56-
→ ∃y (Shape(y, ${shape2}) ∧ Value(y) < ${threshold}TopRightDiagonalOf(y, x))
56+
→ ∃y (Shape(y, ${shape2}) ∧ Value(y) < ${threshold}TopRightOf(y, x))
5757
)
5858
`.trim();
5959

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { GridStatement } from './utils';
3+
4+
interface StatementProps {
5+
correctStatement: GridStatement
6+
isCorrect: boolean
7+
setIsCorrect: React.Dispatch<React.SetStateAction<boolean>>
8+
text: string
9+
setText: React.Dispatch<React.SetStateAction<string>>
10+
}
11+
12+
export const StatementInput: React.FC<StatementProps> = ({
13+
correctStatement, isCorrect, setIsCorrect, text, setText
14+
}:StatementProps) => {
15+
const [feedback, setFeedback] = useState<string>(
16+
'ERROR: This feedback should not be visible.')
17+
const [submitted, setSubmitted] = useState<boolean>(false);
18+
19+
const buttonList = ['∀', '→', '∃', '∧', '≥'];
20+
21+
/**
22+
* Build an object of the key-value pairs extracted from a given statement.
23+
*
24+
* e.g: "Key1(x, value1) ∧ Key2(x, value2)" => {key: value, ..., key2: value2}
25+
* @param text
26+
*/
27+
const pullData = (text:string):Object => {
28+
const rules = {};
29+
const found = text.match(/\w+\(\w+.[\w\s]+\)+?/g);
30+
if (found) {
31+
found.forEach((keyValue) => {
32+
const key = keyValue.match(/\w+(?=\(\w+[^\)])/);
33+
const value = keyValue.match(/\w[\w\s]*(?=\))/);
34+
if (key != null && value != null) {
35+
rules[key.toString().toLowerCase()] =
36+
value.toString().toLowerCase();
37+
}
38+
});
39+
}
40+
return rules;
41+
};
42+
43+
/**
44+
* Splits the statement into a left and right object around the if
45+
* statement.
46+
* @param text
47+
*/
48+
const parseStatement = (text:string):Object[] => {
49+
const sides = text.split('→');
50+
if (sides.length == 2) {
51+
return [pullData(sides[0]), pullData(sides[1])]
52+
}
53+
};
54+
55+
const directionalRelationships = ['Top(y,x)', 'TopLeftOf(y,x)',
56+
'TopRightOf(y,x)', 'LeftOf(y,x)', 'RightOf(y,x)', 'Below(y,x)',
57+
'BottomLeftOf(y,x)', 'BottomRightOf(y,x)'];
58+
59+
const objectRelationships = ['Shape(x/y, Circle/Square/Triangle)',
60+
'Color(x/y, Blue/Green/Red))', 'Value(x/y, 0 to 9))', 'Prime(Value(x/y))',
61+
'Location(x/y, top/bottom/left/right [number of subsets] rows/columns',
62+
'MultipleOf(Value(x/y))'];
63+
64+
const mkList = (items:string[], uniqueClass='') =>
65+
<ul className={`list-group-flush ps-2 ${uniqueClass}`}>
66+
{items.map((item, i) =>
67+
<li key={i} className='list-group-item'>{item}</li>
68+
)}
69+
</ul>
70+
71+
const mkBtnList = (items:string[]) =>
72+
<ul className="list-inline row my-2">
73+
{items.map((item:string, i:number) =>
74+
<li key={i} className='col-auto'>
75+
<button className='btn btn-outline-secondary'
76+
aria-label={`Add a ${item} symbol to the statement.`}
77+
onClick={mkAddChar(item)}
78+
>
79+
{item}</button>
80+
</li>
81+
)}
82+
</ul>
83+
84+
const mkAddChar = (char:string) => () => {
85+
const el =
86+
document.getElementById('statement-text') as HTMLInputElement;
87+
el.value = el.value + char
88+
el.focus();
89+
};
90+
91+
const evaluate = (check:Object[], evalObj:Object[]) => {
92+
if (check.length == evalObj.length) {
93+
for (let i in evalObj) {
94+
for (let [key, value] of Object.entries(evalObj[i])) {
95+
if (!check[i][key] || check[i][key] !== value) {
96+
return false;
97+
}
98+
}
99+
}
100+
return true;
101+
}
102+
return false;
103+
};
104+
105+
const handleCheck = (e) => {
106+
setSubmitted(true);
107+
const el =
108+
document.getElementById('statement-text') as HTMLInputElement;
109+
if (el.value.length > 0) {
110+
const check = parseStatement(el.value);
111+
if (el.value.match(/^\∀x\s*\(.*\)\s*$/)) {
112+
setIsCorrect(evaluate(check,
113+
parseStatement(correctStatement.formalFOLStatement))
114+
);
115+
} else {
116+
setIsCorrect(false);
117+
}
118+
}
119+
};
120+
121+
const handleText = (e) => {
122+
setText(e.value);
123+
};
124+
125+
useEffect(() => {
126+
if (isCorrect) {
127+
setFeedback('Good! XD');
128+
} else {
129+
setFeedback('Ooops <:O');
130+
}
131+
}, [isCorrect])
132+
133+
useEffect(() => {
134+
setSubmitted(false);
135+
console.log(correctStatement.formalFOLStatement);
136+
}, [])
137+
138+
return <section className='col-4'>
139+
<p>Enter the statment that defines the relationship of</p>
140+
<p>{correctStatement.naturalLanguageStatement}</p>
141+
{mkBtnList(buttonList)}
142+
<textarea id='statement-text' className='form-control mb-2' onChange={handleText}
143+
placeholder='Enter the value here' value={text}></textarea>
144+
<button type='submit' className='btn btn-primary my-2'
145+
onClick={handleCheck}>Check Statement</button>
146+
{submitted && (isCorrect ? <p className='text-success'>{feedback}</p> :
147+
<p className='text-danger'>{feedback}</p>)}
148+
<p className='col-12 fs-4 my-2'>Relationships</p>
149+
<div className="row">
150+
<div className="col-6">
151+
<strong>Object</strong>
152+
{mkList(objectRelationships)}
153+
</div>
154+
<div className="col-6">
155+
<strong>Directional</strong>
156+
{mkList(directionalRelationships)}
157+
</div>
158+
</div>
159+
</section>
160+
}

0 commit comments

Comments
 (0)