GDCR 2023: My participation summary
Gautier DI FOLCO November 05, 2022 [Code practice] #gdcr #code kata #coding dojo #code retreatAs every year, the Global day of code retreat is a good excuse to work and rework the same kata a full day.
This year I have attended to the one organized by the Software Crafters Lyon (I'm a member of the organizing team, but I didn't organize the Code Retreat this time).
Subject
It was Codurance's Mars Rover Kata.
I have previously attempted it multiple times in the past, but I did not enjoy it.
My iterations

 Language: Python
 Paired with a freshman student
 It was an exploratory session
 During the introduction, he enumerated a long list of programming he knew, but since he did not have a configured development environment I have chosen Python as I knew it comes with a testing library
 We focused on the rover: making it move forward, teleport at boundaries
 I feel it was more an introduction to industry development

 Language: Java
 Paired with a junior developer
 Additional constraints: Immutable and/or Primitive Obsession
 We focused on the rover: making it move forward, teleport at boundaries, rotate
 We chose Immutable and it was a real challenge for my pair

 Language: Rust
 Paired with a midlevel embedded software developer (C)
 Additional constraints: Evil TDD and/or no controlflow
 We focused on the rover: making it move forward, teleport at boundaries, rotate
 We picked both constraints, while no controlflow was natural, Evil TDD was really fun for me:
 not because I have tried to trap my pair, on the contrary, I have tried to follow a classic TDD style, but at some point it was too uncomfortable for him and he went for a giant step
 the fun part was in his effort to come with complex tests, while I came up with quick and simple implementations

 Language: Scala
 Paired with a midlevel software developer and former part of my group I have already paired with a big number of times
 Additional constraints: Test Commit or Revert and/or PingPong
 We focused on the rover: making it move forward, teleport at boundaries, rotate
 We chose both constraints, but instead of the recommended 2 minutes for the TCR, I have used 4 minutes because I have not done any Scala for a long time, and I'm not used to Mac keyboard layout

 Language: TypeScript
 Paired with a senior frontend software developer and part of my group I have already paired with a couple of times
 Additional constraints: Random constraints (I have got "one level of indentation" and my pair "no controlflow"), but we have judged it was too easy, so we have added "no return"
 We focused on the rover: making it move forward, teleport at boundaries, rotate
 I think it was the most creative session, we have ended up with a map of lambdas for each command

 Language: F#
 I was a mob programming with 1012 other developers
 Additional constraints: Evil TDD / Everything named with one letter
 It was a fun session, we have started with the rover (again) and we went up to deal with obstacles as a simple list
 It was interesting to see that even if our tests name were not helpful, it was key to understand the structure of our code
 Also, we have seen that functions extractions did not help much for structuring code comprehension
My feedback
Unlike last year, I did not have big expectations, consequently, I did not get frustrated to achieve less and less, and I have had a lot of fun along the day.
I have given a ROTI of 4/5.
My attempt
Here is my attempt, let's start with the Rover, without command:
=
describe "Mars Rover Kata" $ do
it "No command should be at start" $
roverCommands "" `shouldBe` "0:0:N"
roverCommands _ = "0:0:N"
spec
It's hardcoded, let's write another one with a move:
 ...
it "Moving once should be in 0:1:N" $
roverCommands "M" `shouldBe` "0:1:N"
roverCommands =
\case
"M" > "0:1:N"
_ > "0:0:N"
Still enumerating, but we can refactor our tests:
=
describe "Mars Rover Kata" $ do
forM_
[ ("No command", "", "0:0:N"),
("Moving once", "M", "0:1:N")
]
$ \(name, commands, result) >
it (name <> " should be " <> result) $
roverCommands commands `shouldBe` result
roverCommands =
\case
"M" > "0:1:N"
_ > "0:0:N"
spec
Let's move without wrapping:
("Moving four times (not wrapping)", "MMMM", "0:4:N")
roverCommands =
\case
xs > "0:" <> show (length xs) <> ":N"
Let's move with wrapping:
("Moving five times (wrapping)", "MMMMM", "0:0:N")
roverCommands =
\case
xs > "0:" <> show (length xs `mod` 5) <> ":N"
We can rotate once:
("Rotating Right", "R", "0:0:E")
roverCommands =
\case
"R" > "0:0:E"
xs > "0:" <> show (length xs `mod` 5) <> ":N"
We can try rotating and moving forward:
("Rotating Right and move", "RM", "1:0:E")
roverCommands = displayPosition . foldl' go (Rover {x = 0, y = 0, direction = North})
where
go p =
\case
'R' > p {direction = East}
_ >
case p.direction of
North > p {y = (p.y + 1) `mod` 5}
East > p {x = p.x + 1}
data Direction = North  East
data Rover = Rover
{ x :: Int,
}
displayPosition p = show p.x <> ":" <> show p.y <> ":" <> displayedPosition
where
displayedPosition =
case p.direction of
North > "N"
East > "E"
That's a huge step, anyway, let's skip wrapping and other rotations:
=
describe "Mars Rover Kata" $ do
forM_
[ ("No command", "", "0:0:N"),
("Moving once", "M", "0:1:N"),
("Moving four times (not wrapping)", "MMMM", "0:4:N"),
("Moving five times (wrapping)", "MMMMM", "0:0:N"),
("Rotating Right", "R", "0:0:E"),
("Rotating Right and move", "RM", "1:0:E"),
("Rotating Right and move four times (not wrapping)", "RMMMM", "4:0:E"),
("Rotating Right and move five times (wrapping)", "RMMMMM", "0:0:E"),
("Rotating Left", "L", "0:0:W"),
("Rotating Left and move (wrapping)", "LM", "4:0:W"),
("Rotating Left and move five times (not wrapping twice)", "LMMMMM", "0:0:W"),
("Rotating Left and move six times (wrapping twice)", "LMMMMMM", "4:0:W"),
("Rotating Left twice", "LL", "0:0:S"),
("Rotating Left twice and move (wrapping)", "LLM", "0:4:S"),
("Rotating Left twice and move five times (not wrapping twice)", "LLMMMMM", "0:0:S"),
("Rotating Left twice and move six times (wrapping twice)", "LLMMMMMM", "0:4:S"),
("Rotating Right twice", "RR", "0:0:S"),
("Rotating Right three times", "RRR", "0:0:W")
]
$ \(name, commands, result) >
it (name <> " should be " <> result) $
roverCommands commands `shouldBe` result
roverCommands = displayPosition . foldl' go (Rover {x = 0, y = 0, direction = North})
where
go p =
\case
'R' >
p
{ direction =
case p.direction of
North > East
East > South
South > West
West > North
}
'L' >
p
{ direction =
case p.direction of
North > West
East > North
South > East
West > South
}
_ >
case p.direction of
North > p {y = (p.y + 1) `mod` 5}
East > p {x = (p.x + 1) `mod` 5}
South > p {y = (p.y  1) `mod` 5}
West > p {x = (p.x  1) `mod` 5}
data Direction = North  East  South  West
data Rover = Rover
{ x :: Int,
}
displayPosition p = show p.x <> ":" <> show p.y <> ":" <> displayedPosition
where
displayedPosition =
case p.direction of
North > "N"
East > "E"
South > "S"
West > "W"
spec
Finally, we can deal with obstacle:
 ...
("Blocked rover have to rotate", [Position 0 1], "MRM", "1:0:E")
]
$ \(name, obstacles, commands, result) >
it (name <> " should be " <> result) $
roverCommands obstacles commands `shouldBe` result
roverCommands obstacles =
displayPosition . foldl' go (Rover {position = Position {x = 0, y = 0}, direction = North})
where
go p =
\case
 ...
'M' >
let nextRover =
case p.direction of
North > p {position = (p.position) {y = (p.position.y + 1) `mod` 5}}
East > p {position = (p.position) {x = (p.position.x + 1) `mod` 5}}
South > p {position = (p.position) {y = (p.position.y  1) `mod` 5}}
West > p {position = (p.position) {x = (p.position.x  1) `mod` 5}}
in if notElem nextRover.position obstacles
then nextRover
else p
c > error $ "Unknown command " <> show c
data Rover = Rover
{ position :: Position,
}
data Position = Position
{ x :: Int,
}
deriving stock (Eq)
I had to extract Position
from Rover
, that's why I never start with data
types when I design, because they are bound to my functions (and the system's flow).
I could create Grid
and delegate it new position computation:
 ...
$ \(name, obstacles, commands, result) >
it (name <> " should be " <> result) $
roverCommands (computeNextPosition $ Grid obstacles) commands `shouldBe` result
roverCommands nextPosition =
 ...
'M' >
let potentialPosition =
case p.direction of
North > (p.position) {y = p.position.y + 1}
East > (p.position) {x = p.position.x + 1}
South > (p.position) {y = p.position.y  1}
West > (p.position) {x = p.position.x  1}
in p {position = fromMaybe p.position $ nextPosition potentialPosition}
c > error $ "Unknown command " <> show c
newtype Grid = Grid {obstacles :: [Position]}
computeNextPosition grid p =
if notElem normalizedPosition grid.obstacles
then Just normalizedPosition
else Nothing
where
normalizedPosition = Position {x = p.x `mod` 5, y = p.y `mod` 5}