'3 Man Chess: In The Round' in APL (NARS2000). Part 1.

2022-04-07

I had previously written a post on getting into APL:

2022-03-31 How I was getting started with APL recently. NARS2000.

You can find traces of getting started with the below in it.

https://3manchess.com

'3 Man Chess: In The Round' was once created by Clif W. King, the board set used to be US-patented but the patent expired.

Years ago I led a team project for student exhibitions for a Festival of Arts and Science of a computer implementation of it. The project was brought to the attention of Clif King and had his assistance with the rules.

Since the closest-to-finished implementation ended up being very deeply wrongfully designed for some of the bugs to be fixed, the effort failed to produce a working game for the end user.

His son has since written an iOS implementation that I couldn't try out due to lack of relevant devices. I am left wondering if that rules implementation ended up being restrictive enough.

The game has since been my goto playtoy for several computer languages along with their paradigms and recommended or interesting Zens and other ways.

Figure: function 'addvec'

result ← from addvec vec; rank
:select
➥{
➥(⍵[1]>⍵[2])∨(⍵[1]=0): ⍵[1] ⍵[2]  ⋄
➥⍵[1]=⍵[2]:            2/⍵[1]≠0   ⋄ 
➥                      ⍵[2] ⍵[1]  } |vec
:case 2 1
 rank←from[1]+vec[1]
 result←{rank>6: 13-rank ⋄ rank} (modfile from[2]+vec[2]+(rank>6)×12)
:case 1 1
 :if from[1]=6
 :andif vec[1]>0
  result←(6 (modfile from[2]-10××vec[2])) ∇ (¯1 1)×(¯1××vec)+vec
 :elseif 7≤vec[1]+from[1]
  result←(6 (modfile from[2]+6-from[1])) ∇ vec-(6-from[1])××vec
 :else
  result←modfile from+vec
 :endif
:case 1 0
 rank←from[1]+vec[1]
 :if rank>6
  result← (13-rank) (modfile from[2]+12)
 :else
  result← rank from[2]
 :endif
:case 0 1
 result←modfile from+vec
:case 0 0
 result←from
:endselect

The above function accepts a left argument 'from' and a right argument 'vec'. It has 'rank' marked as a local variable.

For various types of "vectors" of pieces' movement it is supposed to return the destination coordinates.

The whole body of the function is a ':SELECT' statement. The denominator of the SELECT is written out multiline, with the Line Continuation characters (➥) inserted by me where the NARS2000 displayed them - that's a so-called 'physical' line-break that is not a 'logical' line-break.

It is an anonymous function written with 'guards' that has two conditional cases and a default case. It is then ran on the (mapped to absolute values with the Stile - pipe character meaning "Magnitude" function when monadic) 'vec' argument to our function.

The first conditional case is to if the first element of the vector is greater than the second, or if the first element of the vector is zero, to return the vector's two elements in their original order.

The second conditional case is to if the elements are equal to return a vector of two ones if the elements are non-zero or else of two zeroes.

The default case is to return the vector's two elements in reverse order.

The SELECT statement has no default case, and its cases are "2 1", "1 1", "1 0", "0 1" and "0 0". I expect that due to there being no default case that would assign to the return variable 'result', any wrong 'vec' will result in a "syntax error".

The first case is "2 1" and it represents a knight move. To the helper variable 'rank' the sum of first coordinates of 'from' and the vector is assigned. Then the result is: first coordinate: if 'rank' is greater than 6 (6th is the innermost rank of the board that is a ring to be precise) then 13 minus the value of it, otherwise just the value of it; second coordinate: the boolean value of "'rank' greater than 6" is multiplied by 12 (i.e. 12 when true (greater), zero when false) and added to the sum of second coordinates of 'from' and the vector, then the modfile function is applied.

modfile ← { 1 + 24| ⍵-1 }

The modfile function is substracting one from its argument, having it modulo 24, and then adding one to it and returning. That's because the circular board has 24 files that go in a circle, and since APL uses one-indexing we just have our coordinates from 1 to 24 and not from 0 to 23.

The way ring-board works is there is an empty center that the pieces can cross and they land on the other side, on still the innermost rank but on the file opposite, that is plus 12.

The second case is "1 1" which represents a diagonal move. Diagonal moves are made so that the piece could return to its square of origin ('from') if going inwards from the outermost (first) rank.

It has an IF statement with two conditional cases and a default one.

The first conditional case is to if the rank of origin is the innermost AND the vector is directed inwards, to have the result be the value of our enclosing function recursively (we can write the Del (delta) as well as the full name 'addvec' but the former makes refactoring easier) with the square of origin argument being on innermost (6th) rank and the file being the file of origin with ten times *the sign* of vector's file coordinate (that is, the square that we land after jumping through the center diagonally), and the vector argument being the original vector with a negated unit vector of its original signs substracted, and then the rank coordinate negated.

The second conditional case is to if the sum of the rank of origin and the vector's rank coordinate is 7 or more (i.e. we cross the center but are not starting from the innermost rank) to have the result be the value of our enclosing function recursively again with the square of origin argument again being on the innermost rank and the file being the file of origin plus the distance between the rank of origin and the innermost rank, and the vector argument being our vector with the unit vector of its original signs multiplied by the same ranks distance substracted from it. That is to move us to the first conditional case in the recursion.

The default case is when we are not crossing the center and we can just add the coordinates and 'modfile' the file (and the rank too just to write less, because the rank is much less than 24 anyway).

The third case is "1 0" which represents a rank-wise move vector. It, likewise, assigns to the helper variable 'rank' the sum of first coordinates of 'from' and the vector. If that's greater than 6, it returns the destination square coordinates on the opposite side of the board, and otherwise returns the coordinates with the same file coordinate.

The fourth case is "0 1" which represents a file-wise move vector. That just adds the coordinates, again putting them through 'modfile' function. That's because we never pass through the center moving filewise.

The last case is the zero case, that while such move is not legal for no piece, returns the square of origin; just in case.

Please ask questions (email or wherever) if you were to happen to want to bother yourself asking. I'm eager to answer and to make some sort of FAQ in this post if there were to be any interest.

I had another one prepared for this part, but it's already long, I will just make this half the first.

Next day update

See the next day Part 2 below:

2022-04-08 '3 Man Chess: In The Round' in APL (NARS2000). Part 2.