diff --git a/config.json b/config.json index 607f8df..87650d7 100644 --- a/config.json +++ b/config.json @@ -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", diff --git a/exercises/practice/queen-attack/.docs/instructions.md b/exercises/practice/queen-attack/.docs/instructions.md new file mode 100644 index 0000000..97f22a0 --- /dev/null +++ b/exercises/practice/queen-attack/.docs/instructions.md @@ -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 diff --git a/exercises/practice/queen-attack/.meta/config.json b/exercises/practice/queen-attack/.meta/config.json new file mode 100644 index 0000000..ed03726 --- /dev/null +++ b/exercises/practice/queen-attack/.meta/config.json @@ -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" +} diff --git a/exercises/practice/queen-attack/.meta/example.sml b/exercises/practice/queen-attack/.meta/example.sml new file mode 100644 index 0000000..a051420 --- /dev/null +++ b/exercises/practice/queen-attack/.meta/example.sml @@ -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 diff --git a/exercises/practice/queen-attack/.meta/tests.toml b/exercises/practice/queen-attack/.meta/tests.toml new file mode 100644 index 0000000..e062412 --- /dev/null +++ b/exercises/practice/queen-attack/.meta/tests.toml @@ -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" diff --git a/exercises/practice/queen-attack/queen-attack.sml b/exercises/practice/queen-attack/queen-attack.sml new file mode 100644 index 0000000..b75ec66 --- /dev/null +++ b/exercises/practice/queen-attack/queen-attack.sml @@ -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" diff --git a/exercises/practice/queen-attack/test.sml b/exercises/practice/queen-attack/test.sml new file mode 100644 index 0000000..1e91e03 --- /dev/null +++ b/exercises/practice/queen-attack/test.sml @@ -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 diff --git a/exercises/practice/queen-attack/testlib.sml b/exercises/practice/queen-attack/testlib.sml new file mode 100644 index 0000000..0c8370c --- /dev/null +++ b/exercises/practice/queen-attack/testlib.sml @@ -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)