From 690842907b85764e784a549496124fc768e97cde Mon Sep 17 00:00:00 2001 From: meatball Date: Sat, 4 Jan 2025 21:57:45 +0100 Subject: [PATCH] Add range concept and exercise --- concepts/ranges/.meta/config.json | 7 ++ concepts/ranges/about.md | 74 ++++++++++++++++ concepts/ranges/introduction.md | 74 ++++++++++++++++ concepts/ranges/links.json | 6 ++ config.json | 17 ++++ .../concept/chess-board/.docs/instructions.md | 55 ++++++++++++ .../concept/chess-board/.docs/introduction.md | 74 ++++++++++++++++ exercises/concept/chess-board/.gitignore | 5 ++ .../ChessBoard/ChessBoardExemplar.swift | 12 +++ .../concept/chess-board/.meta/config.json | 18 ++++ exercises/concept/chess-board/Package.swift | 28 ++++++ .../Sources/ChessBoard/ChessBoard.swift | 11 +++ .../ChessBoardTests/ChessBoardTests.swift | 87 +++++++++++++++++++ 13 files changed, 468 insertions(+) create mode 100644 concepts/ranges/.meta/config.json create mode 100644 concepts/ranges/about.md create mode 100644 concepts/ranges/introduction.md create mode 100644 concepts/ranges/links.json create mode 100644 exercises/concept/chess-board/.docs/instructions.md create mode 100644 exercises/concept/chess-board/.docs/introduction.md create mode 100644 exercises/concept/chess-board/.gitignore create mode 100644 exercises/concept/chess-board/.meta/Sources/ChessBoard/ChessBoardExemplar.swift create mode 100644 exercises/concept/chess-board/.meta/config.json create mode 100644 exercises/concept/chess-board/Package.swift create mode 100644 exercises/concept/chess-board/Sources/ChessBoard/ChessBoard.swift create mode 100644 exercises/concept/chess-board/Tests/ChessBoardTests/ChessBoardTests.swift diff --git a/concepts/ranges/.meta/config.json b/concepts/ranges/.meta/config.json new file mode 100644 index 000000000..478ff96bc --- /dev/null +++ b/concepts/ranges/.meta/config.json @@ -0,0 +1,7 @@ +{ + "blurb": "Optionals can be used to represent the possible absence of a value.", + "authors": [ + "meatball133" + ], + "contributors": [] +} diff --git a/concepts/ranges/about.md b/concepts/ranges/about.md new file mode 100644 index 000000000..1d5dcf73f --- /dev/null +++ b/concepts/ranges/about.md @@ -0,0 +1,74 @@ +# Ranges + +[Ranges][range] represent an interval between two values. +The most common types that support ranges are `Int`, `String`, and `Date`. +They can be used for many things, such as quickly creating a collection, slicing an array, checking if a value is in a range, and iteration. +They are created using the range operator `...` or `..<` (inclusive and exclusive, respectively). + +```swift +1...5 // A range containing 1, 2, 3, 4, 5 +1..<5 // A range containing 1, 2, 3, 4 +``` + +The reason for having two range operators is to create ranges that are inclusive or exclusive of the end value, which can be useful when, for example, working with zero-based indexes. + +~~~~exercism/note +When creating a range in Swift using the range operators `...` or `..<`, and wanting to call a method on the range, you need to wrap the range in parentheses. +This is because the otherwise will the method be called on the 2nd argument of the range operator. + +```swift +(1...5).contains(3) // Returns true +1...5.contains(3) // => Error: value of type 'Int' has no member 'contains' +``` +~~~~ + +## Convert a range to an array + +To convert a range to an array, you can use the `Array` initializer. +This can be useful when you want to create a collection of values, without having to write them out. + +```swift +let range = 1...5 +let array = Array(range) // Returns [1, 2, 3, 4, 5] +``` + +## Slice an array + +Ranges can be used to slice an array. + +```swift +let array = [1, 2, 3, 4, 5] +let slice = array[1...3] // Returns [2, 3, 4] +``` + +## Range methods + +Ranges do have a set of methods that can be used to work with them. +For example, these methods can be used to get the sum of all the values in the range or check if the range includes a value. + +| Method                  | Description                                                             | Example                         | +| ----------------------- | ----------------------------------------------------------------------- | ------------------------------------- | +| `count`       | Returns the size of the range                                         | `(1...5).count // returns 5` | +| [`contains`][contains] | Returns `true` if the range includes the given value, otherwise `false` | `(1...5).contains(3) // Returns true` | + +## Endless & Beginningless ranges + +A range can be endless and beginless. + +Using beginless and endless ranges is useful when you want to, for example, slice an array from the beginning or to the end. + +~~~~exercism/caution +If not used on a collection, the endless range can cause an endless sequence, if not used with caution. +~~~~ + +## String ranges + +String can be used in ranges and allow you to get an interval of Strings between two Strings. +For example, this can be handy when you want to get the alphabet. + +```swift +"a"..."z" // A range containing ["a", "b", "c", ..., "z"] +``` + +[range]: https://developer.apple.com/documentation/swift/range +[contains]: https://developer.apple.com/documentation/swift/range/contains(_:) diff --git a/concepts/ranges/introduction.md b/concepts/ranges/introduction.md new file mode 100644 index 000000000..1d5dcf73f --- /dev/null +++ b/concepts/ranges/introduction.md @@ -0,0 +1,74 @@ +# Ranges + +[Ranges][range] represent an interval between two values. +The most common types that support ranges are `Int`, `String`, and `Date`. +They can be used for many things, such as quickly creating a collection, slicing an array, checking if a value is in a range, and iteration. +They are created using the range operator `...` or `..<` (inclusive and exclusive, respectively). + +```swift +1...5 // A range containing 1, 2, 3, 4, 5 +1..<5 // A range containing 1, 2, 3, 4 +``` + +The reason for having two range operators is to create ranges that are inclusive or exclusive of the end value, which can be useful when, for example, working with zero-based indexes. + +~~~~exercism/note +When creating a range in Swift using the range operators `...` or `..<`, and wanting to call a method on the range, you need to wrap the range in parentheses. +This is because the otherwise will the method be called on the 2nd argument of the range operator. + +```swift +(1...5).contains(3) // Returns true +1...5.contains(3) // => Error: value of type 'Int' has no member 'contains' +``` +~~~~ + +## Convert a range to an array + +To convert a range to an array, you can use the `Array` initializer. +This can be useful when you want to create a collection of values, without having to write them out. + +```swift +let range = 1...5 +let array = Array(range) // Returns [1, 2, 3, 4, 5] +``` + +## Slice an array + +Ranges can be used to slice an array. + +```swift +let array = [1, 2, 3, 4, 5] +let slice = array[1...3] // Returns [2, 3, 4] +``` + +## Range methods + +Ranges do have a set of methods that can be used to work with them. +For example, these methods can be used to get the sum of all the values in the range or check if the range includes a value. + +| Method                  | Description                                                             | Example                         | +| ----------------------- | ----------------------------------------------------------------------- | ------------------------------------- | +| `count`       | Returns the size of the range                                         | `(1...5).count // returns 5` | +| [`contains`][contains] | Returns `true` if the range includes the given value, otherwise `false` | `(1...5).contains(3) // Returns true` | + +## Endless & Beginningless ranges + +A range can be endless and beginless. + +Using beginless and endless ranges is useful when you want to, for example, slice an array from the beginning or to the end. + +~~~~exercism/caution +If not used on a collection, the endless range can cause an endless sequence, if not used with caution. +~~~~ + +## String ranges + +String can be used in ranges and allow you to get an interval of Strings between two Strings. +For example, this can be handy when you want to get the alphabet. + +```swift +"a"..."z" // A range containing ["a", "b", "c", ..., "z"] +``` + +[range]: https://developer.apple.com/documentation/swift/range +[contains]: https://developer.apple.com/documentation/swift/range/contains(_:) diff --git a/concepts/ranges/links.json b/concepts/ranges/links.json new file mode 100644 index 000000000..a9c240d5c --- /dev/null +++ b/concepts/ranges/links.json @@ -0,0 +1,6 @@ +[ + { + "url": "https://developer.apple.com/documentation/swift/range", + "description": "Swift docs: Range" + } + ] \ No newline at end of file diff --git a/config.json b/config.json index 0376ea85c..13e7087af 100644 --- a/config.json +++ b/config.json @@ -119,6 +119,18 @@ ], "status": "active" }, + { + "slug": "chess-game", + "name": "Chess Game", + "uuid": "162bf709-ed48-438d-9a32-f572099cf42e", + "concepts": [ + "ranges" + ], + "prerequisites": [ + "arrays" + ], + "status": "active" + }, { "slug": "santas-helper", "name": "Santa's Swifty Helper", @@ -1503,6 +1515,11 @@ "slug": "repeat-while", "name": "repeat while" }, + { + "uuid": "cb0d8166-672c-4bdf-820b-d60531574d73", + "slug": "ranges", + "name": "Ranges" + }, { "uuid": "a4b7ebd1-28f4-4270-910f-cf511ec4b45a", "slug": "self", diff --git a/exercises/concept/chess-board/.docs/instructions.md b/exercises/concept/chess-board/.docs/instructions.md new file mode 100644 index 000000000..78b85db9a --- /dev/null +++ b/exercises/concept/chess-board/.docs/instructions.md @@ -0,0 +1,55 @@ +# Instructions + +As a chess enthusiast, you want to write your version of the game. +Yes, there may be plenty of implementations of chess available online already, but yours will be unique! + +You start with implementing a chess board. + +The chess game will be played on an eight-square wide and eight-square long board. +The squares are identified by a letter and a number. + +## 1. Define rank & file range + +The game will have to store the ranks of the board. +The ranks are the rows of the board, and are numbered from 1 to 8. + +The game will also have to store the files on the board. +The files are the board's columns and are identified by the letters A to H. + +Define the `ranks` and `files` constants that store the range of ranks and files respectively. + +```swift +ranks +// returns 1..8 + +files +// returns 'A'..'H' +``` + +## 2. Check if square is valid + +The game will have to check if a square is valid. +A square is valid if the rank and file are within the ranges of the ranks and files. + +Define the `isValidSquare(rank:file:)` method that takes the arguments `rank` that holds an int of the rank and `file` that holds a char of the file. +The method should return `true` if the rank and file are within the range of ranks and files, and return `false` otherwise. + +```swift +isValidSquare(rank: 1, file: "A") +// returns true +``` + +## 3. Get row + +The game will store all the squares of the board in a single dimensional array. +The squares are formed as a string of the rank and file, e.g. "1A", "8B", "4G", etc. +To get the row of a square, the game will have to calculate the index of the first square of the row. + +Define the `getRow(rank:)` method that takes the argument `rank` that holds an int of the rank. +The method should return an array of strings that represent the squares of the row. + +```swift +let board = ["1A", "1B", "1C", "1D", "1E", "1F", "1G", "1H", "2A", ..., "8H"] +getRow(board, rank: 1) +// returns ["1A", "1B", "1C", "1D", "1E", "1F", "1G", "1H"] +``` diff --git a/exercises/concept/chess-board/.docs/introduction.md b/exercises/concept/chess-board/.docs/introduction.md new file mode 100644 index 000000000..1d5dcf73f --- /dev/null +++ b/exercises/concept/chess-board/.docs/introduction.md @@ -0,0 +1,74 @@ +# Ranges + +[Ranges][range] represent an interval between two values. +The most common types that support ranges are `Int`, `String`, and `Date`. +They can be used for many things, such as quickly creating a collection, slicing an array, checking if a value is in a range, and iteration. +They are created using the range operator `...` or `..<` (inclusive and exclusive, respectively). + +```swift +1...5 // A range containing 1, 2, 3, 4, 5 +1..<5 // A range containing 1, 2, 3, 4 +``` + +The reason for having two range operators is to create ranges that are inclusive or exclusive of the end value, which can be useful when, for example, working with zero-based indexes. + +~~~~exercism/note +When creating a range in Swift using the range operators `...` or `..<`, and wanting to call a method on the range, you need to wrap the range in parentheses. +This is because the otherwise will the method be called on the 2nd argument of the range operator. + +```swift +(1...5).contains(3) // Returns true +1...5.contains(3) // => Error: value of type 'Int' has no member 'contains' +``` +~~~~ + +## Convert a range to an array + +To convert a range to an array, you can use the `Array` initializer. +This can be useful when you want to create a collection of values, without having to write them out. + +```swift +let range = 1...5 +let array = Array(range) // Returns [1, 2, 3, 4, 5] +``` + +## Slice an array + +Ranges can be used to slice an array. + +```swift +let array = [1, 2, 3, 4, 5] +let slice = array[1...3] // Returns [2, 3, 4] +``` + +## Range methods + +Ranges do have a set of methods that can be used to work with them. +For example, these methods can be used to get the sum of all the values in the range or check if the range includes a value. + +| Method                  | Description                                                             | Example                         | +| ----------------------- | ----------------------------------------------------------------------- | ------------------------------------- | +| `count`       | Returns the size of the range                                         | `(1...5).count // returns 5` | +| [`contains`][contains] | Returns `true` if the range includes the given value, otherwise `false` | `(1...5).contains(3) // Returns true` | + +## Endless & Beginningless ranges + +A range can be endless and beginless. + +Using beginless and endless ranges is useful when you want to, for example, slice an array from the beginning or to the end. + +~~~~exercism/caution +If not used on a collection, the endless range can cause an endless sequence, if not used with caution. +~~~~ + +## String ranges + +String can be used in ranges and allow you to get an interval of Strings between two Strings. +For example, this can be handy when you want to get the alphabet. + +```swift +"a"..."z" // A range containing ["a", "b", "c", ..., "z"] +``` + +[range]: https://developer.apple.com/documentation/swift/range +[contains]: https://developer.apple.com/documentation/swift/range/contains(_:) diff --git a/exercises/concept/chess-board/.gitignore b/exercises/concept/chess-board/.gitignore new file mode 100644 index 000000000..95c432091 --- /dev/null +++ b/exercises/concept/chess-board/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ diff --git a/exercises/concept/chess-board/.meta/Sources/ChessBoard/ChessBoardExemplar.swift b/exercises/concept/chess-board/.meta/Sources/ChessBoard/ChessBoardExemplar.swift new file mode 100644 index 000000000..0b7211a0a --- /dev/null +++ b/exercises/concept/chess-board/.meta/Sources/ChessBoard/ChessBoardExemplar.swift @@ -0,0 +1,12 @@ +let ranks = 1...8 +let files = "A"..."H" + +func isVaildSquare(rank: Int, file: String) -> Bool { + return ranks.contains(rank) && files.contains(file) +} + +func getRow(_ board : [String], rank: Int) -> [String] { + let startIndex = (rank - 1) * 8 + let endIndex = startIndex + 8 + return Array(board[startIndex.. Bool { + fatalError("Please implement the isVaildSquare(rank:file:) function") +} + +func getRow(_ board : [String], rank: Int) -> [String] { + fatalError("Please implement the isVaildSquare(_:rank:) function") +} diff --git a/exercises/concept/chess-board/Tests/ChessBoardTests/ChessBoardTests.swift b/exercises/concept/chess-board/Tests/ChessBoardTests/ChessBoardTests.swift new file mode 100644 index 000000000..43ad8c070 --- /dev/null +++ b/exercises/concept/chess-board/Tests/ChessBoardTests/ChessBoardTests.swift @@ -0,0 +1,87 @@ +import Testing +import Foundation + +@testable import ChessBoard + +let RUNALL = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "true"]) ?? false + +@Suite struct ChessBoardTests { + func generateBoard() -> [String] { + var board = [String]() + let files2 = ["A", "B", "C", "D", "E", "F", "G", "H"] + for rank in 1...8 { + for file in files2 { + board.append("\(rank)\(file)") + } + } + return board + } + + @Test("that there is a 8 correct ranks") + func testRanks() { + let expected = 1...8 + #expect(ranks == expected) + } + + @Test("that there is a 8 correct files", .enabled(if: RUNALL)) + func testFiles() { + let expected = "A"..."H" + #expect(files == expected) + } + + @Test("that it returns true when given a valid square", .enabled(if: RUNALL)) + func testValidSquare() { + let rank = 1 + let file = "A" + #expect(isVaildSquare(rank: rank, file: file)) + } + + @Test("that it returns true for another valid square", .enabled(if: RUNALL)) + func testAnotherValidSquare() { + let rank = 8 + let file = "H" + #expect(isVaildSquare(rank: rank, file: file)) + } + + @Test("that it returns false when rank is out of range", .enabled(if: RUNALL)) + func testRankOutOfRange() { + let rank = 9 + let file = "B" + #expect(!isVaildSquare(rank: rank, file: file)) + } + + @Test("that it returns false when file is out of range", .enabled(if: RUNALL)) + func testFileOutOfRange() { + let rank = 1 + let file = "I" + #expect(!isVaildSquare(rank: rank, file: file)) + } + + @Test("that it returns false when rank is less than one", .enabled(if: RUNALL)) + func testRankLessThanOne() { + let rank = 0 + let file = "A" + #expect(!isVaildSquare(rank: rank, file: file)) + } + + @Test("that is returns correct first row", .enabled(if: RUNALL)) + func testFirstRow() { + let board = generateBoard() + let expected = ["1A", "1B", "1C", "1D", "1E", "1F", "1G", "1H"] + #expect(getRow(board, rank: 1) == expected) + } + + @Test("that is returns correct last row", .enabled(if: RUNALL)) + func testLastRow() { + let board = generateBoard() + let expected = ["8A", "8B", "8C", "8D", "8E", "8F", "8G", "8H"] + #expect(getRow(board, rank: 8) == expected) + } + + @Test("that is returns correct middle row", .enabled(if: RUNALL)) + func testMiddleRow() { + let board = generateBoard() + let expected = ["4A", "4B", "4C", "4D", "4E", "4F", "4G", "4H"] + #expect(getRow(board, rank: 4) == expected) + } +}