Implementing the rules of 3 Man Chess: Variant “In The Round”. Part 1, Fundamental Types Of Fundamental Values

For the past three years, my go-to project for learning new programming languages and paradigms was the implementation of the rules of 3 Man Chess: Variant “In The Round”. I have used it to learn much about Go, Dart, Java, Clojure and Haskell. All the implementation attempts are available on my Github profile. So what’s the matter with the “attempts” word, why none of them is working, what’s so difficult? This is what this series is going to be about. But these it this “pilot” article, in this part, will be just fundamental data types because I don’t have much time. I have festival presentation deadlines for this project. One of them is today, in four hours. So, if you could look at the entirety of the code and help me, please do. https://github.com/ArchieT/ThreeManChess

First, the board. It is a circular board, consisting of six rings, later called Ranks, numbered from 0 (MostOuter) to 5 (MostInner).

data Rank = MostOuter | SecondOuter | MiddleOuter 
| MiddleInner | SecondInner | MostInner
deriving (Eq, Ord, Read, Show)
ranks :: [Rank]
inw :: Rank -> Rank
out :: Rank -> Maybe Rank

Each of those ranks consists of 24 Files.

The board is divided in three, one for each of the players. Opponents’ rooks are next to each other, but they cannot capture each other because of a feature called “moats”. Neither can pawns, because of a feature called “creeks”. If you want to know how it looks or have other questions, www.3manchess.com may answer them.

0. Set all Kings on white. White goes first, then clockwise to gray, then black.

data Color = White | Gray | Black
deriving (Eq, Ord, Read)
next :: Color -> Color
prev :: Color -> Color

So now we also need some data type that would correspond to number from 0 to 7, why not just use an Integer type? Because we want to be protected with our Haskellish type system, and another way we do this data type of ours may turn beneficial to us in some ways. So. Say hello to

data SegmentHalf = FirstHalf | SecondHalf 
deriving (Eq, Ord, Read, Show)
data SegmentQuarter = SegmentQuarter
{ half :: SegmentHalf
, halfQuarter :: SegmentHalf }
deriving (Eq, Ord, Read, Show)
data SegmentEight = SegmentEight
{ segmentQuarter :: SegmentQuarter
, quarterHalf :: SegmentHalf }
deriving (Eq, Ord, Read, Show)
type ColorSegment = Color
data File = File { segmColor :: ColorSegment
, colorSegmFile :: SegmentEight }
deriving (Eq, Ord, Read, Show)
opposite :: File -> File
plus :: File -> File
minus :: File -> File
-- and finally
type Pos = (Rank, File)
rank :: Pos -> Rank
file :: Pos -> File
-- |'kfm' is where the King stands
kfm :: SegmentEight
kfm = (SegmentEight (SegmentQuarter SecondHalf FirstHalf) FirstHalf)

Okay, enough about the board coordinates data structures.

1. The center of the board may be passed through, but has no square to be occupied.

a. Adjoining vertical squares, through the center, maintain the same color.

i. Queen, King, Rook, or Pawn, advancing 1 square through center, retains same color.

ii. Knight also will retain same color.

iii. This phenomenon is necessary to maintain ‘round board’ integrity. As players should be aware of this, conventional Chess strategy is not compromised.

[…]

3. VERTICAL MOVES (Queen, King, Rook, Pawn)

a. Follows straight, (and if necessary, through the center), along the checkerboard file.

And a Pawn can go only forward. So, we do not want to prove that it is not possible for a Pawn to perform so many captures as to it become unsure if it’s directed inwards or outwards, and am unsure if I had proven it to be impossible sometime ago already, but anyway such distinction helps. Therefore,

data FigType = InwardPawn | OutwardPawn | Rook | Knight
| Bishop | King | Queen
deriving (Eq, Read)
--for promotion purposes, we will also define
data Promotion = RookPromotion | KnightPromotion |
| BishopPromotion | QueenPromotion
deriving (Eq, Show)
desiredType :: Promotion -> FigType

4. HORIZONTAL MOVES (Queen, King, Rook)

a. Rotate about the center on the same row (rank).

So the moves are, like, modulo 24.

2. DIAGONAL MOVES (Queen, King, Bishop, or Pawn capturing)

a. Follow 1 of the 2 trajectory lines out from the square it rest on.

On the board, there are 48 diagonal trajectory lines, pairs of them crossing over each one of the squares. On the MostOuter rank, on each of the files a pair of trajectory lines forms a corner. Each of those pairs forms a loop over the center.

b. May rotate through the center.

c. Can not ‘turn the corner’ at the outer rank, in 1 move.

Also, what is not included in the rules but what was stated to be the right thing by Mr Clif King, the owner of 3 Man Chess, and I guess their creator, in our correspondence when I asked for this specifically and argumented for it, and which is not yet included in the rules, the figure must not go diagonally through the center or filewise(horizontally) around the center back to the square it went from — it would cause oft something like a lack of zugzwang (compulsion to move), an important feature of chess, and would have serious consequences in gameplay, especially in case of computer gameplay.

5. KNIGHT MOVES

a. 2 squares vertically then 1 square horizontally, or

b. 1 square vertically then 2 horizontally.

c. Use 1 of the 2 methods above (i.e., do not follow a trajectory line through the center). Also, these 2 methods are necessary for BORDER CONTROL described below.

These are just about the same, until when it comes to “border control”, i.e. “moats”.

So, there are multiple ways to perform a single from→to move, which we need to handle separately to avoid unnecessary computations. Therefore, we need data structures for that

data Orientation = Rankwise | Filewise deriving (Eq, Read, Show)
data RankwiseDirection = Inwards | Outwards
deriving (Eq, Show)
data FilewiseDirection = Pluswards | Minuswards
deriving (Eq, Show)
filewiseInc :: FilewiseDirection -> File -> File
data DiagonalDirection = DiagonalDirection
RankwiseDirection
FilewiseDirection
deriving (Eq, Show)
data Count = Once | OnceMore Count deriving (Eq, Read, Show)
class (Eq a) => LinearDirection a where
addOne :: a -> Pos -> Maybe Pos
instance LinearDirection RankwiseDirection where
addOne Inwards (MostInner, file) =
Just (MostInner, opposite file)
addOne Inwards (rank, file) =
Just (inw rank, file)
addOne Outwards (rank, file) =
do { o <- out rank;
return (o, file) }
instance LinearDirection FilewiseDirection where
addOne w (rank, file) = Just (rank, filewiseInc w file)
instance LinearDirection DiagonalDirection where
addOne (DiagonalDirection Inwards p)
(MostInner, file) = ... --long case expressions here
addOne (DiagonalDirection Inwards p) (rank, file) =
Just (inw rank, filewiseInc p file)
addOne (DiagonalDirection Outwards p) (rank, file) =
do { o <- out rank; return (o, filewiseInc p file) }

I have to go back to work now, and I am not sure if I have good reasons not to publish this beginning. There were almost no difficulties so far in this post, though even those few caveats took me many man-days of thinking back in the days. I am writing this post to motivate myself and to clarify the fundamentals of my code. Aaaand I’m hoping for collaborators. Really. Any. Any collaborators. You don’t need to know Haskell for more than a few hours, you will learn Haskell along the way.

So. https://github.com/ArchieT/ThreeManChess

If you want more information about something like “why don’t the rooks capture each other” or “what does it all look like”, look at www.3manchess.com.

Should I update this post later or just post next parts? Maybe posting next parts will be better.

Michał Krzysztof Feiler

Written by

A 19yo who tended to functional programming before heard about it; coming from Py and Go, thru Java, onto Clojure, heading Haskell, C++&Rust. https://archiet.pl