Skip to content

Commit

Permalink
Add queen-attack exercise
Browse files Browse the repository at this point in the history
  • Loading branch information
keiravillekode committed Dec 20, 2023
1 parent bcc6f8f commit 594bdff
Show file tree
Hide file tree
Showing 8 changed files with 334 additions and 0 deletions.
9 changes: 9 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,15 @@
"strings"
]
},
{
"slug": "queen-attack",
"name": "Queen Attack",
"uuid": "a503a4e2-b6eb-4ab0-ac67-8b136157f3dd",
"practices": [],
"prerequisites": [],
"difficulty": 4,
"topics": []
},
{
"slug": "triangle",
"name": "Triangle",
Expand Down
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
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": [
"keiravillekode"
],
"files": {
"solution": [
"queen-attack.sml"
],
"test": [
"test.sml"
],
"example": [
".meta/example.sml"
]
},
"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"
}
16 changes: 16 additions & 0 deletions exercises/practice/queen-attack/.meta/example.sml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
fun create (row: int, column: int) =
if row < 0 then raise Fail "row not positive"
else if row > 7 then raise Fail "row not on board"
else if column < 0 then raise Fail "column not positive"
else if column > 7 then raise Fail "column not on board"
else (row, column)

fun canAttack whiteQueen blackQueen: bool =
let
val (r1, c1) = whiteQueen
val (r2, c2) = blackQueen
val dr = r2 - r1
val dc = c2 - c1
in
dr = 0 orelse dc = 0 orelse dr = dc orelse dr + dc = 0
end
49 changes: 49 additions & 0 deletions exercises/practice/queen-attack/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# 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"

[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"

[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"
5 changes: 5 additions & 0 deletions exercises/practice/queen-attack/queen-attack.sml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fun create (row: int, column: int) =
raise Fail "'create' is not implemented"

fun canAttack whiteQueen blackQueen: bool =
raise Fail "'canAttack' is not implemented"
55 changes: 55 additions & 0 deletions exercises/practice/queen-attack/test.sml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
(* version 1.0.0 *)

use "testlib.sml";
use "queen-attack.sml";

infixr |>
fun x |> f = f x

val testsuite =
describe "queen-attack" [
describe "Test creation of Queens with valid and invalid positions" [
test "queen with a valid position"
(fn _ => create (2, 2) |> Expect.equalTo (create (2, 2))),

test "queen must have positive row"
(fn _ => (fn _ => create (~2, 2)) |> Expect.error (Fail "row not positive")),

test "queen must have row on board"
(fn _ => (fn _ => create (8, 4)) |> Expect.error (Fail "row not on board")),

test "queen must have positive column"
(fn _ => (fn _ => create (2, ~2)) |> Expect.error (Fail "column not positive")),

test "queen must have column on board"
(fn _ => (fn _ => create (4, 8)) |> Expect.error (Fail "column not on board"))
],

describe "Test the ability of one queen to attack another" [
test "cannot attack"
(fn _ => canAttack (create (2, 4)) (create (6, 6)) |> Expect.falsy),

test "can attack on same row"
(fn _ => canAttack (create (2, 4)) (create (2, 6)) |> Expect.truthy),

test "can attack on same column"
(fn _ => canAttack (create (4, 5)) (create (2, 5)) |> Expect.truthy),

test "can attack on first diagonal"
(fn _ => canAttack (create (2, 2)) (create (0, 4)) |> Expect.truthy),

test "can attack on second diagonal"
(fn _ => canAttack (create (2, 2)) (create (3, 1)) |> Expect.truthy),

test "can attack on third diagonal"
(fn _ => canAttack (create (2, 2)) (create (1, 1)) |> Expect.truthy),

test "can attack on fourth diagonal"
(fn _ => canAttack (create (1, 7)) (create (0, 6)) |> Expect.truthy),

test "cannot attack if falling diagonals are only the same when reflected across the longest falling diagonal"
(fn _ => canAttack (create (4, 1)) (create (2, 5)) |> Expect.falsy)
]
]

val _ = Test.run testsuite
160 changes: 160 additions & 0 deletions exercises/practice/queen-attack/testlib.sml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
structure Expect =
struct
datatype expectation = Pass | Fail of string * string

local
fun failEq b a =
Fail ("Expected: " ^ b, "Got: " ^ a)

fun failExn b a =
Fail ("Expected: " ^ b, "Raised: " ^ a)

fun exnName (e: exn): string = General.exnName e
in
fun truthy a =
if a
then Pass
else failEq "true" "false"

fun falsy a =
if a
then failEq "false" "true"
else Pass

fun equalTo b a =
if a = b
then Pass
else failEq (PolyML.makestring b) (PolyML.makestring a)

fun nearTo delta b a =
if Real.abs (a - b) <= delta * Real.abs a orelse
Real.abs (a - b) <= delta * Real.abs b
then Pass
else failEq (Real.toString b ^ " +/- " ^ Real.toString delta) (Real.toString a)

fun anyError f =
(
f ();
failExn "an exception" "Nothing"
) handle _ => Pass

fun error e f =
(
f ();
failExn (exnName e) "Nothing"
) handle e' => if exnMessage e' = exnMessage e
then Pass
else failExn (exnMessage e) (exnMessage e')
end
end

structure TermColor =
struct
datatype color = Red | Green | Yellow | Normal

fun f Red = "\027[31m"
| f Green = "\027[32m"
| f Yellow = "\027[33m"
| f Normal = "\027[0m"

fun colorize color s = (f color) ^ s ^ (f Normal)

val redit = colorize Red

val greenit = colorize Green

val yellowit = colorize Yellow
end

structure Test =
struct
datatype testnode = TestGroup of string * testnode list
| Test of string * (unit -> Expect.expectation)

local
datatype evaluation = Success of string
| Failure of string * string * string
| Error of string * string

fun indent n s = (implode (List.tabulate (n, fn _ => #" "))) ^ s

fun fmt indentlvl ev =
let
val check = TermColor.greenit "\226\156\148 " (**)
val cross = TermColor.redit "\226\156\150 " (**)
val indentlvl = indentlvl * 2
in
case ev of
Success descr => indent indentlvl (check ^ descr)
| Failure (descr, exp, got) =>
String.concatWith "\n" [indent indentlvl (cross ^ descr),
indent (indentlvl + 2) exp,
indent (indentlvl + 2) got]
| Error (descr, reason) =>
String.concatWith "\n" [indent indentlvl (cross ^ descr),
indent (indentlvl + 2) (TermColor.redit reason)]
end

fun eval (TestGroup _) = raise Fail "Only a 'Test' can be evaluated"
| eval (Test (descr, thunk)) =
(
case thunk () of
Expect.Pass => ((1, 0, 0), Success descr)
| Expect.Fail (s, s') => ((0, 1, 0), Failure (descr, s, s'))
)
handle e => ((0, 0, 1), Error (descr, "Unexpected error: " ^ exnMessage e))

fun flatten depth testnode =
let
fun sum (x, y, z) (a, b, c) = (x + a, y + b, z + c)

fun aux (t, (counter, acc)) =
let
val (counter', texts) = flatten (depth + 1) t
in
(sum counter' counter, texts :: acc)
end
in
case testnode of
TestGroup (descr, ts) =>
let
val (counter, texts) = foldr aux ((0, 0, 0), []) ts
in
(counter, (indent (depth * 2) descr) :: List.concat texts)
end
| Test _ =>
let
val (counter, evaluation) = eval testnode
in
(counter, [fmt depth evaluation])
end
end

fun println s = print (s ^ "\n")
in
fun run suite =
let
val ((succeeded, failed, errored), texts) = flatten 0 suite

val summary = String.concatWith ", " [
TermColor.greenit ((Int.toString succeeded) ^ " passed"),
TermColor.redit ((Int.toString failed) ^ " failed"),
TermColor.redit ((Int.toString errored) ^ " errored"),
(Int.toString (succeeded + failed + errored)) ^ " total"
]

val status = if failed = 0 andalso errored = 0
then OS.Process.success
else OS.Process.failure

in
List.app println texts;
println "";
println ("Tests: " ^ summary);
OS.Process.exit status
end
end
end

fun describe description tests = Test.TestGroup (description, tests)
fun test description thunk = Test.Test (description, thunk)

0 comments on commit 594bdff

Please sign in to comment.