Tutorial 2 (week 3) Solutions
You would want to start with something small like testing that the board is empty, testing that adding marks work, etc. We can then start going into more Tic-tac-toe specific logic, such as: marking with the same marks consecutively, marking the same cell, marking outside the 3 by 3 grid, and so on. Finally, we can focus on the winning logic: check for horizontal, vertical, and diagonal. We should also check that we cannot place any marks after the game ends.
Basically, this is how you would want to do it:
func sendEmailToUsers(message: String, database: PDatabase, emailService: PEmailService) { let userEmails = database.queryAll(table: "users").map { $0.email } emailService.sendEmail(to: userEmails, message: message) }
Here, we suppose that
PDatabase
andPEmailService
are both protocols.In the code, you want to write something like:
struct Database: PDatabase { ... } struct EmailService: PEmailService { ... } // At app initialisation let database = Database(...) let emailService = EmailService(...) // When calling the method sendEmailToUsers(message: "hello", database: database, emailService: emailService)
In the tests, you can mock this out in this sense:
// At test utils struct DatabaseMock: PDatabase { func queryAll(table: String) -> [User] { return User(email: "a@example.com") } } struct EmailServiceMock: PEmailService { private(set) var sentEmails: [String] private(set) var sentMessage: String func sendEmail(to emails: [String], message: String) { sentEmails = emails sentMessage = message } } // At the actual tests func testSendEmailToUsers() { let database = DatabaseMock() let emailService = EmailServiceMock() sendEmailToUsers(message: "hello", database: database, emailService: emailService) XCTAssertEqual(emailService.sentEmails, ["a@example.com"]) XCTAssertEqual(emailService.sentMessage, "hello") }
Something like this is acceptable:
import Foundation func intToHexString(_ number: UInt32) -> String { return String(format: "%02x", number) } struct SHA1StateMachine { private(set) var h0: UInt32 private(set) var h1: UInt32 private(set) var h2: UInt32 private(set) var h3: UInt32 private(set) var h4: UInt32 mutating func append(a: UInt32, b: UInt32, c: UInt32, d: UInt32, e: UInt32) { h0 &+= a h1 &+= b h2 &+= c h3 &+= d h4 &+= e } var output: String { return [h0, h1, h2, h3, h4].map(intToHexString).joined() } } func leftRotate(_ data: UInt32, by amount: Int) -> UInt32 { return data << amount | data >> (32 - amount) } func initialiseData(message: String) -> Data { var data = Data(message.utf8) let ml = data.count data.append(0x80) data.append(contentsOf: Array(repeating: 0, count: (64 + (55 - ml) % 64) % 64)) data.append(withUnsafeBytes(of: (ml * 8).bigEndian) { Data($0) }) assert(data.count.isMultiple(of: 64)) return data } func unpackUInt32(data: Data) -> [UInt32] { return stride(from: 0, to: data.count, by: 4).map { i -> UInt32 in let word = data[i..<i + 4] return word.withUnsafeBytes { bytes in bytes.load(as: UInt32.self).bigEndian } } } func prepareWords(data: Data) -> [UInt32] { var words = unpackUInt32(data: data) for i in 16..<80 { words.append(leftRotate( words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16], by: 1 )) } return words } func getFAndK(i: Int, b: UInt32, c: UInt32, d: UInt32) -> (UInt32, UInt32) { switch i { case 0..<20: return ((b & c) ^ ((~b) & d), 0x5A827999) case 20..<40: return (b ^ c ^ d, 0x6ED9EBA1) case 40..<60: return ((b & c) ^ (b & d) ^ (c & d), 0x8F1BBCDC) default: return (b ^ c ^ d, 0xCA62C1D6) } } func getNewState(word: UInt32, i: Int, a: UInt32, b: UInt32, c: UInt32, d: UInt32, e: UInt32) -> (UInt32, UInt32, UInt32, UInt32, UInt32) { let (f, k) = getFAndK(i: i, b: b, c: c, d: d) return (leftRotate(a, by: 5) &+ f &+ e &+ k &+ word, a, leftRotate(b, by: 30), c, d) } func processChunk(data: Data, stateMachine: SHA1StateMachine) -> (UInt32, UInt32, UInt32, UInt32, UInt32) { let words = prepareWords(data: data) var (a, b, c, d, e) = (stateMachine.h0, stateMachine.h1, stateMachine.h2, stateMachine.h3, stateMachine.h4) for (i, word) in words.enumerated() { (a, b, c, d, e) = getNewState(word: word, i: i, a: a, b: b, c: c, d: d, e: e) } return (a, b, c, d, e) } // Taken from https://en.wikipedia.org/wiki/SHA-1#SHA-1_pseudocode func sha1(_ message: String) -> String { let data = initialiseData(message: message) var stateMachine = SHA1StateMachine(h0: 0x67452301, h1: 0xEFCDAB89, h2: 0x98BADCFE, h3: 0x10325476, h4: 0xC3D2E1F0) for chunkIndex in stride(from: 0, to: data.count, by: 64) { let (a, b, c, d, e) = processChunk(data: data[chunkIndex..<chunkIndex + 64], stateMachine: stateMachine) stateMachine.append(a: a, b: b, c: c, d: d, e: e) } return stateMachine.output }
Here is a sample protocol:
protocol SudokuGridViewDelegate { func sudokuGridView(_: SudokuGridView, shouldPut: Int, at: Cell) -> Bool func sudokuGridView(_: SudokuGridView, didPut: Int, at: Cell) func sudokuGridViewShouldUndo(_: SudokuGridView) -> Bool func sudokuGridViewDidUndo(_: SudokuGridView) -> Bool }
Depending on what abstraction layer you wish to operate, you can also have something like:
protocol SudokuGridViewDelegate { func sudokuGridView(_: SudokuGridView, colorOfCell: Cell) -> UIColor func sudokuGridView(_: SudokuGridView, heightOfRow: Int) -> CGFloat }
Note that there are other names for this pattern, if you are a GoF (Gang of Four) follower. The refactoring.guru website has good descriptions on the patterns that are being used. Nevertheless, I would encourage less on remembering the exact names and more on recognising where they can be useful, why there are useful, and actually using them.