Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add connect exercise #121

Merged
merged 3 commits into from
Sep 29, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,14 @@
"prerequisites": [],
"difficulty": 6
},
{
"slug": "connect",
"name": "Connect",
"uuid": "93bcc6cd-07c3-435d-a789-255dc4e4e934",
"practices": [],
"prerequisites": [],
"difficulty": 6
},
{
"slug": "gigasecond",
"name": "Gigasecond",
Expand Down
27 changes: 27 additions & 0 deletions exercises/practice/connect/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Instructions

Compute the result for a game of Hex / Polygon.

The abstract boardgame known as [Hex][hex] / Polygon / CON-TAC-TIX is quite simple in rules, though complex in practice.
Two players place stones on a parallelogram with hexagonal fields.
The player to connect his/her stones to the opposite side first wins.
The four sides of the parallelogram are divided between the two players (i.e. one player gets assigned a side and the side directly opposite it and the other player gets assigned the two other sides).

Your goal is to build a program that given a simple representation of a board computes the winner (or lack thereof).
Note that all games need not be "fair".
(For example, players may have mismatched piece counts or the game's board might have a different width and height.)

The boards look like this:

```text
. O . X .
. X X O .
O O O X .
. X O X O
X O O O X
```

"Player `O`" plays from top to bottom, "Player `X`" plays from left to right.
In the above example `O` has made a connection from left to right but nobody has won since `O` didn't connect top and bottom.

[hex]: https://en.wikipedia.org/wiki/Hex_%28board_game%29
102 changes: 102 additions & 0 deletions exercises/practice/connect/.meta/Example.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
module [winner]

Cell : [StoneO, StoneX, Empty]
Board : List (List Cell)
Position : { x : U64, y : U64 }

## Parse a string to a Board
parse : Str -> Result Board [InvalidCharacter U8]
parse = \boardStr ->
boardStr
|> Str.trim
|> Str.split "\n"
|> List.mapTry \row ->
row
|> Str.toUtf8
|> List.dropIf \char -> char == ' '
|> List.mapTry \char ->
when char is
'O' -> Ok StoneO
'X' -> Ok StoneX
'.' -> Ok Empty
_ -> Err (InvalidCharacter char)

## Ensure that the board has a least one cell, and that all rows have the same length
validate : Board -> Result Board [InvalidBoardShape]
validate = \board ->
rowLengths = board |> List.map List.len |> Set.fromList
if Set.len rowLengths != 1 || rowLengths == Set.fromList [0] then
Err InvalidBoardShape
else
Ok board

winner : Str -> Result [PlayerO, PlayerX] [NotFinished, InvalidCharacter U8, InvalidBoardShape]
winner = \boardStr ->
maybeBoard = boardStr |> parse?
board = maybeBoard |> validate?
ageron marked this conversation as resolved.
Show resolved Hide resolved
if board |> hasNorthSouthPath StoneO then
Ok PlayerO
else if board |> transpose |> hasNorthSouthPath StoneX then
Ok PlayerX
else
Err NotFinished

transpose : Board -> Board
transpose = \board ->
width = board |> firstRow |> List.len
List.range { start: At 0, end: Before width }
|> List.map \x ->
List.range { start: At 0, end: Before (board |> List.len) }
|> List.map \y ->
when board |> getCell { x, y } is
Ok cell -> cell
Err OutOfBounds -> crash "Unreachable: all rows have the same length"

firstRow : Board -> List Cell
firstRow = \board ->
when board |> List.first is
Ok row -> row
Err ListWasEmpty -> crash "Unreachable: the board has at least one cell"

getCell : Board, Position -> Result Cell [OutOfBounds]
getCell = \board, { x, y } ->
board |> List.get? y |> List.get x

hasNorthSouthPath : Board, Cell -> Bool
hasNorthSouthPath = \board, stone ->
hasPathToSouth : List Position, Set Position -> Bool
hasPathToSouth = \toVisit, visited ->
when toVisit is
[] -> Bool.false
[position, .. as rest] ->
isPlayerStone = board |> getCell position == Ok stone
if isPlayerStone && !(visited |> Set.contains position) then
{ x, y } = position
if y + 1 == List.len board then
Bool.true # we've reached the South!
else

neighbors =
[(-1, 0), (1, 0), (0, -1), (1, -1), (-1, 1), (0, 1)]
|> List.joinMap \(dx, dy) ->
nx = (x |> Num.toI64) + dx
ny = (y |> Num.toI64) + dy
if nx >= 0 && ny >= 0 then
[{ x: nx |> Num.toU64, y: ny |> Num.toU64 }]
else
[]
hasPathToSouth (rest |> List.concat neighbors) (visited |> Set.insert position)
else
hasPathToSouth rest visited

northStones : List Position
northStones =
board
|> firstRow
|> List.mapWithIndex \cell, x ->
when cell is
StoneO | StoneX -> if cell == stone then Ok { x, y: 0 } else Err NotPlayerStone
Empty -> Err NotPlayerStone
|> List.keepOks \id -> id
hasPathToSouth northStones (Set.empty {})

17 changes: 17 additions & 0 deletions exercises/practice/connect/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"authors": [
"ageron"
],
"files": {
"solution": [
"Connect.roc"
],
"test": [
"connect-test.roc"
],
"example": [
".meta/Example.roc"
]
},
"blurb": "Compute the result for a game of Hex / Polygon."
}
14 changes: 14 additions & 0 deletions exercises/practice/connect/.meta/template.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{%- import "generator_macros.j2" as macros with context -%}
{{ macros.canonical_ref() }}
{{ macros.header() }}

import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_camel }}]

{% for case in cases -%}
# {{ case["description"] }}
expect
board = {{ case["input"]["board"] | to_roc_multiline_string | indent(8) }}
result = board |> {{ case["property"] | to_camel }}
result == {% if case["expected"] == "" %}Err NotFinished{% else %}Ok Player{{ case["expected"] }}{% endif %}

{% endfor %}
40 changes: 40 additions & 0 deletions exercises/practice/connect/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[6eff0df4-3e92-478d-9b54-d3e8b354db56]
description = "an empty board has no winner"

[298b94c0-b46d-45d8-b34b-0fa2ea71f0a4]
description = "X can win on a 1x1 board"

[763bbae0-cb8f-4f28-bc21-5be16a5722dc]
description = "O can win on a 1x1 board"

[819fde60-9ae2-485e-a024-cbb8ea68751b]
description = "only edges does not make a winner"

[2c56a0d5-9528-41e5-b92b-499dfe08506c]
description = "illegal diagonal does not make a winner"

[41cce3ef-43ca-4963-970a-c05d39aa1cc1]
description = "nobody wins crossing adjacent angles"

[cd61c143-92f6-4a8d-84d9-cb2b359e226b]
description = "X wins crossing from left to right"

[73d1eda6-16ab-4460-9904-b5f5dd401d0b]
description = "O wins crossing from top to bottom"

[c3a2a550-944a-4637-8b3f-1e1bf1340a3d]
description = "X wins using a convoluted path"

[17e76fa8-f731-4db7-92ad-ed2a285d31f3]
description = "X wins using a spiral path"
5 changes: 5 additions & 0 deletions exercises/practice/connect/Connect.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module [winner]

winner : Str -> Result [PlayerO, PlayerX] _
winner = \boardStr ->
crash "Please implement the 'winner' function"
131 changes: 131 additions & 0 deletions exercises/practice/connect/connect-test.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# These tests are auto-generated with test data from:
# https://github.com/exercism/problem-specifications/tree/main/exercises/connect/canonical-data.json
# File last updated on 2024-09-27
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br",
}

main =
Task.ok {}

import Connect exposing [winner]

# an empty board has no winner
expect
board =
"""
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
"""
result = board |> winner
result == Err NotFinished

# X can win on a 1x1 board
expect
board = "X"
result = board |> winner
result == Ok PlayerX

# O can win on a 1x1 board
expect
board = "O"
result = board |> winner
result == Ok PlayerO

# only edges does not make a winner
expect
board =
"""
O O O X
X . . X
X . . X
X O O O
"""
result = board |> winner
result == Err NotFinished

# illegal diagonal does not make a winner
expect
board =
"""
X O . .
O X X X
O X O .
. O X .
X X O O
"""
result = board |> winner
result == Err NotFinished

# nobody wins crossing adjacent angles
expect
board =
"""
X . . .
. X O .
O . X O
. O . X
. . O .
"""
result = board |> winner
result == Err NotFinished

# X wins crossing from left to right
expect
board =
"""
. O . .
O X X X
O X O .
X X O X
. O X .
"""
result = board |> winner
result == Ok PlayerX

# O wins crossing from top to bottom
expect
board =
"""
. O . .
O X X X
O O O .
X X O X
. O X .
"""
result = board |> winner
result == Ok PlayerO

# X wins using a convoluted path
expect
board =
"""
. X X . .
X . X . X
. X . X .
. X X . .
O O O O O
"""
result = board |> winner
result == Ok PlayerX

# X wins using a spiral path
expect
board =
"""
O X X X X X X X X
O X O O O O O O O
O X O X X X X X O
O X O X O O O X O
O X O X X X O X O
O X O O O X O X O
O X X X X X O X O
O O O O O O O X O
X X X X X X X X O
"""
result = board |> winner
result == Ok PlayerX