/*
* File : Arith.java
* Created : 06-may-2001 18:28
* By : fbusquets
*
* JClic - Authoring and playing system for educational activities
*
* Copyright (C) 2000 - 2005 Francesc Busquets & Departament
* d'Educacio de la Generalitat de Catalunya
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details (see the LICENSE file).
*/
package edu.xtec.jclic.automation.arith;
import edu.xtec.jclic.automation.ActiveBagContentKit;
import edu.xtec.jclic.automation.AutoContentProvider;
import edu.xtec.jclic.boxes.ActiveBagContent;
import edu.xtec.util.JDomUtility;
import edu.xtec.util.ResourceBridge;
import java.text.DecimalFormat;
import java.util.Iterator;
import java.util.Random;
/**
* <CODE>Arith</CODE> is the first implementation of {@link edu.xtec.jclic.automation.AutoContentProvider}.
* It is based on the code of ARITH2.DLL, made originally for Clic 3.0. It provides
* activities with randomly generated menthal arithmetics operations. The operations
* can be additions, substractions, multiplications or divides. The unknown can be the
* result of the operation or any of the two operators (in the form A # B = ?, A # ? = C or ? # B = C), or
* also the operation itself (like A ? B = C).
* Activities must implement {@link edu.xtec.jclic.automation.ActiveBagContentKit.Compatible}
* in order to use <CODE>Arith</CODE>.
* @author Francesc Busquets (fbusquets@xtec.cat)
* @version 13.08.28
*/
public class Arith extends AutoContentProvider {
private static final String DLL_TITLE="ARITH2.DLL";
private static final int ARITHVER=0x02;
protected static final int NMAXLOOPS=60;
protected static final int NOSORT=0, SORTASC=1, SORTDESC=2;
protected static final int SUM=1, REST=2, MULT=4, DIV=8, NOPERACIONS=4;
// Math signs:
// \u00D7 - multiply (cross)
// \u00B7 - multiply (middle dot)
// \u00F7 - division (two points+bar)
protected static final String[] OPSTR={"+", "-", "\u00D7", ":"};
protected static final int ABX=1, AXC=2, XBC=4, AXBC=8, CAXB=16, NTIPUSEX=5;
protected static final int INDIF=0, AGB=1, BGA=2;
private static final int RES=-12345;
private static final int MAX_STR_LEN=100;
private static String S="\u00A0";
protected class Num {
float vf;
int c;
}
protected class Operacio {
Num numA=new Num();
Num numB=new Num();
Num numR=new Num();
int op;
}
protected static final String ID="id", A="A", B="B",
OPERATIONS="operations", PLUS="plus", MINUS="minus", MULTIPLY="multiply", DIVIDE="divide",
UNKNOWN="unknown",
RESULT="result", FIRST="first", LAST="last", OPERAND="operand", INVERSE="inverse",
FROM="from", TO="to", NOT_CARRY="notCarry", DUPLICATES="duplicates",
ORDER="order", ASCENDING="ascending", DESCENDING="descending",
CONDITION="condition", FIRST_BIG="firstBig", LAST_BIG="lastBig";
protected static DecimalFormat[] DF;
protected static final int WILDCARD_DF=5;
Operator opA, opB;
boolean use_add, use_subst, use_mult, use_div;
boolean exp_abx, exp_axc, exp_xbc, exp_axbc, exp_caxb;
int resultLimInf, resultLimSup;
boolean resultCarry, resultNoDup;
int resultOrder;
int opCond;
private Random random;
/** Creates new Arith */
public Arith() {
random=new Random();
opA=new Operator();
opB=new Operator();
exp_abx=true;
use_add=true;
resultLimInf=Operator.LIM0;
resultLimSup=Operator.NOLIM;
resultOrder=NOSORT;
opCond=INDIF;
if(DF==null){
DF=new DecimalFormat[WILDCARD_DF+1];
DF[0]=new DecimalFormat("0");
DF[1]=new DecimalFormat("0.0");
DF[2]=new DecimalFormat("0.00");
DF[3]=new DecimalFormat("0.000");
DF[4]=new DecimalFormat("0.0000");
DF[WILDCARD_DF]=new DecimalFormat("0000000000");
}
}
public DecimalFormat getDF(int index){
return DF[index<=WILDCARD_DF ? index : WILDCARD_DF-1];
}
@Override
public org.jdom.Element getJDomElement() {
org.jdom.Element e=super.getJDomElement();
org.jdom.Element op=opA.getJDomElement();
op.setAttribute(ID, A);
e.addContent(op);
op=opB.getJDomElement();
op.setAttribute(ID, B);
e.addContent(op);
org.jdom.Element eop=new org.jdom.Element(OPERATIONS);
eop.setAttribute(PLUS, JDomUtility.boolString(use_add));
eop.setAttribute(MINUS, JDomUtility.boolString(use_subst));
eop.setAttribute(MULTIPLY, JDomUtility.boolString(use_mult));
eop.setAttribute(DIVIDE, JDomUtility.boolString(use_div));
e.addContent(eop);
org.jdom.Element eu=new org.jdom.Element(UNKNOWN);
eu.setAttribute(RESULT, JDomUtility.boolString(exp_abx));
eu.setAttribute(FIRST, JDomUtility.boolString(exp_xbc));
eu.setAttribute(LAST, JDomUtility.boolString(exp_axc));
eu.setAttribute(OPERAND, JDomUtility.boolString(exp_axbc));
eu.setAttribute(INVERSE, JDomUtility.boolString(exp_caxb));
e.addContent(eu);
org.jdom.Element er=new org.jdom.Element(RESULT);
er.setAttribute(FROM, Operator.LIM_CH[resultLimInf]);
er.setAttribute(TO, Operator.LIM_CH[resultLimSup]);
if(resultCarry)
er.setAttribute(NOT_CARRY, JDomUtility.boolString(resultCarry));
er.setAttribute(DUPLICATES, JDomUtility.boolString(!resultNoDup));
if(resultOrder!=NOSORT)
er.setAttribute(ORDER, resultOrder==SORTASC ? ASCENDING : DESCENDING);
if(opCond!=INDIF)
er.setAttribute(CONDITION, opCond==AGB ? FIRST_BIG : LAST_BIG);
e.addContent(er);
return e;
}
public void setProperties(org.jdom.Element e, Object aux) throws Exception{
org.jdom.Element child;
String s;
Iterator itr = e.getChildren(Operator.ELEMENT_NAME).iterator();
while (itr.hasNext()){
child=(org.jdom.Element)(itr.next());
s=child.getAttributeValue(ID);
if(A.equals(s))
opA.setProperties(child, aux);
else if(B.equals(s))
opB.setProperties(child, aux);
else
throw new IllegalArgumentException("Unknown operator: "+s);
}
if((child=e.getChild(OPERATIONS))!=null){
use_add=JDomUtility.getBoolAttr(child, PLUS, use_add);
use_subst=JDomUtility.getBoolAttr(child, MINUS, use_subst);
use_mult=JDomUtility.getBoolAttr(child, MULTIPLY, use_mult);
use_div=JDomUtility.getBoolAttr(child, DIVIDE, use_div);
}
if((child=e.getChild(UNKNOWN))!=null){
exp_abx=JDomUtility.getBoolAttr(child, RESULT, exp_abx);
exp_xbc=JDomUtility.getBoolAttr(child, FIRST, exp_xbc);
exp_axc=JDomUtility.getBoolAttr(child, LAST, exp_axc);
exp_axbc=JDomUtility.getBoolAttr(child, OPERAND, exp_axbc);
exp_caxb=JDomUtility.getBoolAttr(child, INVERSE, exp_caxb);
}
if((child=e.getChild(RESULT))!=null){
resultLimInf=JDomUtility.getStrIndexAttr(child, FROM, Operator.LIM_CH, resultLimInf);
resultLimSup=JDomUtility.getStrIndexAttr(child, TO, Operator.LIM_CH, resultLimSup);
resultCarry=JDomUtility.getBoolAttr(child, NOT_CARRY, resultCarry);
resultNoDup=!JDomUtility.getBoolAttr(child, DUPLICATES, !resultNoDup);
s=child.getAttributeValue(ORDER);
resultOrder= (s==null ? NOSORT : s.equals(ASCENDING) ? SORTASC : SORTDESC);
s=child.getAttributeValue(CONDITION);
opCond = (s==null ? INDIF : s.equals(FIRST_BIG) ? AGB : BGA);
}
}
@Override
public boolean setClic3Properties(byte[] ops) {
int v;
int i, lb, hb;
boolean fromBlank=false;
int p=0;
int arithVer;
p=opA.setClic3Properties(ops, p);
p=opB.setClic3Properties(ops, p);
v=ops[p++]&0x7F;
if(v==0) v=SUM;
use_add = (v & SUM)!=0;
use_subst = (v & REST)!=0;
use_mult = (v & MULT)!=0;
use_div = (v & DIV)!=0;
v=ops[p++]&0x7F;
if(v==0) v=ABX;
exp_abx = (v & ABX)!=0;
exp_axc = (v & AXC)!=0;
exp_xbc = (v & XBC)!=0;
exp_axbc = (v & AXBC)!=0;
exp_caxb = (v & CAXB)!=0;
resultLimInf=((i=ops[p++]&0x7F)==0 ? Operator.LIM0:i);
resultLimSup=((i=ops[p++]&0x7F)==0 ? Operator.NOLIM:i);
resultCarry=(ops[p++]&0x1)==1;
resultNoDup=(ops[p++]&0x1)==1;
resultOrder=ops[p++]&0x3;
opCond=ops[p++]&0x3;
if(p<ops.length)
arithVer=ops[p++]&0x7F;
else
arithVer=0;
if(arithVer==0){
arithVer=ARITHVER;
if(!opA.fromBlank){
opA.limInf=Operator.adjustLimVer(opA.limInf);
opA.limSup=Operator.adjustLimVer(opA.limSup);
opB.limInf=Operator.adjustLimVer(opB.limInf);
opB.limSup=Operator.adjustLimVer(opB.limSup);
resultLimInf=Operator.adjustLimVer(resultLimInf);
resultLimSup=Operator.adjustLimVer(resultLimSup);
}
}
if(arithVer>ARITHVER){
return false;
}
return true;
}
boolean genNum(Num n, Operator op, long limInf2, long limSup2){
int r, exp, rang;
long ls, li, k, v;
boolean resolt;
n.c=op.numDec;
exp= n.c==0 ? 1 : n.c==1 ? 10 : 100;
ls=Operator.LIMITS[op.limSup];
if(limSup2!=RES && limSup2<ls)
ls=limSup2;
li=Operator.LIMITS[op.limInf];
if(limInf2!=RES && limInf2>li)
li=limInf2;
resolt=false;
if(op.fromList>0){
n.vf=(long)op.lst[random.nextInt(op.fromList)];
resolt=true;
}
if(!resolt){
r=random.nextInt(100);
if(op.wZero && r<=10){
n.vf=0; resolt=true;
}
else if(op.wOne && r>10 && r<=20){
n.vf=1;
resolt=true;
}
else if(op.wMinusOne && r>20 && r<=30){
n.vf=-1;
resolt=true;
}
}
if(!resolt){
if(li>ls){
k=li;
li=ls;
ls=k;
}
rang=(int)(ls-li+1);
if(rang<0)
rang=1;
v=(long)(random.nextInt(rang)+li)*exp;
if (exp>1)
v+=random.nextInt(exp);
n.vf=((float)v)/exp;
//resolt=true;
}
return true;
}
boolean genOp(Operacio o){
int i;
int[] ops=new int[NOPERACIONS];
int nops, op;
long rlinf, rlsup, ri2, rs2;
float q;
rlinf=Operator.LIMITS[resultLimInf];
rlsup=Operator.LIMITS[resultLimSup];
nops=0;
if(use_add)
ops[nops++]=SUM;
if(use_subst)
ops[nops++]=REST;
if(use_mult)
ops[nops++]=MULT;
if(use_div)
ops[nops++]=DIV;
op=ops[random.nextInt(nops)];
switch(op){
case SUM:
for(i=0; i<NMAXLOOPS; i++){
genNum(o.numA, opA, RES, rlsup);
ri2= o.numA.vf<rlinf ? rlinf-(long)o.numA.vf:RES;
rs2=rlsup-(long)o.numA.vf;
switch(opCond){
case AGB:
if(rs2==RES || rs2>o.numA.vf)
rs2=(long)o.numA.vf;
break;
case BGA:
if(ri2==RES || ri2<o.numA.vf)
ri2=(long)o.numA.vf;
break;
}
genNum(o.numB, opB, ri2, rs2);
o.numR.vf=o.numA.vf+o.numB.vf;
if(o.numR.vf>=rlinf && o.numR.vf<=rlsup)
break;
}
o.numR.c = o.numA.c > o.numB.c ? o.numA.c : o.numB.c;
o.op=0;
if(resultCarry && o.numA.vf>0 && o.numB.vf>0){
int va, vb;
q=o.numR.c==2 ? 100 : o.numR.c==1 ? 10 : 1;
char[] bufa=getDF(WILDCARD_DF).format((long)(o.numA.vf*q+0.5)).toCharArray();
char[] bufb=getDF(WILDCARD_DF).format((long)(o.numB.vf*q+0.5)).toCharArray();
for(i=0; i<10; i++)
if(bufa[i]!='0' || bufb[i]!='0')
break;
for(; i<10; i++){
va=bufa[i]-'0';
vb=bufb[i]-'0';
if(va+vb<10)
continue;
while(va+vb>9){
if(va>vb)
va=(va>0 ? random.nextInt(va) : 0);
else
vb=(vb>0 ? random.nextInt(vb) : 0);
}
bufa[i]='0';
for(int x=0; x<va; x++)
bufa[i]++;
bufb[i]='0';
for(int x=0; x<vb; x++)
bufb[i]++;
}
o.numA.vf=(float)(Long.parseLong(new String(bufa)));
o.numB.vf=(float)(Long.parseLong(new String(bufb)));
o.numR.vf=(float)(long)(o.numA.vf + o.numB.vf + 0.5);
o.numA.vf/=q;
o.numB.vf/=q;
o.numR.vf/=q;
}
break;
case REST:
for(i=0; i<NMAXLOOPS; i++){
genNum(o.numA, opA, rlinf, RES);
ri2= o.numA.vf > rlsup ? (long)(o.numA.vf - rlsup) : RES;
rs2= (long)(o.numA.vf - rlinf);
switch(opCond){
case AGB:
if(rs2==RES || rs2>o.numA.vf)
rs2=(long)o.numA.vf;
break;
case BGA:
if(ri2==RES || ri2<o.numA.vf)
ri2=(long)o.numA.vf;
break;
}
genNum(o.numB, opB, ri2, rs2);
o.numR.vf=o.numA.vf-o.numB.vf;
if(o.numR.vf>=rlinf && o.numR.vf<=rlsup)
break;
}
o.numR.c = o.numA.c > o.numB.c ? o.numA.c : o.numB.c;
o.op=1;
if(resultCarry && o.numA.vf>0 && o.numB.vf>0 && o.numA.vf>=o.numB.vf){
int va, vb;
q = (o.numR.c==2 ? 100 : (o.numR.c==1 ? 10 : 1));
char[] bufa=getDF(WILDCARD_DF).format((long)(o.numA.vf*q+0.5)).toCharArray();
char[] bufb=getDF(WILDCARD_DF).format((long)(o.numB.vf*q+0.5)).toCharArray();
for(i=0; i<10; i++)
if(bufb[i]!='0')
break;
for(; i<10; i++){
va=bufa[i]-'0';
vb=bufb[i]-'0';
if(va>=vb)
continue;
vb = (va>0 ? random.nextInt(va) : 0);
bufb[i]='0';
for(int x=0; x<vb; x++)
bufb[i]++;
}
o.numA.vf=(float)(Long.parseLong(new String(bufa)));
o.numB.vf=(float)(Long.parseLong(new String(bufb)));
o.numR.vf=(float)(long)(o.numA.vf - o.numB.vf + 0.5);
o.numA.vf/=q;
o.numB.vf/=q;
o.numR.vf/=q;
}
break;
case MULT:
for(i=0; i<NMAXLOOPS; i++){
genNum(o.numA, opA, RES, RES);
ri2= Operator.LIMITS[opB.limInf];
rs2= Operator.LIMITS[opB.limSup];
switch(opCond){
case AGB:
if(rs2>o.numA.vf)
rs2=(long)o.numA.vf;
break;
case BGA:
if(ri2<o.numA.vf)
ri2=(long)o.numA.vf;
break;
}
genNum(o.numB, opB, ri2, rs2);
o.numR.vf=o.numA.vf * o.numB.vf;
if(o.numR.vf>=rlinf && o.numR.vf<=rlsup)
break;
}
o.numR.c = o.numA.c + o.numB.c;
o.op=2;
break;
case DIV:
for(i=0; i<NMAXLOOPS; i++){
genNum(o.numA, opA, RES, RES);
ri2= Operator.LIMITS[opB.limInf];
rs2= Operator.LIMITS[opB.limSup];
switch(opCond){
case AGB:
if(rs2>o.numA.vf)
rs2=(long)o.numA.vf;
break;
case BGA:
if(ri2<o.numA.vf)
ri2=(long)o.numA.vf;
break;
}
genNum(o.numB, opB, ri2, rs2);
if(o.numB.vf!=0
&& Math.abs(o.numA.vf)>=Math.abs(o.numB.vf)
&& (o.numR.vf=o.numA.vf/o.numB.vf)>=rlinf
&& o.numR.vf<=rlsup)
break;
}
if(o.numB.vf==0)
o.numB.vf=1;
o.numR.vf=o.numA.vf / o.numB.vf;
i=o.numA.c - o.numB.c;
q=(float)(Math.pow(10, i));
o.numA.vf*=q;
o.numR.vf*=q;
o.numR.vf=((long)o.numR.vf);
o.numA.vf=o.numR.vf*o.numB.vf;
o.numA.vf/=q;
o.numR.vf/=q;
o.numR.c = i>0 ? i : 0;
o.op=3;
break;
default:
return false;
}
return true;
}
public boolean generateContent(Object kit, ResourceBridge rb) {
boolean result=false;
if(kit instanceof ActiveBagContentKit){
ActiveBagContentKit k=(ActiveBagContentKit)kit;
result=generateContent(k.nRows, k.nCols, k.content, k.useIds, rb);
}
return result;
}
protected boolean generateContent(int nRows, int nCols, ActiveBagContent[] content, boolean useIds, ResourceBridge rb) {
if(nRows<=0 || nCols<=0 ||
content==null || content.length<1 || content[0]==null ||
rb==null)
return false;
Operacio o;
Operacio[] op;
int i, j, k;
int[] tipus=new int[NTIPUSEX];
int numTipus, tipX;
boolean tipInv;
String va, vb, vc, operator;
String [] stra, strb, strc;
int nColsB=nCols, nRowsB=nRows;
int nCells=nRows*nCols;
if(nCells<2)
return false;
int[] ass = null;
numTipus=0;
if(exp_abx)
tipus[numTipus++]=ABX;
if(exp_axc)
tipus[numTipus++]=AXC;
if(exp_xbc)
tipus[numTipus++]=XBC;
if(exp_axbc)
tipus[numTipus++]=AXBC;
if(numTipus==0)
return false;
tipInv=exp_caxb;
op=new Operacio[nCells];
stra=new String[nCells];
strb=new String[nCells];
strc=new String[nCells];
for(i=0; i<nCells; i++){
o=new Operacio();
for(j=0; j<NMAXLOOPS; j++){
genOp(o);
if(resultNoDup){
for(k=0; k<i; k++){
if(o.numR.vf==op[k].numR.vf) break;
}
if(k==i) break;
}
else break;
}
op[i]=o;
}
if(resultOrder!=0){
for(i=nCells-1; i>0; i--){
for(j=0; j<i; j++){
if((resultOrder==SORTASC && op[j].numR.vf > op[j+1].numR.vf)
|| (resultOrder==SORTDESC && op[j].numR.vf < op[j+1].numR.vf)){
o=op[j];
op[j]=op[j+1];
op[j+1]=o;
}
}
}
}
for(i=0; i<nCells; i++){
tipX=tipus[random.nextInt(numTipus)];
va=getDF(op[0].numA.c).format(op[i].numA.vf);
vb=getDF(op[0].numB.c).format(op[i].numB.vf);
vc=getDF(op[0].numR.c).format(op[i].numR.vf);
operator=OPSTR[op[i].op];
if(tipInv)
strc[i]=vc + S + "=" + S + va + S + operator + S + vb;
else
strc[i]=va + S + operator + S + vb + S + "=" + S + vc;
switch(tipX){
case AXC:
strb[i]=vb;
if(tipInv)
stra[i]=vc + S + "=" + S + va + S + operator + S + "?";
else
stra[i]=va + S + operator + S + "?" + S + "=" + S + vc;
break;
case XBC:
strb[i]=va;
if(tipInv)
stra[i]=vc + S + "=" + S + "?" + S + operator + S + vb;
else
stra[i]="?" + S + operator + S + vb + S + "=" + S + vc;
break;
case AXBC:
strb[i]=operator;
if(tipInv)
stra[i]=vc + S + "=" + S + va + S + "?" + S + vb;
else
stra[i]=va + S + "?" + S + vb + S + "=" + S + vc;
break;
default:
strb[i]=vc;
if(tipInv)
stra[i]="?" + S + "=" + S + va + S + operator + S + vb;
else
stra[i]=va + S + operator + S + vb + S + "=";
break;
}
}
if(useIds){
ass=new int[nCells];
String[] strbx=new String[nCells];
k=0;
for(i=0; i<nCells; i++){
for(j=0; j<k; j++)
if(strb[i].equals(strbx[j]))
break;
if(j==k){
strbx[k]=strb[i];
ass[i]=k;
k++;
}
else
ass[i]=j;
}
strb=new String[k];
for(i=0; i<k; i++)
strb[i]=strbx[i];
if(nRowsB*nColsB!=k){
//boolean distH=nColsB>=nRowsB;
boolean distH=false;
switch(k){
case 6:
nRowsB=distH ? 2 : 3; nColsB=distH ? 3 : 2;
break;
case 8:
nRowsB=distH ? 2 : 4; nColsB=distH ? 4 : 2;
break;
case 9:
nRowsB=3; nColsB=3;
break;
case 10:
nRowsB=distH ? 2 : 5; nColsB=distH ? 5 : 2;
break;
case 12:
nRowsB=distH ? 3 : 4; nColsB=distH ? 4 : 3;
break;
case 14:
nRowsB=distH ? 2 : 7; nColsB= distH ? 7 : 2;
break;
case 15:
nRowsB=distH ? 3 : 5; nColsB=distH ? 3 : 5;
break;
case 16:
nRowsB=4; nColsB=4;
break;
case 18:
nRowsB=distH ? 6 : 3; nColsB=distH ? 3 : 6;
break;
case 20:
nRowsB=distH ? 4 : 5; nColsB=distH ? 5 : 4;
break;
default:
nRowsB=distH ? 1 : k; nColsB=distH ? k : 1;
break;
}
}
}
content[0].setTextContent(stra, nCols, nRows);
if(ass!=null)
content[0].setIds(ass);
if(content.length>1 && content[1]!=null){
content[1].setTextContent(strb, nColsB, nRowsB);
content[1].getShaper().reset(nColsB, nRowsB);
}
if(content.length>2 && content[2]!=null)
content[2].setTextContent(strc, nCols, nRows);
return true;
}
public static boolean checkClient(Class cl){
return ActiveBagContentKit.Compatible.class.isAssignableFrom(cl);
}
}