diff --git a/Chess/BoardView.swift b/Chess/BoardView.swift index 559d891..a10f0ac 100644 --- a/Chess/BoardView.swift +++ b/Chess/BoardView.swift @@ -24,6 +24,11 @@ class BoardView: UIView { private(set) var squares: [UIView] = [] private(set) var pieces: [String: UIImageView] = [:] private(set) var moveIndicators: [UIView] = [] + + var flipBlackPieces: Bool = false { + didSet { updatePieces() } + } + var theme: Theme = .init(rawValue: Storage.shared.boardTheme ?? "") ?? .classic { didSet { updateTheme() } } @@ -119,7 +124,10 @@ class BoardView: UIView { usedIDs.insert(piece.id) view.image = UIImage(named: piece.imageName) view.frame = frame(x: j, y: i, size: size) - view.layer.transform = CATransform3DMakeScale(0.8, 0.8, 0) + var transform = CATransform3DMakeScale(0.8, 0.8, 0) + let rotate = flipBlackPieces && piece.color == .black + transform = CATransform3DRotate(transform, rotate ? .pi : 0, 0, 0, 1) + view.layer.transform = transform } } for (id, view) in pieces where !usedIDs.contains(id) { @@ -198,13 +206,16 @@ private extension UIImageView { ) { let pulseView = UIImageView(frame: frame) pulseView.image = image + pulseView.layer.transform = layer.transform superview?.addSubview(pulseView) UIView.animate( withDuration: duration, delay: 0, options: .curveEaseOut, animations: { - pulseView.transform = .init(scaleX: scale, y: scale) + var transform = pulseView.layer.transform + transform = CATransform3DScale(transform, scale, scale, 1) + pulseView.layer.transform = transform pulseView.alpha = 0 }, completion: { _ in pulseView.removeFromSuperview() diff --git a/Chess/SettingsViewController.swift b/Chess/SettingsViewController.swift index abebd07..1a016e8 100644 --- a/Chess/SettingsViewController.swift +++ b/Chess/SettingsViewController.swift @@ -10,28 +10,9 @@ import UIKit class SettingsViewController: UIViewController { let tableView = UITableView(frame: .zero, style: .insetGrouped) - var selectedTheme: Theme { - didSet { - Storage.shared.boardTheme = selectedTheme.rawValue - tableView.reloadData() - } - } var onThemeSelect: ((Theme) -> Void)? - - init(selectedTheme: Theme?) { - self.selectedTheme = selectedTheme ?? .classic - super.init(nibName: nil, bundle: nil) - } - - deinit { - print("SettingsViewController deinit") - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + var onFlipBlackWhenHuman: ((Bool) -> Void)? override func viewDidLoad() { super.viewDidLoad() @@ -54,28 +35,76 @@ class SettingsViewController: UIViewController { } extension SettingsViewController: UITableViewDataSource, UITableViewDelegate { + enum Section: Int, CaseIterable { + case settings + case themes + } + func numberOfSections(in _: UITableView) -> Int { - 1 + Section.allCases.count } - func tableView(_: UITableView, titleForHeaderInSection _: Int) -> String? { - "Board Themes" + func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? { + switch Section(rawValue: section) { + case .settings: + return nil + case .themes: + return "Board Themes" + case nil: + return nil + } } - func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { - Theme.allCases.count + func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int { + switch Section(rawValue: section) { + case .settings: + return 1 + case .themes: + return Theme.allCases.count + case nil: + return 0 + } } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) - let theme = Theme.allCases[indexPath.row] - cell.textLabel?.text = theme.rawValue - cell.accessoryType = selectedTheme == theme ? .checkmark : .none + switch Section(rawValue: indexPath.section) { + case .settings: + cell.textLabel?.text = "Flip black pieces when human" + let toggle = UISwitch() + toggle.isOn = Storage.shared.flipBlackWhenHuman + toggle.addTarget(self, action: #selector(toggleFlipBlack), for: .valueChanged) + cell.accessoryView = toggle + case .themes: + let theme = Theme.allCases[indexPath.row] + cell.textLabel?.text = theme.rawValue + let selected = Storage.shared.boardTheme == theme.rawValue + cell.accessoryType = selected ? .checkmark : .none + cell.accessoryView = nil + case nil: + break + } return cell } + @objc private func toggleFlipBlack() { + let cell = tableView.cellForRow(at: IndexPath(item: 0, section: Section.settings.rawValue)) + let selected = (cell?.accessoryView as? UISwitch)?.isOn ?? false + Storage.shared.flipBlackWhenHuman = selected + onFlipBlackWhenHuman?(selected) + } + func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) { - selectedTheme = Theme.allCases[indexPath.row] - onThemeSelect?(selectedTheme) + switch Section(rawValue: indexPath.section) { + case .settings: + break + case .themes: + let selectedTheme = Theme.allCases[indexPath.row] + Storage.shared.boardTheme = selectedTheme.rawValue + tableView.reloadSections([Section.themes.rawValue], with: .none) + onThemeSelect?(selectedTheme) + case nil: + break + } } } diff --git a/Chess/Storage.swift b/Chess/Storage.swift index b7c2444..5f2dfbc 100644 --- a/Chess/Storage.swift +++ b/Chess/Storage.swift @@ -8,19 +8,15 @@ import Foundation -@propertyWrapper public struct UserDefaultWrapper { - public let key: String - public let storage: UserDefaults = .standard +@propertyWrapper struct UserDefaultWrapper { + let key: String + let `default`: Value + let storage: UserDefaults = .standard - public init(key: String) { - self.key = key - } - - public var wrappedValue: Value? { + var wrappedValue: Value { get { - storage.value(forKey: key) as? Value + storage.value(forKey: key) as? Value ?? `default` } - set { storage.setValue(newValue, forKey: key) storage.synchronize() @@ -31,6 +27,9 @@ import Foundation class Storage { static let shared = Storage() - @UserDefaultWrapper(key: "boardTheme") + @UserDefaultWrapper(key: "boardTheme", default: nil) var boardTheme: String? + + @UserDefaultWrapper(key: "flipBlackWhenHuman", default: false) + var flipBlackWhenHuman: Bool } diff --git a/Chess/ViewController.swift b/Chess/ViewController.swift index 57ab9c3..9e1c540 100644 --- a/Chess/ViewController.swift +++ b/Chess/ViewController.swift @@ -99,8 +99,11 @@ class ViewController: UIViewController { } @IBAction private func settings() { - let vc = SettingsViewController(selectedTheme: boardView?.theme ?? .classic) + let vc = SettingsViewController() vc.onThemeSelect = { self.boardView?.theme = $0 } + vc.onFlipBlackWhenHuman = { + self.boardView?.flipBlackPieces = $0 && self.game.blackIsHuman + } present(vc, animated: true) } } @@ -149,6 +152,7 @@ private extension ViewController { func updateUI() { setControl(undoButton, enabled: canUndo) setControl(resetButton, enabled: game.inProgress) + boardView?.flipBlackPieces = game.blackIsHuman && Storage.shared.flipBlackWhenHuman } func setControl(_ control: UIControl?, enabled: Bool) {