% modeling of a suanpan (Chinese abacus)
% D. Roegel 9 September - 12 October 2008
%
% These macros provide basic functions for producing suanpan (Chinese abacus)
% figures as well as soroban ones (Japanese abacus)
numeric u;
u=1cm;
% key-val mechanism (from LGC2, p. 59-60)
vardef executekeyval(text k)=
save _equals;
let _equals= =;
tertiarydef _ll_ _assign _rr_ =
hide(_ll_ _equals _rr_ ) 1
enddef;
save =;
let = _equals _assign ;
for _xx_ _equals k:endfor;
enddef;
numeric n; % number of digits
numeric nbl,vbu,nbu; % number of beads (lower/upper)
% vbu=value of a bead in the upper deck
numeric hsep,vsep,beadw,thframe,beadsp,wdvline,hlwindow,huwindow;
numeric wdframe,huwindow;
vardef draw_abacus_rods=
save h,ha,hb,savep;
savep=savepen;
ha=hlwindow;
hb=huwindow;
h=ha+hb+thframe;
for i=1 upto n:
draw (thframe+ihsep+(i-1)*hsep-.5wdvline,thframe)
--(thframe+ihsep+(i-1)*hsep-.5wdvline,thframe+ha);
draw (thframe+ihsep+(i-1)*hsep+.5wdvline,thframe)
--(thframe+ihsep+(i-1)*hsep+.5wdvline,thframe+ha);
draw (thframe+ihsep+(i-1)*hsep-.5wdvline,thframe+h-hb)
--(thframe+ihsep+(i-1)*hsep-.5wdvline,thframe+h);
draw (thframe+ihsep+(i-1)*hsep+.5wdvline,thframe+h-hb)
--(thframe+ihsep+(i-1)*hsep+.5wdvline,thframe+h);
if rod_numbers:
label.bot(decimal i,(thframe+ihsep+(n-i)*hsep,0));
label.top(decimal i,(thframe+ihsep+(n-i)*hsep,2thframe+h));
fi
endfor;
pickup pencircle scaled 3pt;
if abacus_units=1:
for i=3 step 3 until n:
draw (thframe+ihsep+(n-i)*hsep,thframe+ha+.5thframe);
endfor;
fi;
pickup savep;
enddef;
numeric valU[],valL[],vl; % stores the abacus values and the number of digits
numeric valUMu[],valUMs[],valLMs[],valLMu[]; % moved values will be in gray
% this macro draws a biconal bead, like on a soroban
vardef draw_soroban_bead(expr X,Y) text options=
define_soroban_bead_points(X,Y);
unfill pa;
draw pa;draw p1--p4;draw p5--p8;
enddef;
% this macro draws a biconal bead, like on a soroban
vardef draw_soroban_gray_bead(expr X,Y) text options=
define_soroban_bead_points(X,Y);
fill pa withcolor .7white;
draw pa;draw p1--p4;draw p5--p8;
enddef;
vardef draw_abacus_beads=
save na,nb,X,Y;
% we go through every bead line:
for i=1 upto n:
X:=thframe+ihsep+(n-i)*hsep;
% first, we handle the lower beads:
na:=nbl-valL[i];
nb:=valL[i];
% ------------------------------------------------------------
% these are the lower beads which have not been raised (unset)
for j=1 upto na:
Y:=thframe+.5vsep+(j-1)*vsep;
draw_bead(X,Y);
endfor;
% some of the previous ones are in gray:
for j=na downto na-(valLMu[i]-1):
Y:=thframe+.5vsep+(j-1)*vsep;
draw_gray_bead(X,Y);
endfor;
%-------------------------------------------------------------
% these are the lower beads which have been raised (set)
for j=1 upto nb:
Y:=thframe+hlwindow-(.5vsep+(j-1)*vsep);
draw_bead(X,Y);
endfor;
% some of the previous ones are in gray:
for j=nb downto nb-(valLMs[i]-1):
Y:=thframe+hlwindow-(.5vsep+(j-1)*vsep);
draw_gray_bead(X,Y);
endfor;
%-------------------------------------------------------------
% then we handle the upper beads:
na:=nbu-valU[i];
nb:=valU[i];
%--------------------------------------------------------------
% these are the upper beads which have not been lowered (unset)
for j=1 upto na:
Y:=htframe-(thframe+.5vsep+(j-1)*vsep);
draw_bead(X,Y);
endfor;
for j=na downto na-(valUMu[i]-1):
Y:=htframe-(thframe+.5vsep+(j-1)*vsep);
draw_gray_bead(X,Y);
endfor;
%--------------------------------------------------------------
% these are the upper beads which have been lowered (set)
for j=1 upto nb:
Y:=2thframe+hlwindow+(.5vsep+(j-1)*vsep);
draw_bead(X,Y);
endfor;
for j=nb downto nb-(valUMs[i]-1):
Y:=2thframe+hlwindow+(.5vsep+(j-1)*vsep);
draw_gray_bead(X,Y);
endfor;
%--------------------------------------------------------------
endfor;
enddef;
% v must be a string
vardef set_abacus_val(expr v)=
save vx;
numeric vx[];
reset_abacus;
vl:=min(length(v),n)-1; % handle overflows in the input
for i=0 upto length(v)-1:
vx[i]=ASCII(substring(i,i+1) of v)-ASCII("0");
endfor;
for i=1 upto length(v):
valL[i]:=vx[length(v)-i];
if valL[i]>vbu-1: % we must use vbu and not nbl, although
% they are usually the same
valU[i]:=1;
valL[i]:=valL[i]-vbu; % same remark
fi;
endfor;
enddef;
vardef reset_abacus_gray=
save s;
string s;
s="";
for i=1 upto n:s:=s & "0";endfor;
set_abacus_gray(deck="lower",below=s,above=s);
set_abacus_gray(deck="upper",below=s,above=s);
enddef;
vardef set_abacus_gray_below=
save vx;
numeric vx[];
reset_abacus_gray_below;
% reading the first string, for the lower part (which beads are gray)
for i=0 upto length(below)-1:
vx[i]:=ASCII(substring(i,i+1) of below)-ASCII("0");
endfor;
for i=1 upto length(below):
valLMu[i]:=vx[length(below)-i];
endfor;
% reading the second string, for the upper part (which beads are gray)
for i=0 upto length(above)-1:
vx[i]:=ASCII(substring(i,i+1) of above)-ASCII("0");
endfor;
for i=1 upto length(above):
valLMs[i]:=vx[length(above)-i];
endfor;
% we check that there is no conflict in the graying of beads:
% a bead can only be grayed if it exists
for i=1 upto n:
% lower beads ...............................
na:=nbl-valL[i];
nb:=valL[i];
% we force threshholds if necessary:
if valLMs[i]>valL[i]:valLMs[i]:=valL[i];fi
if valLMu[i]>na:valLMu[i]:=na;fi
endfor;
enddef;
vardef set_abacus_gray_above=
save vx;
numeric vx[];
reset_abacus_gray_above;
% reading the first string, for the lower part (which beads are gray)
for i=0 upto length(below)-1:
vx[i]:=ASCII(substring(i,i+1) of below)-ASCII("0");
endfor;
for i=1 upto length(below):
valUMs[i]:=vx[length(below)-i];
endfor;
% reading the second string, for the upper part (which beads are gray)
for i=0 upto length(above)-1:
vx[i]:=ASCII(substring(i,i+1) of above)-ASCII("0");
endfor;
for i=1 upto length(above):
valUMu[i]:=vx[length(above)-i];
endfor;
% we check that there is no conflict in the graying of beads:
% a bead can only be grayed if it exists
for i=1 upto n:
% upper beads ...............................
na:=nbu-valU[i];
nb:=valU[i];
% we force threshholds if necessary:
if valUMs[i]>valU[i]:valUMs[i]:=valU[i];fi
if valUMu[i]>na:valUMu[i]:=na;fi
endfor;
enddef;
vardef set_abacus_gray(text kv)=
save deck,below,above; % key names
string deck,below,above;
executekeyval(kv);
if deck="lower":
set_abacus_gray_below;
elseif deck="upper":
set_abacus_gray_above;
fi;
enddef;
% put the label `lab' on the j-th bead on row i (i=1 at the right)
vardef mark_abacus(text kv)(expr lab)=
save i,j; % key names
numeric i,j;
executekeyval(kv);
save X,Y,na,nb,ju;
X:=thframe+ihsep+(n-i)*hsep;
if j<=nbl: % the bead to mark is below:
% first, we handle the lower beads:
na:=nbl-valL[i];
nb:=valL[i];
% ------------------------------------------------------------
if j<=na:
% these are the lower beads which have not been raised (unset)
Y:=thframe+.5vsep+(j-1)*vsep;
else:
%-------------------------------------------------------------
% these are the lower beads which have been raised (set)
Y:=thframe+hlwindow-(.5vsep+(nb-(j-na))*vsep);
fi
else: % the bead to mark is above:
ju=j-nbl;
%-------------------------------------------------------------
% then we handle the upper beads:
na:=nbu-valU[i];
nb:=valU[i];
if ju>nb:
%--------------------------------------------------------------
% these are the upper beads which have not been lowered (unset)
Y:=htframe-(thframe+.5vsep+(na-(ju-nb))*vsep);
else:
%--------------------------------------------------------------
% these are the upper beads which have been lowered (set)
Y:=2thframe+hlwindow+(.5vsep+(ju-1)*vsep);
fi;
fi;
draw_bead_contour(X,Y);
draw thelabel(lab,(X,Y)); % withcolor white;
enddef;
def show_val=
for i=1 upto vl+1:
message "valU[" & decimal i & "]=" & decimal (valU[i]);
message "valL[" & decimal i & "]=" & decimal (valL[i]);
endfor;
enddef;
% v=value, iv=image view
def add_val(text kv)=
save v,iv,fig; % key names
string v;numeric iv;boolean fig;
executekeyval(kv);
save vx,VL,VU,carry;
numeric vx[],vL[],vU[],vx.len;
overflow:=false;
% we store the new value to add in one array
vx.len=min(length(v),n)-1; % we consider at most n digits
%message "vx.len=" & decimal vx.len;
for i=0 upto vx.len:
vx[i]=ASCII(substring(i,i+1) of v)-ASCII("0");
endfor;
for i=1 upto length(v):
vL[i]:=vx[length(v)-i];
endfor;
% if necessary, we modify the size of the first number:
if vx.len>vl:vl:=vx.len;fi
% the result will be either this wide, or will have one more digit
% to the left
if fig:
% the initial configuration:
beginfig(iv);
draw_abacus;
endfig;
fi
% we add the digits from left to right:
for i=vx.len+1 downto 1:
valL[i]:=valL[i]+vL[i];
% if there would be too many beads below, we adjust the upper ones:
forever:
if valL[i]>vbu-1:
valL[i]:=valL[i]-vbu;
valU[i]:=valU[i]+1;
fi
exitif valL[i]<vbu;
endfor;
% then, we propagate a carry if necessary:
carry:=0;
if valU[i]>1: % valU[i]=2 or 3 are possible values
carry:=1;
% propagate carry
valU[i]:=valU[i]-2;
for j=i+1 upto vl+1:
valL[j]:=valL[j]+carry;
if valL[j]>4:valL[j]:=0;valU[j]:=valU[j]+1;fi
if valU[j]>1:carry:=1;valU[j]:=valU[j]-2;else:carry:=0;fi
exitif carry=0; % no need to go all the way through,
% if the carry becomes 0
endfor;
if carry=1:
% we increment vl and we add the carry:
vl:=vl+1;
valL[vl+1]:=1;
if vl+1>n:overflow:=true;fi
fi
fi
%message "adding " & decimal vL[i] & " at position " & decimal i
% & " and generating view " & decimal (iv+(vx.len+1-i)+1);
if fig:
beginfig(iv+(vx.len+1-i)+1);
draw_abacus;
endfig;
fi
endfor;