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 queen attack #103

Merged
merged 2 commits into from
Sep 21, 2024
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -287,6 +287,14 @@
"prerequisites": [],
"difficulty": 3
},
{
"slug": "queen-attack",
"name": "Queen Attack",
"uuid": "a246b648-cad7-4cff-b776-562896689a13",
"practices": [],
"prerequisites": [],
"difficulty": 3
},
{
"slug": "rectangles",
"name": "Rectangles",
Expand Down
27 changes: 27 additions & 0 deletions exercises/practice/queen-attack/.docs/instructions.append.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Instructions append

In Chess, rows are called "ranks", and columns are called "files". Ranks range
from 1 to 8, while files range from A to H.

In this exercise, `Square` is an opaque type which represents a square on a
chessboard. It uses 0-indexed `row` & `column` fields internally, and it
guarantees that they are always valid (i.e., from 0 to 7).

The `create` function takes a `Str` as input, representing a valid square on
the chessboard using the official notation, such as `"C5"`. The `create`
function must parse this `Str`, verify that it represents a valid square, and if
so it must return a `Square` value containing the appropriate `row` and `column`
fields. Rank 1 corresponds to row 0, and rank 8 corresponds to row 7. For
example, `create "C5"` must return `@Square { row : 2, column : 3 }`.

The `QueenAttack` module also exposes handy `rank` & `file` functions. In the
example above, `rank` must return the integer `5`, and `file` must return the
character `'C'`.

Lastly, the `queenCanAttack` function takes two different `Square` values and
checks whether or not queens placed on these squares can attack each other.

Take-away: opaque types such as `Square` are great when you want to offer some
guarantees, such as the fact that `row` and `column` are always between 0 and 7.
Opaque types also hide implementation details from the users, which makes the
API cleaner.
21 changes: 21 additions & 0 deletions exercises/practice/queen-attack/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Instructions

Given the position of two queens on a chess board, indicate whether or not they are positioned so that they can attack each other.

In the game of chess, a queen can attack pieces which are on the same row, column, or diagonal.

A chessboard can be represented by an 8 by 8 array.

So if you are told the white queen is at `c5` (zero-indexed at column 2, row 3) and the black queen at `f2` (zero-indexed at column 5, row 6), then you know that the set-up is like so:

![A chess board with two queens. Arrows emanating from the queen at c5 indicate possible directions of capture along file, rank and diagonal.](https://assets.exercism.org/images/exercises/queen-attack/queen-capture.svg)

You are also able to answer whether the queens can attack each other.
In this case, that answer would be yes, they can, because both pieces share a diagonal.

## Credit

The chessboard image was made by [habere-et-dispertire][habere-et-dispertire] using LaTeX and the [chessboard package][chessboard-package] by Ulrike Fischer.

[habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire
[chessboard-package]: https://github.com/u-fischer/chessboard
30 changes: 30 additions & 0 deletions exercises/practice/queen-attack/.meta/Example.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module [create, rank, file, queenCanAttack]

Square := { row : U8, column : U8 }

rank : Square -> U8
rank = \@Square { row, column: _ } -> 8 - row

file : Square -> U8
file = \@Square { row: _, column } -> column + 'A'

create : Str -> Result Square [InvalidSquare]
create = \squareStr ->
chars = squareStr |> Str.toUtf8
if List.len chars != 2 then
Err InvalidSquare
else

fileChar = chars |> List.get 0 |> Result.mapErr? \OutOfBounds -> InvalidSquare
rankChar = chars |> List.get 1 |> Result.mapErr? \OutOfBounds -> InvalidSquare
if fileChar < 'A' || fileChar > 'H' || rankChar < '1' || rankChar > '8' then
Err InvalidSquare
else
Ok (@Square { row: 7 - (rankChar - '1'), column: fileChar - 'A' })

queenCanAttack : Square, Square -> Bool
queenCanAttack = \@Square { row: r1, column: c1 }, @Square { row: r2, column: c2 } ->
absDiff = \u, v -> if u < v then v - u else u - v
rowDiff = absDiff r1 r2
columnDiff = absDiff c1 c2
rowDiff == 0 || columnDiff == 0 || rowDiff == columnDiff
19 changes: 19 additions & 0 deletions exercises/practice/queen-attack/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"authors": [
"ageron"
],
"files": {
"solution": [
"QueenAttack.roc"
],
"test": [
"queen-attack-test.roc"
],
"example": [
".meta/Example.roc"
]
},
"blurb": "Given the position of two queens on a chess board, indicate whether or not they are positioned so that they can attack each other.",
"source": "J Dalbey's Programming Practice problems",
"source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html"
}
12 changes: 12 additions & 0 deletions exercises/practice/queen-attack/.meta/plugins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
def to_square(queen):
rank = to_rank(queen["position"]["row"])
file = to_file(queen["position"]["column"])
return f"{file}{rank}"


def to_rank(row):
return 8 - row


def to_file(column):
return chr(column + ord("A"))
44 changes: 44 additions & 0 deletions exercises/practice/queen-attack/.meta/template.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{%- import "generator_macros.j2" as macros with context -%}
{{ macros.canonical_ref() }}
{{ macros.header() }}

import {{ exercise | to_pascal }} exposing [create, rank, file, queenCanAttack]

{% for supercase in cases %}
##
## {{ supercase["description"] }}
##

{% for case in supercase["cases"] -%}
# {{ case["description"] }}
{%- if case["property"] == "create" %}
{%- if case["expected"] == 0 %}
expect
maybeSquare = create "{{ plugins.to_square(case["input"]["queen"]) }}"
result = maybeSquare |> Result.try \square ->
Ok (rank square)
result == Ok {{ plugins.to_rank(case["input"]["queen"]["position"]["row"]) }}

expect
maybeSquare = create "{{ plugins.to_square(case["input"]["queen"]) }}"
result = maybeSquare |> Result.try \square ->
Ok (file square)
result == Ok '{{ plugins.to_file(case["input"]["queen"]["position"]["column"]) }}'
{%- else %}
expect
result = create "{{ plugins.to_square(case["input"]["queen"]) }}"
result |> Result.isErr
{%- endif %}
{%- elif case["property"] == "canAttack" %}
expect
maybeSquare1 = create "{{ plugins.to_square(case["input"]["white_queen"]) }}"
maybeSquare2 = create "{{ plugins.to_square(case["input"]["black_queen"]) }}"
result = when (maybeSquare1, maybeSquare2) is
(Ok square1, Ok square2) ->
square1 |> queenCanAttack square2
_ -> crash "Unreachable: {{ plugins.to_square(case["input"]["white_queen"]) }} and {{ plugins.to_square(case["input"]["black_queen"]) }} are both valid squares"
result == {{ case["expected"] | to_roc }}
{%- endif %}

{% endfor %}
{% endfor %}
51 changes: 51 additions & 0 deletions exercises/practice/queen-attack/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# 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.

[3ac4f735-d36c-44c4-a3e2-316f79704203]
description = "Test creation of Queens with valid and invalid positions -> queen with a valid position"

[4e812d5d-b974-4e38-9a6b-8e0492bfa7be]
description = "Test creation of Queens with valid and invalid positions -> queen must have positive row"
include = false

[f07b7536-b66b-4f08-beb9-4d70d891d5c8]
description = "Test creation of Queens with valid and invalid positions -> queen must have row on board"

[15a10794-36d9-4907-ae6b-e5a0d4c54ebe]
description = "Test creation of Queens with valid and invalid positions -> queen must have positive column"
include = false

[6907762d-0e8a-4c38-87fb-12f2f65f0ce4]
description = "Test creation of Queens with valid and invalid positions -> queen must have column on board"

[33ae4113-d237-42ee-bac1-e1e699c0c007]
description = "Test the ability of one queen to attack another -> cannot attack"

[eaa65540-ea7c-4152-8c21-003c7a68c914]
description = "Test the ability of one queen to attack another -> can attack on same row"

[bae6f609-2c0e-4154-af71-af82b7c31cea]
description = "Test the ability of one queen to attack another -> can attack on same column"

[0e1b4139-b90d-4562-bd58-dfa04f1746c7]
description = "Test the ability of one queen to attack another -> can attack on first diagonal"

[ff9b7ed4-e4b6-401b-8d16-bc894d6d3dcd]
description = "Test the ability of one queen to attack another -> can attack on second diagonal"

[0a71e605-6e28-4cc2-aa47-d20a2e71037a]
description = "Test the ability of one queen to attack another -> can attack on third diagonal"

[0790b588-ae73-4f1f-a968-dd0b34f45f86]
description = "Test the ability of one queen to attack another -> can attack on fourth diagonal"

[543f8fd4-2597-4aad-8d77-cbdab63619f8]
description = "Test the ability of one queen to attack another -> cannot attack if falling diagonals are only the same when reflected across the longest falling diagonal"
19 changes: 19 additions & 0 deletions exercises/practice/queen-attack/QueenAttack.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module [create, rank, file, queenCanAttack]

Square := { row : U8, column : U8 }

rank : Square -> U8
rank = \@Square { row, column } ->
crash "Please implement the 'rank' function"

file : Square -> U8
file = \@Square { row, column } ->
crash "Please implement the 'file' function"

create : Str -> Result Square _
create = \squareStr ->
crash "Please implement the 'create' function"

queenCanAttack : Square, Square -> Bool
queenCanAttack = \square1, square2 ->
crash "Please implement the 'queenCanAttack' function"
143 changes: 143 additions & 0 deletions exercises/practice/queen-attack/queen-attack-test.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# These tests are auto-generated with test data from:
# https://github.com/exercism/problem-specifications/tree/main/exercises/queen-attack/canonical-data.json
# File last updated on 2024-09-21
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 QueenAttack exposing [create, rank, file, queenCanAttack]

##
## Test creation of Queens with valid and invalid positions
##

# queen with a valid position
expect
maybeSquare = create "C6"
result =
maybeSquare
|> Result.try \square ->
Ok (rank square)
result == Ok 6

expect
maybeSquare = create "C6"
result =
maybeSquare
|> Result.try \square ->
Ok (file square)
result == Ok 'C'

# queen must have row on board
expect
result = create "E0"
result |> Result.isErr

# queen must have column on board
expect
result = create "I4"
result |> Result.isErr

##
## Test the ability of one queen to attack another
##

# cannot attack
expect
maybeSquare1 = create "E6"
maybeSquare2 = create "G2"
result =
when (maybeSquare1, maybeSquare2) is
(Ok square1, Ok square2) ->
square1 |> queenCanAttack square2

_ -> crash "Unreachable: E6 and G2 are both valid squares"
result == Bool.false

# can attack on same row
expect
maybeSquare1 = create "E6"
maybeSquare2 = create "G6"
result =
when (maybeSquare1, maybeSquare2) is
(Ok square1, Ok square2) ->
square1 |> queenCanAttack square2

_ -> crash "Unreachable: E6 and G6 are both valid squares"
result == Bool.true

# can attack on same column
expect
maybeSquare1 = create "F4"
maybeSquare2 = create "F6"
result =
when (maybeSquare1, maybeSquare2) is
(Ok square1, Ok square2) ->
square1 |> queenCanAttack square2

_ -> crash "Unreachable: F4 and F6 are both valid squares"
result == Bool.true

# can attack on first diagonal
expect
maybeSquare1 = create "C6"
maybeSquare2 = create "E8"
result =
when (maybeSquare1, maybeSquare2) is
(Ok square1, Ok square2) ->
square1 |> queenCanAttack square2

_ -> crash "Unreachable: C6 and E8 are both valid squares"
result == Bool.true

# can attack on second diagonal
expect
maybeSquare1 = create "C6"
maybeSquare2 = create "B5"
result =
when (maybeSquare1, maybeSquare2) is
(Ok square1, Ok square2) ->
square1 |> queenCanAttack square2

_ -> crash "Unreachable: C6 and B5 are both valid squares"
result == Bool.true

# can attack on third diagonal
expect
maybeSquare1 = create "C6"
maybeSquare2 = create "B7"
result =
when (maybeSquare1, maybeSquare2) is
(Ok square1, Ok square2) ->
square1 |> queenCanAttack square2

_ -> crash "Unreachable: C6 and B7 are both valid squares"
result == Bool.true

# can attack on fourth diagonal
expect
maybeSquare1 = create "H7"
maybeSquare2 = create "G8"
result =
when (maybeSquare1, maybeSquare2) is
(Ok square1, Ok square2) ->
square1 |> queenCanAttack square2

_ -> crash "Unreachable: H7 and G8 are both valid squares"
result == Bool.true

# cannot attack if falling diagonals are only the same when reflected across the longest falling diagonal
expect
maybeSquare1 = create "B4"
maybeSquare2 = create "F6"
result =
when (maybeSquare1, maybeSquare2) is
(Ok square1, Ok square2) ->
square1 |> queenCanAttack square2

_ -> crash "Unreachable: B4 and F6 are both valid squares"
result == Bool.false