Zum Inhalt springen

René’s Avigo Page: The Numbers Game

The Numbers Game – Les Chiffres

This is my second try on the Avigo system. It is actually a port from a
Java applet and then again from very old C routines I once wrote. The
problem is based on a TV show in french channel one.

Numbers-Capture.jpg

In the above picture, the problem is to form the 179 with 3, 5, 6, 6,
7, and 100 not using any number twice. The allowed operations are +, -,
* and /. You can either let the computer solve the problem, and the
solution in the captured image has been found by the computer. Or you
can try to solve yourself, using the right part of the panel. This is
done by selecting two numbers on the list of available numbers and
pressing an arithmetic button. Other screen elements are four buttons
and one label "New".

The code consists of two header files and two C files. You can download the application with source in zipped form from here: numbers.zip

Screen Layout

The screen layout consists of several elements. Here is a list from the code

PNUMBERFIELD * INumbers[6];
PNUMBERFIELD * ITarget;
PBUTTON * NewProblem;
PBUTTON * NewTarget;
PBUTTON * Solve;

PBUTTON * Retry;
PBUTTON * Plus;
PBUTTON * Minus;
PBUTTON * Div;
PBUTTON * Mult;
PLISTER * List;
PLABEL * Output[5];
PLABEL * New;

The seven top elements are of type NUMBERFIELD. Clicking on one of them
displays a number picker, which is a small calculator. The screen is
repainted afterwards. It might be possible to save the image below the
picker, but I did not find the code for this. I did not want to invest
too much time for this application. The code to get and display a
number field is this:

/* Generate six number labels at the top */
for (i=0; i<6; i++)
{INumbers[i]=(PNUMBERFIELD
*)CreateNumberField(
id++,
5+25*i, row,
(unsigned
char *)(Numbers+i),
DTINTEGER,
MK_FAR_PTR("Input Number"));
INumbers[i]->right=25+25*i;

LoadBank(&(dsk->insert));
dsk->insert((VOID_PTR)dsk,INumbers[i]);
}

/* Generate a target label centered below */
row+=20;
ITarget=(PNUMBERFIELD *)CreateNumberField(

id++, 65, row,
(unsigned char *)(&Target),
DTINTEGER, MK_FAR_PTR("Input Target"));
ITarget->right=95;

LoadBank(&(dsk->insert));
dsk->insert((VOID_PTR)dsk,ITarget);

As you see, there is a row variable, which is allows later insertion or rearrangement of screen elements. I am unsure, if the LoadBank?

operations are needed every time. However, they seem not to harm. By
the way, the objects are inserted into a DESKBOX object dsk, which is
the same as in the Lotto example.

Then we have six labels at the left. These are generated with the
CreateLabel function, just as the Number Fields. I won’t explain the
buttons either, because they are straightforward. The only change is a
command number, which the CreateButton function takes. This number will
be used in the event handler.

A more complicate topic is the list of available numbers at the right
panel. The Avigo SDk does not support a comfortable list element. It
only provides a LISTER object, which reacts on mouse buttons and calls
a redraw routine for each item. You have to override both event
handlers and take the proper action. My solution is this:

/* event handlers */
extern BANKED void list_writeItem (VOID_PTR view,
unsigned char x, unsigned char y, unsigned short item);
extern BANKED void list_pen (VOID_PTR view,
unsigned char x, unsigned char y);
int Available[6],Marked[6];
int NAvailable;
void setAvailable ()
/* copy numbers from the Numbers field */
{ int i;

for (i=0; i<6; i++)
{ Available[i]=Numbers[i];
Marked[i]=0;
}

NAvailable=6;
}
int nmarked ()
/* get number of marked objects */
{ int i,n=0;
for (i=0; i<NAvailable; i++)
if (Marked[i]) n++;
return n;
}
void list_writeItem (VOID_PTR view,
unsigned char x, unsigned char y, unsigned short item)

/* event handler for writing */
{ char s[16];

PLISTER *p; p=(PLISTER *)view;
if (item>=NAvailable) return;
NumericToStr((double)Available[item],s,DTINTEGER);
SetFontType(PRPFONT11N);

WriteString(x,y,s,Marked[item]);
}
void unmarkall ()

/* unmark all numbers */
{ int i;
for (i=0; i<NAvailable; i++) Marked[i]=0;
}
void list_pen (VOID_PTR view, unsigned char x, unsigned char y)

/* event handler for pen touches */
{ PLISTER *p; p=(PLISTER *)view;
int i;
x=0;
i=(y-p->top)/((p->bottom-p->top)/6);

if (i>=0 && i<NAvailable)
{ if (nmarked()>=2)
{ if
(!Marked[i]) unmarkall();

}
Marked[i]=!Marked[i];
}
PLISTER_draw(view);
}
void availableremove (int j)

/* remove number at j from available numbers */
{ int i;
if (j>=NAvailable) return;

for (i=j; i<NAvailable-1; i++)
{ Available[i]=Available[i+1];
Marked[i]=Marked[i+1];
}

NAvailable–;
}
void availableappend (int h)

/* append number h to available nubmers */
{ if (NAvailable>=6) return;
Available[NAvailable]=h;
Marked[NAvailable]=0;
NAvailable++;
}
void available (int command)
/* called from the main event handler (buttons +,-,*,/) */
{ if (nmarked()!=2) return;
int h1,h2,i1,i2,f,i,h;
long L;
f=0;
for (i=0; i<NAvailable; i++)
{ if (Marked[i])
{ if
(f) i2=i;
else
i1=i;
f++;
}
}
h1=Available[i1]; h2=Available[i2];
if (NAvailable==6) clear();
switch (command)
{ case cmdPlus :
h=h1+h2;
writesolution(6-NAvailable,h1,h2,’+‘,h);
break;
case cmdMinus :
if
(h1<h2)
{ h=h2-h1;
writesolution(6-NAvailable,h2,h1,‘-‚,h);
}
else
if (h1>h2)
{ h=h1-h2;
writesolution(6-NAvailable,h1,h2,‘-‚,h);
}
break;
case cmdMult :
L=h1*h2;
if
(L>30000l) return;
h=h1*h2;
writesolution(6-NAvailable,h1,h2,’*‘,h);
break;
case cmdDiv :
if
(h1>h2)
{ if
(h1%h2!=0) return;
h=h1/h2;
writesolution(6-NAvailable,h1,h2,’/‘,h);
}
else
if (h1<h2)
{ if
(h2%h1!=0) return;
h=h2/h1;
writesolution(6-NAvailable,h2,h1,’/‘,h);
}
else
if (h1==h2)
{ h=1;
writesolution(6-NAvailable,h2,h1,’/‘,h);
}
else
return;
}
availableremove(i2);
availableremove(i1);
availableappend(h);
}

This is some code! The last function, however, is already an interface,
which connects the +, -, * and / buttons to the available numbers list.
So it does not count. You have to assign the handler functions.

List=(PLISTER *)CreateLister?(
id++,95,row,120,row+5*15,6);
LoadBank(&(dsk->insert));
dsk->insert((VOID_PTR)dsk,List);
BankedAssign(List->writeItem,list_writeItem);
BankedAssign(List->penDownAct,list_pen);

So you only insert the List field as a complete object.

The last problem is to draw the lines, which seperate the panels. this is done in the panel redraw function.

void test_draw (VOID_PTR view)
{ /* the pointer is really a point to a DESKBOX */
PDESKBOX *dsk;
dsk=(PDESKBOX *)view;
PDESKBOX_draw(view); /* default, clears the view */
DrawLine(0,line1,160,line1,DRAW_BLACK);
DrawLine(0,line2,160,line2,DRAW_BLACK);
DrawLine(80,line1,80,line2,DRAW_BLACK);
}

The line1 and line2 variables are set by the generation routine, which inserts the screen elements from top to bottom.

After a first test, it was clear that a complete redraw of the screen
is to slow. So separate redraw functions had to be written for the
numbers, the target, the solution and the available number list.

void solution_draw ()
/* redraw the solution strings */
{ int i;
for (i=0; i<5; i++)
{ PVIEW *p;
p=(PVIEW *)Output[i];
FillRect(p->left,p->top,
p->right,p->bottom,

DRAW_WHITE);
PLABEL_draw((VOID_PTR)Output[i]);
}
}
void clearSolution ()

/* clear the solution area and redraw it */
{ clear();
solution_draw();
}

void available_draw ()

/* redraw the available nubmers */
{ PLISTER_draw((VOID_PTR)List);
}
void numbers_draw ()

/* redraw the numbers */
{ int i;
for (i=0; i<6; i++)

PNUMBERFIELD_draw((VOID_PTR)INumbers[i]);
}
void target_draw ()

/* redraw the target number */
{ PNUMBERFIELD_draw((VOID_PTR)ITarget);
}

The Solution strings are contained in the other source file, by the way.

Solving the problem

This routine is ported from the Java version. However, Java on a
Pentium 166 detects if any solution within seconds. I did not even
bother for multithreading. On the Avigo, detecting that there is no
solution takes minutes.

So I tried to make this interruptable by the user. I did it by calling GetEvent?
at regular intervals (every 1000 trials). However, this only makes the
application slower, since I am catching Timer interrupts only. In the
simulator however it works OK. So right now, you can only interrupt the
trial by pressing the TODO key. This does not work immediately, but
after a while. Switching off and on does not work. The Avigo just
resumes the computation. I will try to find a solution for this.

The main problem is the lack of a TestEvent function, which tests for events without waiting for them. There is a TestPutEvent
function but it only tests for events that you have put on the event
queue. It seems there is a special buffer for one such user put event.

I do switch off the auto power off during computation, and on again
after it is finished. So when the user interrupts the computation, its

auto power off setting may be switched off. I had not time to
investigate this.

Problems I met

I had two bugs, which were very hard to find. One was a missing case statement.

switch (TOWORD(x,y))
{ cmdNewProblem :
ClearEvent(evType);
mix(); makeTarget();
PDESKBOX_draw(view);
break;
cmdNewTarget :
ClearEvent(evType);
makeTarget();
PDESKBOX_draw(view);
break;
}

A clever C compiler might issue a warning about two unused labels. But
this is one of the reasons, why Java abandoned simple form of goto and
label usage. Such errors tend to be very tough to detect.

The second bug was an index out of range. The simulated Avigo fell into
the setup routine (tab the screen here and so on), when the function in
question was called. This is not exactly a clear error message. Again,
such errors are much easier to find in Java.

René Grothmann

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert