所以我正在通过MOOC,斯坦福大学的“使用Swift开发iOS 11应用程序”,在课程中,教授高度强调在MVC架构中将View与模型分开是多么重要。
其中一项任务是此游戏的简单实现:Set。虽然我确实将它们分离了,但感觉关闭。
我正在寻找有关如何更多地改进模型结构的建议,而不添加特定的视图逻辑。例如,当我开始时,我没有使用Ints作为我的卡模型中的字段,而是使用了这样的枚举:
enum Symbol {
case square, circle, triangle
}
哪种方法有效,但教授建议不要这样做,因为我们会更新View层以使用不同的符号。这是我最终使用的代码,但正如我上面提到的那样,感觉很好,并且想知道更有经验的人会如何处理这个问题(最相关的部分是Card.swift,但我包含了所有内容以说明它是如何被使用的):< / p>
Card.swift
import Foundation
struct Card : Hashable {
var hashValue: Int {
return self.identifier
}
static func ==(lhs: Card, rhs: Card) -> Bool {
return lhs.identifier == rhs.identifier
}
let symbol: Int
let color: Int
let shading: Int
let quantity: Int
private let identifier: Int
private static var previousIdentifier = 0
private static func generateUid() -> Int {
previousIdentifier += 1
return previousIdentifier
}
init(symbol: Int, color: Int, shading: Int, quantity: Int) {
self.symbol = symbol
self.color = color
self.shading = shading
self.quantity = quantity
self.identifier = Card.generateUid()
}
static func doesMakeSet(_ cards: [Card]) -> Bool {
let colors = cards[0].color == cards[1].color && cards[1].color == cards[2].color
let shading = cards[0].shading == cards[1].shading && cards[1].shading == cards[2].shading
let quantities = cards[0].quantity == cards[1].quantity && cards[1].quantity == cards[2].quantity
let symbols = cards[0].symbol == cards[1].symbol && cards[1].symbol == cards[2].symbol
return colors || shading || quantities || symbols
}
}
Set.swift
import Foundation
class Set {
private var deck = [Card]()
private(set) var board = [Card]() {
didSet {
assert(board.count <= 24, "Set.board: board cannot contain more than 24 cards")
}
}
var canDealMoreCards: Bool {
let matchesThatCanBeRemoved = board.filter { matchedCards.contains($0) }
let occupiedSpacesOnBoard = board.count - matchesThatCanBeRemoved.count
let freeSpaces = 24 - occupiedSpacesOnBoard
let cardsRemaining = deck.count
return cardsRemaining != 0 && freeSpaces >= 3
}
private(set) var selectedCards = [Card]()
private(set) var matchedCards = [Card]()
var currentSelectionIsMatch: Bool?
private(set) var score = 0
private func createDeck() -> [Card] {
var newDeck = [Card]()
let noOfOptions = 1...3
for quantity in noOfOptions {
for symbol in noOfOptions {
for color in noOfOptions {
for shading in noOfOptions {
newDeck.append(Card(symbol: symbol, color: color, shading: shading, quantity: quantity))
}
}
}
}
return newDeck
}
func dealCards(numberOfCards: Int = 3) {
if numberOfCards + board.count > 24 {
board = board.filter { !matchedCards.contains($0) }
}
if deck.count >= numberOfCards {
let range = 0..<numberOfCards
board.append(contentsOf: deck[range])
deck.removeSubrange(range)
assert(board.count <= 24, "Set.dealCards(): there can't be more than 24 cards on the board but \(board.count) are")
}
}
func cardChosen(index: Int) {
if index > board.count - 1 {
return
}
let card = board[index]
if matchedCards.contains(card) {
return
}
if selectedCards.count == 3 {
if Card.doesMakeSet(selectedCards) {
matchedCards += selectedCards
selectedCards.removeAll()
score += 3
currentSelectionIsMatch = true
dealCards()
} else {
currentSelectionIsMatch = false
selectedCards.removeAll()
score -= 5
}
} else {
currentSelectionIsMatch = nil
}
if selectedCards.contains(card) {
selectedCards.remove(at: selectedCards.index(of: card)!)
} else {
if !matchedCards.contains(card) {
selectedCards.append(card)
}
}
}
init() {
deck = createDeck()
var shuffled = [Card]()
for _ in deck.indices {
let randomIndex = deck.count.arc4random
shuffled.append(deck.remove(at: randomIndex))
}
deck = shuffled
assert(deck.count == 81)
dealCards(numberOfCards: 12)
}
}
extension Int {
var arc4random: Int {
if self > 0 {
return Int(arc4random_uniform(UInt32(self)))
} else if self < 0 {
return -Int(arc4random_uniform(UInt32(abs(self))))
} else {
return 0
}
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController {
var game = Set()
@IBOutlet var cardButtons: [UIButton]!
@IBOutlet weak var deal3CardsButton: UIButton!
@IBOutlet weak var scoreLabel: UILabel!
@IBOutlet weak var statusLabel: UILabel!
override func viewDidLoad() {
updateViewFromModel()
}
@IBAction func cardTouched(_ sender: UIButton) {
if let index = cardButtons.index(of: sender) {
game.cardChosen(index: index)
updateViewFromModel()
}
}
@IBAction func deal3CardsTouched() {
game.dealCards()
updateViewFromModel()
}
@IBAction func newGameTouched() {
game = Set()
updateViewFromModel()
}
func updateViewFromModel() {
scoreLabel.text = "Score: \(game.score)"
if let match = game.currentSelectionIsMatch {
statusLabel.text = match ? "Match!" : "Not a Match!"
} else {
statusLabel.text = ""
}
cardButtons.forEach {
$0.hide()
}
for index in game.board.indices {
let text = getAttributedString(forCard: game.board[index])
cardButtons[index].setAttributedTitle(text, for: UIControlState.normal)
if game.selectedCards.contains(game.board[index]) {
cardButtons[index].backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
} else if game.matchedCards.contains(game.board[index]) {
cardButtons[index].hide()
} else {
cardButtons[index].backgroundColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
}
}
deal3CardsButton.isUserInteractionEnabled = game.canDealMoreCards
deal3CardsButton.backgroundColor = game.canDealMoreCards ? #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) : #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
}
let symbols = ["▲", "●", "■"]
let colors = [#colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1), #colorLiteral(red: 0.2392156869, green: 0.6745098233, blue: 0.9686274529, alpha: 1), #colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1)]
func getAttributedString (forCard card: Card) -> NSAttributedString {
let symbol = symbols[card.symbol-1]
let string = String(repeating: symbol, count: card.quantity)
let color = colors[card.color-1]
let shading = Shading(rawValue: card.shading)!
var attributes: [NSAttributedStringKey: Any] = [
.strokeWidth: -3,
.strokeColor: color,
.foregroundColor: color
]
switch shading {
case .fill:
break
case .open:
attributes[.foregroundColor] = color.withAlphaComponent(0)
case .striped:
attributes[.foregroundColor] = color.withAlphaComponent(0.25)
}
return NSAttributedString(string: string, attributes: attributes)
}
// var circle = Symbol.circle
}
// ▲ ● ■
enum Shading: Int {
case fill = 1, striped, open
}
extension UIButton {
func hide() {
self.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0)
self.setTitle(nil, for: UIControlState.normal)
self.setAttributedTitle(nil, for: UIControlState.normal)
}
}