Cognition Academy

TO DO  15-August-2014

✓(1) Read and write student database from/to file
	 ✓(2) Incorporate actual problem sets
	 ✓(3) Add new record to database for new skill
	(4) Add “manupulative” question type.
	
Look and feel improvements
	✓Change progress icons to 3D images
	Apply “Modal”, “Editable”, “Moveable”, etc. to restrict users from unwanted actions
	Regularize fonts, colors, etc.
	
Mathematica 10 needs:
	Ability to programatically set focus on an entry field.
	Ability to easily stack dialogs
	Richer button styling options (use Overlay[] ?)

Part 1 - The Mathematica code to run the question dialogs

path=NotebookDirectory[]<>"Lessons and Quizzes\\"

SeedRandom[]

PaddedIntegerString[ n, w ]
   Return the integer as a n string of length w, padded on the left with zeros.

PaddedIntegerString[n_Integer,w_]:=Module[{ns,zs,k},
ns=ToString[n];k=StringLength[ns];
zs=StringJoin[Table["0",{w-k}]];
Return[zs<>ns];
];

CenterString[ string, n ]
    pad the string with blanks on both sides to make its total length n

CenterString[str_,n_]:=Module[{l,k,blnks},
l=StringLength[str];
blnks=StringInsert[" "," ",Table[1,{n}]];
If[l≥n,Return[str]];
k=Quotient[n-l,2];
Return[StringTake[blnks,k]<>str<>StringTake[blnks,n-l-k]];
];

GetStudentInfo[ studentid ]
   Retrieve quiz scores and totals for the given student

GetStudentInfo[studentid_]:=Module[{studentinfo},
(* student info holds totals and individual quiz outcomes *)
studentinfo={};
Return[studentinfo];
]

DisplayStudentScores[ studentid, score ]
   Display the scoring information in the student database

DisplayStudentScores[studentid_,score_,outof_]:=Module[{},
ChoiceDialog[Style["Your total score for this quiz was "
<>ToString[score]<>" out of "<>ToString[outof],18]];
Return[True];
]

TrimAnswers[ question,n ]
	Given a question with multiple correct and incorrect answers
	return a question with 1 of the correct answers and n-1 of the incorrect answers

TrimAnswerList[question_,n_]:=Module[{answrs,trsp,frsp},
(* trim only multiple choice questions, not T/F, Open nor Cross Matching *)
If[question[[2,2]]=="t"||question[[2,2]]=="o"||question[[2,2]]=="x",
Return[question];
];
trsp=Select[question[[3]],#[[2]]==True&];
frsp=Select[question[[3]],#[[2]]==False&];
answrs=RandomSample[trsp,1];
answrs=Join[answrs,RandomSample[frsp,n-1]];
answrs=RandomSample[answrs,n]; (* random permutation *)
Return[{question[[1]],question[[2]],answrs}]
]

SelectQuestions[ quizid, studentid ]
   Choose a randomized set of questions for the given student
   return the total possible score for these questions also

SelectQuestions[quizid_,studentid_]:=Module[{fname,qpool,qlist,numquest,tscore},
fname=path<>"quizfile_"<>quizid<>".m";
qpool=Get[fname];
numquest=6;
numquest=Min[numquest,Length[qpool]];
qpool=RandomSample[qpool,numquest];
qlist=TrimAnswerList[#,4]&/@qpool;
tscore=Total[qlist[[All,2,1]]];
Return[{tscore,qlist}];
];

RunMatchingQuestion[ question ]
   Run a question with pairs of answers to match

RunMatchingQuestion[{qid_,i_,m_},question_]:=Module[{ans,q,ml,mr,m1,m2,m3,m4,k,rm,cm,score,clr,fbstr},
m1="";m2="";m3="";m4="";k=4;
(* choose k random pairs *)
q=RandomSample[question[[3]],k];
(* scramble the lists of the left and right members *)
ml=RandomSample[q[[All,1]],k];
mr=RandomSample[q[[All,2]],k];
(* build a map of indices a->b reconnecting the pairs *)
rm=First@FirstPosition[mr,#[[2]]]->First@FirstPosition[ml,#[[1]]]&/@q;
cm=Take[CharacterRange["A","Z"],k][[Range[k]/.rm]];
ans=0;
CreateDialog[{Column[{TextCell[Style[TextCell[question[[1]]],18]],"",
Style[Grid[{
{" (A) ",TextCell[ml[[1]]],"         ",InputField[Dynamic[m1],String,FieldSize->{1,1}],mr[[1]]},
{" (B) ",TextCell[ml[[2]]],"         ",InputField[Dynamic[m2],String,FieldSize->{1,1}],mr[[2]]},
{" (C) ",TextCell[ml[[3]]],"         ",InputField[Dynamic[m3],String,FieldSize->{1,1}],mr[[3]]},
{" (D) ",TextCell[ml[[4]]],"         ",InputField[Dynamic[m4],String,FieldSize->{1,1}],mr[[4]]}
},Alignment->{Left,Center}],18],"",
Row[{Button[Style["Submit",18],DialogReturn[ans={m1,m2,m3,m4}]],""}]
}]}];
While[ans==0,Pause[0.25]];
If[And@@(StringMatchQ[#[[1]],#[[2]],IgnoreCase->True]&/@Transpose[{cm,ans}]),
score=question[[2,1]];fbstr="Correct";clr=Darker[Green];
,
score=0;fbstr="Incorrect matching";clr=Darker[Red];
];
ChoiceDialog[Column[{Style[question[[1]],18],Style[fbstr,clr,18]},Spacings->{1,1}]];
Return[score];
];

RunOpenQuestion[ question ]
   Run a question with an open (string response)

RunOpenQuestion[{qid_,i_,m_},question_]:=Module[{score,ans,nm,d,clr,len,fbstr},
ans=0;nm="";score=0;
len=2+Max[StringLength/@question[[3,All,1]]];
CreateDialog[{TextCell[Style[question[[1]],18]],
InputField[Dynamic[nm],String,BaseStyle->{FontSize->18},FieldSize->{len,1}],
Button[Style["Submit",18],DialogReturn[ans=nm]]}
];
While[ans==0,Pause[0.2]];
(* the list of responses must all be correct, except for the last one *)
d=Min[EditDistance[ToUpperCase[StringTrim[ans]],ToUpperCase[StringTrim[#]]]&/@question[[3,All,1]]];
If[d<1,
score=question[[2,1]];fbstr=question[[3,1,3]]; clr=Darker[Green];
,
fbstr=Last[question[[3,All,3]]];clr=Darker[Red];
];
ChoiceDialog[Column[{Style[question[[1]],18],Style[fbstr,clr,18]},Spacings->{1,1}]];
Return[score];
]

RunMultipleChoiceQuestion[ question ]
	Present the question, along with the choices as buttons,
	Wait for the answer and return the score obtained
	Both the question and the answers may be any Mathematica expression

RunMultipleChoiceQuestion[{qid_,j_,m_},question_]:=Module[{score,ans,clr,n,c,buttons,fbstr},
score=0;ans=0;
n=Length[question[[3]]];
c=First[Flatten[Position[question[[3]],True]]];
(* We need an implicit function here to force value of ans=1 into DialogReturn *)
buttons=Table[Button[Style[question[[3,i,1]],Darker[Red],18],DialogReturn[ans=#],ImageSize->250,ImageMargins->{{20,10},{10,1}}]&[i],{i,1,n}];
If[question[[2,2]]=="g",
CreateDialog[Column[{Style[Row[{"Quiz ",qid,", Question ",j," of ",m}],Darker[Blue],22],
Style[question[[1]],Darker[Blue],22],Grid[Partition[buttons,2]]}]]
,
CreateDialog[Column[Join[{Style[Row[{"Quiz ",qid,", Question ",j," of ",m}],Darker[Blue],22]},{Style[question[[1]],Darker[Blue],22]},buttons]]]
];
While[ans==0,
Pause[0.2];
];
If[ans==c,
clr=Darker[Green];score=question[[2,1]];
,
clr=Darker[Red];score=0;
Beep[];
];
fbstr=question[[3,ans,3]];
ChoiceDialog[Column[{Style[question[[1]],18],Style[fbstr,clr,18]},Spacings->{1,1}]];
Return[score];
];

RunGeneralQuestion[ question ]
	Present the question, wait for the answer and return the score obtained
	Both the question and the answers may be any Mathematica expression

RunGeneralQuestion[{qid_,i_,m_},question_]:=Module[{score,ans,buttons,img,n,c,clr,mtchfb},
score=0;
If[question[[2,2]]=="m"||question[[2,2]]=="t"||question[[2,2]]=="g",
score=RunMultipleChoiceQuestion[{qid,i,m},question];
];
If[question[[2,2]]=="o",
score=RunOpenQuestion[{qid,i,m},question];
];
If[question[[2,2]]=="x",
score=RunMatchingQuestion[{qid,i,m},question];
];
Return[score];
]

DoLinearAlgebraQuiz[ quizid,studentid ]
   Run the quiz given by quizid for student identified by student id

DoLinearAlgebraQuiz[quizid_,studentid_]:=Module[{i,n,qlist,score,tscore,outof},
{outof,qlist}=SelectQuestions[quizid,studentid];
n=Length[qlist];
tscore=0;
For[i=1,i≤n,++i,
score=RunGeneralQuestion[{quizid,i,n},qlist[[i]]];
tscore+=score;
];
DisplayStudentScores[studentid,tscore,outof];
Return[{tscore,outof,n}];
];

Part I1 - The Mathematica code to run the main screen and skill progress screens

UpdateStudentScores[ stid, skill, { score, outof, m }]
   Update the percent complete and the percent correct in the student database
   and return and updated student record.  Add a new record to the student database if needed.

UpdateStudentScores[stid_,skill_,{score_,outof_,m_}]:=Module[{pf,pc,sn,ax},
sn=First@FirstPosition[StringMatchQ[#,stid]&/@studentDB[[All,1]],True];
{pf,pc}=Lookup[studentDB[[sn,4]],skill,{0,0}];
pf+=2m;pf=Min[100,pf];
If[pc==0,pc=100score/outof,pc=(pc+100 score/outof)/2];
pc=Min[100,pc];
ax=studentDB[[sn,4]];
ax[skill]={pf,pc};
studentDB[[sn,4]]=ax;
Return[studentDB[[sn]]];
]

Create a student progress status icon given percent finished (pf) and percent correct (pc)

MakeDisk[stpct_,endpct_]:=Disk[{0,0},1,π{(3/2)-2endpct,(3/2)-2 stpct}];

StatusIconFlat[pf_,pc_]:=Graphics[{Circle[],Darker[Green],MakeDisk[0,pf pc],Red,MakeDisk[pf pc,pf]},ImageSize->28];

PlotSphereSegment[th1_,th2_,clr_]:=If[th1≥th2,Graphics3D[{}],SphericalPlot3D[1,{θ,0,π},{ϕ,th1,th2},PlotStyle->Directive[{Specularity[White,20],clr,Opacity[0.65]}],Mesh->None,PerformanceGoal->"Speed"]];

StatusIcon[pf_,pc_]:=Module[{th1,th2,sph1,sph2,sph3,gr},
th2=2π pf;th1=2π pf pc;
{sph1,sph2,sph3}=Apply[PlotSphereSegment,#]&/@{{(3/2)π-th1,(3/2) π,Green},{(3/2) π-th2,(3/2) π-th1,Red},{-(1/2)π,(3/2)π-th2,GrayLevel[0.3]}};
gr=Show[sph1,sph2,sph3,Boxed->False,Axes->None,PlotRange->All,ImageSize->45,ViewPoint->{0,0,100},Background->GrayLevel[0.85]];
Return[gr];
];

Create a status Icon for the given student’s progress in the selected skill
	it will be an empty circle if there is no record of progress for the skill

MakeStatusIcon[student_,skill_]:=StatusIcon@@(0.01(student[[4,skill]]/._Missing->{0,0}));

Make a progress gauge for the given percent finished and the given percent correct

MakeProgressGauge[pf_,pc_]:=AngularGauge[{pf,pf,0.01pc pf,0.01 pc pf},{0,100},GaugeMarkers->{"ScaleRange","Center","ScaleRange","Center"},GaugeLabels->{Placed[Column[{Style[ToString[Round[pf]]<>"% Complete",Darker[Blue],14],Style[" ",1]},Spacings->0],Above],Placed[Column[{Style[" ",1],Row[{Style[ToString[Round[pc]]<>"% Correct of the ",Darker[Green],14],Style[ToString[Round[pf]]"% Complete",Darker[Blue],14]}],Spacings->0}],Below]},GaugeStyle->{Red,Darker[Blue],Darker[Green],Darker[Green]},ImageSize->270,ImageMargins->10,ImagePadding->30,Background->GrayLevel[0.9]];

Present information and choices for the selected subject

RunSkillDialogPrev[student_,subject_]:=Module[{ans},
ans=0;
CreateDialog[{Column[{Style[Row[{"Lesson: ",subject[[1]],Spacer[10],subject[[2]]}],20],
Panel[Style[subject[[3]],18],ImageSize->{500,All}],
Row[{MakeProgressGauge@@student[[4,subject[[1]]]]},Frame->True],
Row[{
Button[Style["Practice",18],DialogReturn[ans=2],ImageSize->100],Spacer[10],
Button[Style["Do Test",18],DialogReturn[ans=3],ImageSize->100],Spacer[10],
Button[Style["View Video",18],DialogReturn[ans=4],ImageSize->100],Spacer[10],
Button[Style["Return",18],DialogReturn[ans=1],ImageSize->100]
}]
},Alignment->Center,Spacings->1]},Modal->True];
While[ans==0,Pause[0.2]];
Return[ans];
];

RunSkillDialog[student_,subject_]:=Module[{ans},
ans=0;
CreateDialog[{Column[{Style[Row[{"Lesson: ",subject[[1]],Spacer[10],subject[[2]]}],20],
Panel[Style[subject[[3]],18],ImageSize->{500,All}],
Row[{MakeProgressGauge@@Lookup[student[[4]],subject[[1]],{0,0}]},Frame->True],
Row[{
Button[Style["Practice",18],DialogReturn[ans=2],ImageSize->100],Spacer[10],
Button[Style["Do Test",18],DialogReturn[ans=3],ImageSize->100],Spacer[10],
Button[Style["View Video",18],DialogReturn[ans=4],ImageSize->100],Spacer[10],
Button[Style["Return",18],DialogReturn[ans=1],ImageSize->100]
}]
},Alignment->Center,Spacings->1]},Modal->True];
While[ans==0,Pause[0.2]];
Return[ans];
];

Display a button for each skill along with a status indicator of the students progress on that skill

SkillsProgressDialog[stdnt_]:=Module[{n,student,ans1,ans2,ans3,slp,skill,score,outof,m,pc,pf,videofile},
ans2=0;student=stdnt;
n=Length[lessonlist];
slp=Flatten[Partition[lessonlist,Quotient[n,2]]\[Transpose],1];
While[¬SameQ[ans2,1],
ans2=0;
CreateDialog[
Column[{
Style["Cognition Academy",36],
Row[{Style["Linear Algebra Skills Progress - student: "<>student[[1]],Darker[Blue],24]},ImageSize->Full,Alignment->Center],
Grid@Partition[
Row[{MakeStatusIcon[student,#[[1]]],Spacer[5],Button[Style[Row[{#[[1]],Spacer[5],#[[2]]}],Darker[Blue],18],DialogReturn[ans2=#],ImageSize->480]}]&/@slp,2],
Row[{Spacer[40],Button[Style["Log Out",Darker[Blue],16],DialogReturn[ans2=1],ImageSize->200],Spacer[40]}]
},Alignment->Center],WindowSize->Scaled[1.0]];
While[ans2==0,Pause[0.2]];
If[¬SameQ[ans2,1],
skill=ans2[[1]];
ans1=0;
While[ans1≠1,
ans1=RunSkillDialog[student,ans2];
If[ans1==4,
videofile=NotebookDirectory[]<>"Lesson"<>ToString[skill]<>"Video.mp4";
SystemOpen[videofile];
];
If[ans1==2∨ans1==3,
If[ChoiceDialog[Row[{Style["Begin Practice: "<>ans2[[2]],Blue,24]},ImageSize->All]],
{score,outof,m}=DoLinearAlgebraQuiz[skill,student[[1]]];
student=UpdateStudentScores[student[[1]],skill,{score,outof,m}];
];
];
];
];
];
Return[ans2];
];

Display and run the Welcome and login screen
	verify the user’s credentials and bring them to the skills progress screen

WelcomeScreen[]:=Module[{ans1,usrnm,pswd,indx,student},
{lessonlist,studentDB,UJAlogo}=Import[path<>"StudentDB.m"];
lessonlist=Take[lessonlist,22];
While[True,
ans1=0;usrnm="";pswd="";
CreateDialog[{Column[{
Style["  Welcome to Cognition Academy   ",Blue,36],
Row[{UJAlogo},ImageSize->Full],
Row[{TextCell[Style["User Name: ",18]],Style[InputField[Dynamic[usrnm],String,FieldSize->24],14]}],
Row[{TextCell[Style["Password:  ",18]],Style[InputField[Dynamic[pswd],String,FieldMasked->True,FieldSize->24],14]}],
Row[{Button[Style["Login",Darker[Blue],18],DialogReturn[ans1=2]],
Spacer[12],
Button[Style["Exit",Darker[Blue],18],DialogReturn[ans1=1]]}]
},Spacings->2,Alignment->Center]
}];
While[ans1==0,Pause[0.2]];
If[ans1==1,Break[]];
If[ans1==2,
indx=Position[StringMatchQ[#,usrnm,IgnoreCase->True]&/@studentDB[[All,2]],True];
If[indx==={},
ChoiceDialog[Style[" Unknown username ",18]];
,
indx=First[Flatten[indx]];
student=studentDB[[indx]];
If[!StringMatchQ[student[[3]],pswd],
ChoiceDialog[Style[" Password incorrect ",18]]
,
SkillsProgressDialog[student];
(* Update the student database *)
Export[path<>"StudentDB.m",{lessonlist,studentDB,UJAlogo}];
];
];
];
];
];

Run Congnitive Academy

WelcomeScreen[]