From dfb7bafd64cf317d373046192b0e26872f55428d Mon Sep 17 00:00:00 2001 From: Marco Benzi Date: Sun, 23 Jun 2019 19:05:36 +0200 Subject: [PATCH] Tab == 2 spaces --- TetraVex/AppDelegate.swift | 248 ++++++------ TetraVex/TVBoardView.swift | 204 +++++----- TetraVex/TVGameViewController.swift | 274 ++++++------- TetraVex/TVHighScoreViewController.swift | 114 +++--- TetraVex/TVHighScores.swift | 90 ++--- TetraVex/TVPieceView.swift | 470 +++++++++++------------ TetraVexKit/TVBoardModel.swift | 152 ++++---- TetraVexKit/TVGameModel.swift | 16 +- TetraVexKit/TVPieceModel.swift | 60 +-- TetraVexKit/TVPuzzleGenerator.swift | 80 ++-- TetraVexKitTests/TetraVexKitTests.swift | 180 ++++----- TetraVexUITests/TetraVexUITests.swift | 70 ++-- 12 files changed, 977 insertions(+), 981 deletions(-) diff --git a/TetraVex/AppDelegate.swift b/TetraVex/AppDelegate.swift index 567c2a8..654835b 100644 --- a/TetraVex/AppDelegate.swift +++ b/TetraVex/AppDelegate.swift @@ -11,139 +11,135 @@ import TetraVexKit @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { - - var currentGameModel : TVGameModel = TVGameModel() - var currentGamePieces : [[TVPieceModel]]? - - //MARK: - Resizing board - func setBoardSize(width: Int, height: Int) { - currentGameModel.boardWidth = width - currentGameModel.boardHeight = height - } - /* - * Reminder, First Responder actions have to marked with @IBAction - * to appear in the Interface Builder. - */ + + var currentGameModel : TVGameModel = TVGameModel() + var currentGamePieces : [[TVPieceModel]]? var optionMenu: NSMenuItem? { return NSApplication.shared.mainMenu? .item(withTitle: "Options") } + //MARK: - Resizing board + func setBoardSize(width: Int, height: Int) { + currentGameModel.boardWidth = width + currentGameModel.boardHeight = height + } + // MARK: Board size menu manipulation var sizeSubMenu: NSMenuItem? { return optionMenu?.submenu?.item(withTitle: "Size") } - @IBAction func setBoardTo2x2(sender: Any?) { - setBoardSize(width: 2, height: 2) - let sm = sizeSubMenu?.submenu - sm?.item(withTitle: "2x2")?.state = NSControl.StateValue(rawValue: 1) - sm?.item(withTitle: "3x3")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "4x4")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "5x5")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "6x6")?.state = NSControl.StateValue(rawValue: 0) - } - - @IBAction func setBoardTo3x3(sender: Any?) { - setBoardSize(width: 3, height: 3) - let sm = sizeSubMenu?.submenu - sm?.item(withTitle: "2x2")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "3x3")?.state = NSControl.StateValue(rawValue: 1) - sm?.item(withTitle: "4x4")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "5x5")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "6x6")?.state = NSControl.StateValue(rawValue: 0) - } - - @IBAction func setBoardTo4x4(sender: Any?) { - setBoardSize(width: 4, height: 4) - let sm = sizeSubMenu?.submenu - sm?.item(withTitle: "2x2")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "3x3")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "4x4")?.state = NSControl.StateValue(rawValue: 1) - sm?.item(withTitle: "5x5")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "6x6")?.state = NSControl.StateValue(rawValue: 0) - } - - @IBAction func setBoardTo5x5(sender: Any?) { - setBoardSize(width: 5, height: 5) - let sm = sizeSubMenu?.submenu - sm?.item(withTitle: "2x2")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "3x3")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "4x4")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "5x5")?.state = NSControl.StateValue(rawValue: 1) - sm?.item(withTitle: "6x6")?.state = NSControl.StateValue(rawValue: 0) - } - - @IBAction func setBoardTo6x6(sender: Any?) { - setBoardSize(width: 6, height: 6) - let sm = sizeSubMenu?.submenu - sm?.item(withTitle: "2x2")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "3x3")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "4x4")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "5x5")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "6x6")?.state = NSControl.StateValue(rawValue: 1) - } + @IBAction func setBoardTo2x2(sender: Any?) { + setBoardSize(width: 2, height: 2) + let sm = sizeSubMenu?.submenu + sm?.item(withTitle: "2x2")?.state = NSControl.StateValue(rawValue: 1) + sm?.item(withTitle: "3x3")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "4x4")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "5x5")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "6x6")?.state = NSControl.StateValue(rawValue: 0) + } + @IBAction func setBoardTo3x3(sender: Any?) { + setBoardSize(width: 3, height: 3) + let sm = sizeSubMenu?.submenu + sm?.item(withTitle: "2x2")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "3x3")?.state = NSControl.StateValue(rawValue: 1) + sm?.item(withTitle: "4x4")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "5x5")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "6x6")?.state = NSControl.StateValue(rawValue: 0) + } - //MARK: - Changing range of digits - func setNumberOfDigits(num :Int) { - currentGameModel.currentNumberDigits = num - } + @IBAction func setBoardTo4x4(sender: Any?) { + setBoardSize(width: 4, height: 4) + let sm = sizeSubMenu?.submenu + sm?.item(withTitle: "2x2")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "3x3")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "4x4")?.state = NSControl.StateValue(rawValue: 1) + sm?.item(withTitle: "5x5")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "6x6")?.state = NSControl.StateValue(rawValue: 0) + } + + @IBAction func setBoardTo5x5(sender: Any?) { + setBoardSize(width: 5, height: 5) + let sm = sizeSubMenu?.submenu + sm?.item(withTitle: "2x2")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "3x3")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "4x4")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "5x5")?.state = NSControl.StateValue(rawValue: 1) + sm?.item(withTitle: "6x6")?.state = NSControl.StateValue(rawValue: 0) + } + + @IBAction func setBoardTo6x6(sender: Any?) { + setBoardSize(width: 6, height: 6) + let sm = sizeSubMenu?.submenu + sm?.item(withTitle: "2x2")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "3x3")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "4x4")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "5x5")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "6x6")?.state = NSControl.StateValue(rawValue: 1) + } + + + //MARK: - Changing range of digits + func setNumberOfDigits(num :Int) { + currentGameModel.currentNumberDigits = num + } - // MARK: Number of digits menu manipulation + // MARK: Number of digits menu manipulation var numberOfDigitsSubMenu: NSMenuItem? { return optionMenu?.submenu?.item(withTitle: "Digits") } - @IBAction func setNumberOfDigitsTo6(sender: Any?) { - setNumberOfDigits(num: 5) - let sm = numberOfDigitsSubMenu?.submenu - sm?.item(withTitle: "6")?.state = NSControl.StateValue(rawValue: 1) - sm?.item(withTitle: "7")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "8")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "9")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "10")?.state = NSControl.StateValue(rawValue: 0) - } - - @IBAction func setNumberOfDigitsTo7(sender: Any?) { - setNumberOfDigits(num: 6) - let sm = numberOfDigitsSubMenu?.submenu - sm?.item(withTitle: "6")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "7")?.state = NSControl.StateValue(rawValue: 1) - sm?.item(withTitle: "8")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "9")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "10")?.state = NSControl.StateValue(rawValue: 0) - } - - @IBAction func setNumberOfDigitsTo8(sender: Any?) { - setNumberOfDigits(num: 7) - let sm = numberOfDigitsSubMenu?.submenu - sm?.item(withTitle: "6")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "7")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "8")?.state = NSControl.StateValue(rawValue: 1) - sm?.item(withTitle: "9")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "10")?.state = NSControl.StateValue(rawValue: 0) - } + @IBAction func setNumberOfDigitsTo6(sender: Any?) { + setNumberOfDigits(num: 5) + let sm = numberOfDigitsSubMenu?.submenu + sm?.item(withTitle: "6")?.state = NSControl.StateValue(rawValue: 1) + sm?.item(withTitle: "7")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "8")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "9")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "10")?.state = NSControl.StateValue(rawValue: 0) + } - @IBAction func setNumberOfDigitsTo9(sender: Any?) { - setNumberOfDigits(num: 8) - let sm = numberOfDigitsSubMenu?.submenu - sm?.item(withTitle: "6")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "7")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "8")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "9")?.state = NSControl.StateValue(rawValue: 1) - sm?.item(withTitle: "10")?.state = NSControl.StateValue(rawValue: 0) - } - - @IBAction func setNumberOfDigitsTo10(sender: Any?) { - setNumberOfDigits(num: 9) - let sm = numberOfDigitsSubMenu?.submenu - sm?.item(withTitle: "6")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "7")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "8")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "9")?.state = NSControl.StateValue(rawValue: 0) - sm?.item(withTitle: "10")?.state = NSControl.StateValue(rawValue: 1) - } + @IBAction func setNumberOfDigitsTo7(sender: Any?) { + setNumberOfDigits(num: 6) + let sm = numberOfDigitsSubMenu?.submenu + sm?.item(withTitle: "6")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "7")?.state = NSControl.StateValue(rawValue: 1) + sm?.item(withTitle: "8")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "9")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "10")?.state = NSControl.StateValue(rawValue: 0) + } + + @IBAction func setNumberOfDigitsTo8(sender: Any?) { + setNumberOfDigits(num: 7) + let sm = numberOfDigitsSubMenu?.submenu + sm?.item(withTitle: "6")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "7")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "8")?.state = NSControl.StateValue(rawValue: 1) + sm?.item(withTitle: "9")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "10")?.state = NSControl.StateValue(rawValue: 0) + } + + @IBAction func setNumberOfDigitsTo9(sender: Any?) { + setNumberOfDigits(num: 8) + let sm = numberOfDigitsSubMenu?.submenu + sm?.item(withTitle: "6")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "7")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "8")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "9")?.state = NSControl.StateValue(rawValue: 1) + sm?.item(withTitle: "10")?.state = NSControl.StateValue(rawValue: 0) + } + + @IBAction func setNumberOfDigitsTo10(sender: Any?) { + setNumberOfDigits(num: 9) + let sm = numberOfDigitsSubMenu?.submenu + sm?.item(withTitle: "6")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "7")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "8")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "9")?.state = NSControl.StateValue(rawValue: 0) + sm?.item(withTitle: "10")?.state = NSControl.StateValue(rawValue: 1) + } @IBAction func setTextStyleToDigits(sender: Any?) { @@ -180,15 +176,15 @@ class AppDelegate: NSObject, NSApplicationDelegate { controller.setTextStyle(to: .greekSymbols) } - - //MARK: - Game actions - @IBAction func newGame(sender: Any?) { - let pg = TVPuzzleGenerator(width: currentGameModel.boardWidth, height: currentGameModel.boardHeight, rangeOfNumbers: 0...currentGameModel.currentNumberDigits) - currentGamePieces = pg.solvedBoard - let pv : TVGameViewController = NSApplication.shared.mainWindow?.contentViewController as! TVGameViewController - pv.solvedBoard = currentGamePieces - pv.newBoard(currentGameModel.boardWidth, height: currentGameModel.boardHeight) - - } - + + //MARK: - Game actions + @IBAction func newGame(sender: Any?) { + let pg = TVPuzzleGenerator(width: currentGameModel.boardWidth, height: currentGameModel.boardHeight, rangeOfNumbers: 0...currentGameModel.currentNumberDigits) + currentGamePieces = pg.solvedBoard + let pv : TVGameViewController = NSApplication.shared.mainWindow?.contentViewController as! TVGameViewController + pv.solvedBoard = currentGamePieces + pv.newBoard(currentGameModel.boardWidth, height: currentGameModel.boardHeight) + + } + } diff --git a/TetraVex/TVBoardView.swift b/TetraVex/TVBoardView.swift index de00325..0b614ff 100644 --- a/TetraVex/TVBoardView.swift +++ b/TetraVex/TVBoardView.swift @@ -12,110 +12,110 @@ import TetraVexKit @IBDesignable class TVBoardView: NSView { - - @IBInspectable var pieceWidth : CGFloat = 90 - @IBInspectable var pieceHeight : CGFloat = 90 - @IBInspectable var insetStrokeLineWidth : CGFloat = 3 - - var model : TVBoardModel? = nil { - didSet { - guard let model = model else { - return - } - prepareBoard(with: model) - } - } - - override func mouseUp(with event: NSEvent) { - - } - - override func draw(_ dirtyRect: NSRect) { - super.draw(dirtyRect) - - let path = NSBezierPath(roundedRect: self.bounds, xRadius: 3, yRadius: 3) - - NSColor.lightGray.setFill() - NSColor.black.setStroke() - - path.fill() - - - guard let model = model else { - return - } - - let pathSquare = NSRect( - x: 0, - y: 0, - width: pieceWidth, - height: pieceHeight) - - for i in 0.. Bool { - if boardModel!.removePieceFromBoard(p.pieceModel!) { - p.pieceModel!.isOnBoard = false - return true - } - return false - } - - func checkPiece(with pv:TVPieceView,at dropOffPosition:NSPoint) { - /* Determine the position of view inside the grid */ - if boardAreaBox.frame.contains(dropOffPosition) { - let i : Int = Int((dropOffPosition.x - boardAreaBox.frame.origin.x) / pv.frame.width) - let j : Int = Int((dropOffPosition.y - boardAreaBox.frame.origin.y) / pv.frame.height) - - if boardModel!.addPieceToBoard(pv.pieceModel!, x: i, y: j) { - pv.frame.origin.x = CGFloat(i)*pv.frame.width + boardAreaBox.frame.origin.x - pv.frame.origin.y = CGFloat(j)*pv.frame.height + boardAreaBox.frame.origin.y - pv.pieceModel?.isOnBoard = true - if boardModel!.isCompleted() { - timer?.invalidate() - timer = nil - - let size = "\(boardModel!.boardWidth)x\(boardModel!.boardHeight)" - scores?.scores?[size]?[NSDate()] = secondsPassed - scores?.save() - } - } else { - let randomSource = GKARC4RandomSource() - var newOrigin = templatePieceView.frame.origin - newOrigin.x += CGFloat(Int.random(0...100,randomSource: randomSource)) - newOrigin.y -= CGFloat(Int.random(0...100,randomSource: randomSource)) - pv.animator().setFrameOrigin(newOrigin) - } - } - } - - //MARK: - Timing - @objc func tick() { - secondsPassed += 1 - timerLabel.stringValue = TVHighScores.timeToString(secondsPassed) - } - + } + } + + } + + //start timing the game + if timer != nil { + timer?.invalidate() + timer = nil + } + secondsPassed = 0 + timer = Timer.scheduledTimer( + timeInterval: 1, + target: self, + selector: #selector(tick), + userInfo: nil, + repeats: true) + } + + func removeFromBoard(piece p:TVPieceView) -> Bool { + if boardModel!.removePieceFromBoard(p.pieceModel!) { + p.pieceModel!.isOnBoard = false + return true + } + return false + } + + func checkPiece(with pv:TVPieceView,at dropOffPosition:NSPoint) { + /* Determine the position of view inside the grid */ + if boardAreaBox.frame.contains(dropOffPosition) { + let i : Int = Int((dropOffPosition.x - boardAreaBox.frame.origin.x) / pv.frame.width) + let j : Int = Int((dropOffPosition.y - boardAreaBox.frame.origin.y) / pv.frame.height) + + if boardModel!.addPieceToBoard(pv.pieceModel!, x: i, y: j) { + pv.frame.origin.x = CGFloat(i)*pv.frame.width + boardAreaBox.frame.origin.x + pv.frame.origin.y = CGFloat(j)*pv.frame.height + boardAreaBox.frame.origin.y + pv.pieceModel?.isOnBoard = true + if boardModel!.isCompleted() { + timer?.invalidate() + timer = nil + + let size = "\(boardModel!.boardWidth)x\(boardModel!.boardHeight)" + scores?.scores?[size]?[NSDate()] = secondsPassed + scores?.save() + } + } else { + let randomSource = GKARC4RandomSource() + var newOrigin = templatePieceView.frame.origin + newOrigin.x += CGFloat(Int.random(0...100,randomSource: randomSource)) + newOrigin.y -= CGFloat(Int.random(0...100,randomSource: randomSource)) + pv.animator().setFrameOrigin(newOrigin) + } + } + } + + //MARK: - Timing + @objc func tick() { + secondsPassed += 1 + timerLabel.stringValue = TVHighScores.timeToString(secondsPassed) + } + // MARK: Changing the piece text style var visiblePieces : [TVPieceView]? - + func setTextStyle(to style:TVPieceModel.TextStyle) { guard let pieces = visiblePieces else { return } textStyle(for: pieces, style) } - + func textStyle(for pieces:[TVPieceView],_ style: TVPieceModel.TextStyle) { for piece in pieces { guard var model = piece.pieceModel else { continue } - model.textStyle = style - piece.pieceModel = model - piece.needsDisplay = true + model.textStyle = style + piece.pieceModel = model + piece.needsDisplay = true } } - + } extension TVGameViewController : TVPieceViewDelegate { - func wasLiftedFromBoard(piece: TVPieceView) { - removeFromBoard(piece: piece) - guard let layer = piece.layer else { - return - } - layer.shadowColor = NSColor.black.cgColor - layer.shadowOpacity = 0.75 - layer.shadowOffset = CGSize(width: 5,height: 10) - layer.shadowRadius = 10 + func wasLiftedFromBoard(piece: TVPieceView) { + removeFromBoard(piece: piece) + guard let layer = piece.layer else { + return } - - func wasDropped(piece: TVPieceView, at: NSPoint) { - checkPiece(with: piece, at: at) - guard let layer = piece.layer else { - return - } - layer.shadowOpacity = 0.0 + layer.shadowColor = NSColor.black.cgColor + layer.shadowOpacity = 0.75 + layer.shadowOffset = CGSize(width: 5,height: 10) + layer.shadowRadius = 10 + } + + func wasDropped(piece: TVPieceView, at: NSPoint) { + checkPiece(with: piece, at: at) + guard let layer = piece.layer else { + return } + layer.shadowOpacity = 0.0 + } } diff --git a/TetraVex/TVHighScoreViewController.swift b/TetraVex/TVHighScoreViewController.swift index 72f283c..87942c6 100644 --- a/TetraVex/TVHighScoreViewController.swift +++ b/TetraVex/TVHighScoreViewController.swift @@ -21,61 +21,61 @@ import Cocoa class TVHighScoreViewController : NSViewController, NSTableViewDelegate, NSTableViewDataSource { - - @IBOutlet weak var tableView: NSTableView! - - var selectedSize: String = "2x2" - var scores: TVHighScores? - var dates: [NSDate]? - - var dateFmt: DateFormatter? - - override func viewDidLoad() { - scores = TVHighScores.read() - reload() - - dateFmt = DateFormatter() - dateFmt?.timeZone = TimeZone.current - dateFmt?.dateFormat = "yyyy-MM-dd HH:mm:ss" - - tableView.delegate = self - tableView.dataSource = self - } - - func reload() { - dates = Array((scores?.scores![selectedSize]!.keys)!).sorted(by: { date1, date2 in - return scores!.scores![selectedSize]![date1]! < scores!.scores![selectedSize]![date2]! - }) - tableView.reloadData() - } - - func numberOfRows(in tableView: NSTableView) -> Int { - return dates!.count - } - - func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { - if (tableColumn?.identifier)!.rawValue == "Date-Time" { - return dateFmt?.string(from: dates![row] as Date) - } else { - return TVHighScores.timeToString(scores!.scores![selectedSize]![dates![row]]!) - } - } - - @IBAction func deleteScores(_ sender: Any) { - let alert = NSAlert() - alert.messageText = "Are you sure you want to delete your high scores? This cannot be undone." - alert.addButton(withTitle: "No") - alert.addButton(withTitle: "Yes") - if alert.runModal() == NSApplication.ModalResponse.alertSecondButtonReturn { - scores?.scores = TVHighScores.emptyScores - scores?.save() - reload() - } - } - - @IBAction func sizeChooser(_ sender: NSPopUpButton) { - selectedSize = sender.titleOfSelectedItem! - reload() - } - + + @IBOutlet weak var tableView: NSTableView! + + var selectedSize: String = "2x2" + var scores: TVHighScores? + var dates: [NSDate]? + + var dateFmt: DateFormatter? + + override func viewDidLoad() { + scores = TVHighScores.read() + reload() + + dateFmt = DateFormatter() + dateFmt?.timeZone = TimeZone.current + dateFmt?.dateFormat = "yyyy-MM-dd HH:mm:ss" + + tableView.delegate = self + tableView.dataSource = self + } + + func reload() { + dates = Array((scores?.scores![selectedSize]!.keys)!).sorted(by: { date1, date2 in + return scores!.scores![selectedSize]![date1]! < scores!.scores![selectedSize]![date2]! + }) + tableView.reloadData() + } + + func numberOfRows(in tableView: NSTableView) -> Int { + return dates!.count + } + + func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { + if (tableColumn?.identifier)!.rawValue == "Date-Time" { + return dateFmt?.string(from: dates![row] as Date) + } else { + return TVHighScores.timeToString(scores!.scores![selectedSize]![dates![row]]!) + } + } + + @IBAction func deleteScores(_ sender: Any) { + let alert = NSAlert() + alert.messageText = "Are you sure you want to delete your high scores? This cannot be undone." + alert.addButton(withTitle: "No") + alert.addButton(withTitle: "Yes") + if alert.runModal() == NSApplication.ModalResponse.alertSecondButtonReturn { + scores?.scores = TVHighScores.emptyScores + scores?.save() + reload() + } + } + + @IBAction func sizeChooser(_ sender: NSPopUpButton) { + selectedSize = sender.titleOfSelectedItem! + reload() + } + } diff --git a/TetraVex/TVHighScores.swift b/TetraVex/TVHighScores.swift index 6e61cd2..f7cb02c 100644 --- a/TetraVex/TVHighScores.swift +++ b/TetraVex/TVHighScores.swift @@ -21,49 +21,49 @@ import Foundation class TVHighScores: NSObject, NSCoding { - - static let emptyScores: [String: [NSDate : Int]] = [ - "2x2":[:], - "3x3":[:], - "4x4":[:], - "5x5":[:], - "6x6":[:] - ] - var scores: [String: [NSDate : Int]]? - - init(_ scores: [String: [NSDate : Int]]) { - self.scores = scores - } - - static func timeToString(_ secondsPassed: Int) -> String { - let minutes = secondsPassed / 60 - let seconds: String = "00\(secondsPassed - minutes * 60)" - return "\(minutes):\(seconds.suffix(2))" - } - - //MARK: - En/Decoding - func encode(with aCoder: NSCoder) { - aCoder.encode(scores) - } - - required convenience init?(coder aDecoder: NSCoder) { - guard let scores = aDecoder.decodeObject() as? [String: [NSDate : Int]] else { - return nil - } - self.init(scores) - } - - //MARK: - Saving/Reading - func save() { - let data = NSKeyedArchiver.archivedData(withRootObject: self) - UserDefaults.standard.set(data, forKey: "TVHighScore") - } - - static func read() -> TVHighScores { - guard let data = UserDefaults.standard.object(forKey: "TVHighScore") as? Data else { - return TVHighScores(TVHighScores.emptyScores) - } - return NSKeyedUnarchiver.unarchiveObject(with: data) as! TVHighScores - } - + + static let emptyScores: [String: [NSDate : Int]] = [ + "2x2":[:], + "3x3":[:], + "4x4":[:], + "5x5":[:], + "6x6":[:] + ] + var scores: [String: [NSDate : Int]]? + + init(_ scores: [String: [NSDate : Int]]) { + self.scores = scores + } + + static func timeToString(_ secondsPassed: Int) -> String { + let minutes = secondsPassed / 60 + let seconds: String = "00\(secondsPassed - minutes * 60)" + return "\(minutes):\(seconds.suffix(2))" + } + + //MARK: - En/Decoding + func encode(with aCoder: NSCoder) { + aCoder.encode(scores) + } + + required convenience init?(coder aDecoder: NSCoder) { + guard let scores = aDecoder.decodeObject() as? [String: [NSDate : Int]] else { + return nil + } + self.init(scores) + } + + //MARK: - Saving/Reading + func save() { + let data = NSKeyedArchiver.archivedData(withRootObject: self) + UserDefaults.standard.set(data, forKey: "TVHighScore") + } + + static func read() -> TVHighScores { + guard let data = UserDefaults.standard.object(forKey: "TVHighScore") as? Data else { + return TVHighScores(TVHighScores.emptyScores) + } + return NSKeyedUnarchiver.unarchiveObject(with: data) as! TVHighScores + } + } diff --git a/TetraVex/TVPieceView.swift b/TetraVex/TVPieceView.swift index c2e02fc..336f0a8 100644 --- a/TetraVex/TVPieceView.swift +++ b/TetraVex/TVPieceView.swift @@ -10,92 +10,92 @@ import Cocoa import TetraVexKit protocol TVPieceViewDelegate { - func wasLiftedFromBoard(piece:TVPieceView) - func wasDropped(piece:TVPieceView, at: NSPoint) + func wasLiftedFromBoard(piece:TVPieceView) + func wasDropped(piece:TVPieceView, at: NSPoint) } @IBDesignable class TVPieceView : NSView, NSAccessibilityButton { - var pieceModel :TVPieceModel? { - didSet{ - guard let model = pieceModel else { - return - } - bufferImage = drawTetraVex(with: model) - } - } - var isBeingDragged : Bool = false - var lastDraggedPosition : NSPoint = NSPoint() - - @IBInspectable var backgroundColor : NSColor = #colorLiteral(red: 0.7480000257, green: 0.7480000257, blue: 0.7480000257, alpha: 1) - @IBInspectable var roundedRectRadius : CGFloat = 0 - @IBInspectable var insetStrokeColor : NSColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) - @IBInspectable var insetStrokeLineWidth : CGFloat = 3 - @IBInspectable var insetOffset: CGFloat = 3 - @IBInspectable var insetShadowColor: NSColor = .gray - @IBInspectable var outerStrokeColor : NSColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) - - @IBInspectable var textColor : NSColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) - @IBInspectable var fontSize : CGFloat = 24 - - var bufferImage : NSImage? = nil - var delegate : TVPieceViewDelegate? = nil - - // MARK: - Dragging operations - override func mouseDown(with event: NSEvent) { - guard let pieceModel = pieceModel else { - return - } - - if pieceModel.boltedInPlace == false { - isBeingDragged = true - lastDraggedPosition = self.convert(event.locationInWindow, to: self) - let i = superview?.subviews.firstIndex(of: self) - var svs = superview!.subviews - if (i != svs.count-1) { - svs.swapAt(i!, svs.count-1) - superview?.subviews = svs - } - if pieceModel.isOnBoard { - delegate?.wasLiftedFromBoard(piece: self) - } - NSCursor.closedHand.push() - self.needsDisplay = true - } + var pieceModel :TVPieceModel? { + didSet{ + guard let model = pieceModel else { + return + } + bufferImage = drawTetraVex(with: model) } - - func offsetLocation(by dx:CGFloat, dy:CGFloat) { - self.setNeedsDisplay(self.bounds); - let invertDeltaY : CGFloat = self.isFlipped ? -1.0 : 1.0; - self.frame = self.frame.offsetBy(dx: dx, dy: dy*invertDeltaY) - self.setNeedsDisplay(self.bounds) + } + var isBeingDragged : Bool = false + var lastDraggedPosition : NSPoint = NSPoint() + + @IBInspectable var backgroundColor : NSColor = #colorLiteral(red: 0.7480000257, green: 0.7480000257, blue: 0.7480000257, alpha: 1) + @IBInspectable var roundedRectRadius : CGFloat = 0 + @IBInspectable var insetStrokeColor : NSColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) + @IBInspectable var insetStrokeLineWidth : CGFloat = 3 + @IBInspectable var insetOffset: CGFloat = 3 + @IBInspectable var insetShadowColor: NSColor = .gray + @IBInspectable var outerStrokeColor : NSColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) + + @IBInspectable var textColor : NSColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) + @IBInspectable var fontSize : CGFloat = 24 + + var bufferImage : NSImage? = nil + var delegate : TVPieceViewDelegate? = nil + + // MARK: - Dragging operations + override func mouseDown(with event: NSEvent) { + guard let pieceModel = pieceModel else { + return } - - override func mouseDragged(with event: NSEvent) { - if (isBeingDragged) { - let p : NSPoint = self.convert(event.locationInWindow, to: self) - offsetLocation(by: p.x - lastDraggedPosition.x, dy: p.y - lastDraggedPosition.y) - lastDraggedPosition = p - self.autoscroll(with: event) - } + + if pieceModel.boltedInPlace == false { + isBeingDragged = true + lastDraggedPosition = self.convert(event.locationInWindow, to: self) + let i = superview?.subviews.firstIndex(of: self) + var svs = superview!.subviews + if (i != svs.count-1) { + svs.swapAt(i!, svs.count-1) + superview?.subviews = svs + } + if pieceModel.isOnBoard { + delegate?.wasLiftedFromBoard(piece: self) + } + NSCursor.closedHand.push() + self.needsDisplay = true } - - override func mouseUp(with event: NSEvent) { - if isBeingDragged { - let dropPosition : NSPoint = superview!.convert(event.locationInWindow, to: nil) - delegate?.wasDropped(piece: self, at: dropPosition) - isBeingDragged = false - NSCursor.pop() - self.needsDisplay = true - } + } + + func offsetLocation(by dx:CGFloat, dy:CGFloat) { + self.setNeedsDisplay(self.bounds); + let invertDeltaY : CGFloat = self.isFlipped ? -1.0 : 1.0; + self.frame = self.frame.offsetBy(dx: dx, dy: dy*invertDeltaY) + self.setNeedsDisplay(self.bounds) + } + + override func mouseDragged(with event: NSEvent) { + if (isBeingDragged) { + let p : NSPoint = self.convert(event.locationInWindow, to: self) + offsetLocation(by: p.x - lastDraggedPosition.x, dy: p.y - lastDraggedPosition.y) + lastDraggedPosition = p + self.autoscroll(with: event) } - - // MARK: - Accessibility functions - override func accessibilityLabel() -> String? { - return "TetraVex Piece" + } + + override func mouseUp(with event: NSEvent) { + if isBeingDragged { + let dropPosition : NSPoint = superview!.convert(event.locationInWindow, to: nil) + delegate?.wasDropped(piece: self, at: dropPosition) + isBeingDragged = false + NSCursor.pop() + self.needsDisplay = true } - - // MARK: - Drawing operations + } + + // MARK: - Accessibility functions + override func accessibilityLabel() -> String? { + return "TetraVex Piece" + } + + // MARK: - Drawing operations var textStyle: TVPieceModel.TextStyle { return pieceModel?.textStyle ?? TVPieceModel.TextStyle.digits } @@ -107,29 +107,29 @@ class TVPieceView : NSView, NSAccessibilityButton { case .greekSymbols: let nstr: String switch (str) { - case "0": - nstr = "α" - case "1": - nstr = "β" - case "2": - nstr = "γ" - case "3": - nstr = "δ" - case "4": - nstr = "ϵ" - case "5": - nstr = "ζ" - case "6": - nstr = "η" - case "7": - nstr = "θ" - case "8": - nstr = "ι" - case "9": - nstr = "κ" - default: - nstr = "ω" - } + case "0": + nstr = "α" + case "1": + nstr = "β" + case "2": + nstr = "γ" + case "3": + nstr = "δ" + case "4": + nstr = "ϵ" + case "5": + nstr = "ζ" + case "6": + nstr = "η" + case "7": + nstr = "θ" + case "8": + nstr = "ι" + case "9": + nstr = "κ" + default: + nstr = "ω" + } return NSString(string: nstr) case .letters: let nstr: String @@ -160,140 +160,140 @@ class TVPieceView : NSView, NSAccessibilityButton { return NSString(string: nstr) } } - - func drawStringCenteredAt(_ center: NSPoint, str: String, attribs: [NSAttributedString.Key: Any]?) { - let nsstr = convert(str, with: textStyle) - let b = nsstr.boundingRect(with: NSSize(width: 300,height: 300), options: NSString.DrawingOptions.usesFontLeading, attributes: attribs) - var dCenter = center - dCenter.x = center.x - b.width/2 - dCenter.y = center.y - b.height/2 - nsstr.draw(at: dCenter, withAttributes: attribs) - } - - override func draw(_ dirtyRect: NSRect) { - guard let image = bufferImage else { - return - } - image.draw(in: self.bounds) - } - - func drawTetraVex(with pieceModel: TVPieceModel) -> NSImage { - let image = NSImage(size: self.bounds.size) - image.lockFocus() - let pathRect = self.bounds - let path = NSBezierPath(roundedRect: pathRect, - xRadius: roundedRectRadius, - yRadius: roundedRectRadius) - - // Background Fill - backgroundColor.setFill() - path.fill() - - // Inner X lines - NSGraphicsContext.saveGraphicsState() - - drawInsetLine(NSPoint(x:pathRect.origin.x, - y:pathRect.origin.y+insetOffset), - to: NSPoint(x: pathRect.maxX, - y: pathRect.maxY+insetOffset), - color: insetShadowColor, - lineWidth: insetStrokeLineWidth) - drawInsetLine(NSPoint(x:pathRect.minX - insetOffset, - y:pathRect.maxY), - to: NSPoint(x: pathRect.maxX - insetOffset, - y: pathRect.minY), - color: insetShadowColor, - lineWidth: insetStrokeLineWidth) - - drawInsetLine(pathRect.origin, - to: NSPoint(x: pathRect.maxX, y: pathRect.maxY), - color: insetStrokeColor, - lineWidth: insetStrokeLineWidth) - drawInsetLine(NSPoint(x:pathRect.minX,y:pathRect.maxY), - to: NSPoint(x: pathRect.maxX, y: pathRect.minY), - color: insetStrokeColor, - lineWidth: insetStrokeLineWidth) - - NSGraphicsContext.restoreGraphicsState() - - // Outer stroke - let outerStrokeLineWidth : CGFloat = 10 - let outerStrokeOffset : CGFloat = 2 - - drawInsetLine(NSPoint(x:pathRect.maxX - outerStrokeOffset, - y:pathRect.minY + outerStrokeOffset), - to: NSPoint(x: pathRect.maxX - outerStrokeOffset, - y: pathRect.maxY + outerStrokeOffset), - color: .gray, - lineWidth: outerStrokeLineWidth) - drawInsetLine(NSPoint(x:pathRect.minX + outerStrokeOffset, - y:pathRect.minY - outerStrokeOffset), - to: NSPoint(x: pathRect.minX + outerStrokeOffset, - y: pathRect.maxY + outerStrokeOffset), - color: .white, - lineWidth: outerStrokeLineWidth) - drawInsetLine(NSPoint(x:pathRect.minX - outerStrokeOffset, - y:pathRect.maxY - outerStrokeOffset), - to: NSPoint(x: pathRect.maxX + outerStrokeOffset, - y: pathRect.maxY - outerStrokeOffset), - color: .white, - lineWidth: outerStrokeLineWidth) - drawInsetLine(NSPoint(x:pathRect.minX + outerStrokeOffset, - y:pathRect.minY + outerStrokeOffset), - to: NSPoint(x: pathRect.maxX - outerStrokeOffset, - y: pathRect.minY + outerStrokeOffset), - color: .gray, - lineWidth: outerStrokeLineWidth) - - - path.lineWidth = 1 - outerStrokeColor.setStroke() - path.stroke() - - - // Text - let paragraphStyle : NSMutableParagraphStyle = NSMutableParagraphStyle() - paragraphStyle.alignment = NSTextAlignment.center - let font = NSFont(name: "Helvetica-Bold", size: fontSize*(pathRect.height/80.0)) ?? NSFont.systemFont(ofSize: fontSize*(pathRect.height/80.0)) - let shadow = NSShadow() - shadow.shadowColor = .white - shadow.shadowOffset = NSSize(width: -1, height: 1) - shadow.shadowBlurRadius = 1 - let attribs : [NSAttributedString.Key:Any] = - [NSAttributedString.Key.font:font, - NSAttributedString.Key.strokeColor: textColor, - NSAttributedString.Key.shadow: shadow, - NSAttributedString.Key.paragraphStyle: - paragraphStyle] - - - let ptop = NSPoint(x: pathRect.width*0.5 + pathRect.minX, - y: pathRect.height*0.80 + pathRect.minY) - let pbot = NSPoint(x: pathRect.width*0.5 + pathRect.minX, - y: pathRect.height*0.20 + pathRect.minY) - let pleft = NSPoint(x: pathRect.width*0.20 + pathRect.minX, - y: pathRect.height*0.5 + pathRect.minY) - let pright = NSPoint(x: pathRect.width*0.80 + pathRect.minX, - y: pathRect.height*0.5 + pathRect.minY) - - drawStringCenteredAt(ptop, str: "\(pieceModel.topValue)", attribs: attribs) - drawStringCenteredAt(pbot, str: "\(pieceModel.bottomValue)", attribs: attribs) - drawStringCenteredAt(pleft, str: "\(pieceModel.leftValue)", attribs: attribs) - drawStringCenteredAt(pright, str: "\(pieceModel.rightValue)", attribs: attribs) - image.unlockFocus() - return image - } - - func drawInsetLine(_ from: CGPoint, to: CGPoint, color: NSColor, lineWidth:CGFloat = 1) { - let innerPath = NSBezierPath() - innerPath.lineWidth = insetStrokeLineWidth - innerPath.move(to: from) - innerPath.line(to: to) - color.setStroke() - innerPath.stroke() - } - - override func prepareForInterfaceBuilder() { - pieceModel = TVPieceModel(top: 1, left: 2, bottom: 3, right: 4) + + func drawStringCenteredAt(_ center: NSPoint, str: String, attribs: [NSAttributedString.Key: Any]?) { + let nsstr = convert(str, with: textStyle) + let b = nsstr.boundingRect(with: NSSize(width: 300,height: 300), options: NSString.DrawingOptions.usesFontLeading, attributes: attribs) + var dCenter = center + dCenter.x = center.x - b.width/2 + dCenter.y = center.y - b.height/2 + nsstr.draw(at: dCenter, withAttributes: attribs) + } + + override func draw(_ dirtyRect: NSRect) { + guard let image = bufferImage else { + return } + image.draw(in: self.bounds) + } + + func drawTetraVex(with pieceModel: TVPieceModel) -> NSImage { + let image = NSImage(size: self.bounds.size) + image.lockFocus() + let pathRect = self.bounds + let path = NSBezierPath(roundedRect: pathRect, + xRadius: roundedRectRadius, + yRadius: roundedRectRadius) + + // Background Fill + backgroundColor.setFill() + path.fill() + + // Inner X lines + NSGraphicsContext.saveGraphicsState() + + drawInsetLine(NSPoint(x:pathRect.origin.x, + y:pathRect.origin.y+insetOffset), + to: NSPoint(x: pathRect.maxX, + y: pathRect.maxY+insetOffset), + color: insetShadowColor, + lineWidth: insetStrokeLineWidth) + drawInsetLine(NSPoint(x:pathRect.minX - insetOffset, + y:pathRect.maxY), + to: NSPoint(x: pathRect.maxX - insetOffset, + y: pathRect.minY), + color: insetShadowColor, + lineWidth: insetStrokeLineWidth) + + drawInsetLine(pathRect.origin, + to: NSPoint(x: pathRect.maxX, y: pathRect.maxY), + color: insetStrokeColor, + lineWidth: insetStrokeLineWidth) + drawInsetLine(NSPoint(x:pathRect.minX,y:pathRect.maxY), + to: NSPoint(x: pathRect.maxX, y: pathRect.minY), + color: insetStrokeColor, + lineWidth: insetStrokeLineWidth) + + NSGraphicsContext.restoreGraphicsState() + + // Outer stroke + let outerStrokeLineWidth : CGFloat = 10 + let outerStrokeOffset : CGFloat = 2 + + drawInsetLine(NSPoint(x:pathRect.maxX - outerStrokeOffset, + y:pathRect.minY + outerStrokeOffset), + to: NSPoint(x: pathRect.maxX - outerStrokeOffset, + y: pathRect.maxY + outerStrokeOffset), + color: .gray, + lineWidth: outerStrokeLineWidth) + drawInsetLine(NSPoint(x:pathRect.minX + outerStrokeOffset, + y:pathRect.minY - outerStrokeOffset), + to: NSPoint(x: pathRect.minX + outerStrokeOffset, + y: pathRect.maxY + outerStrokeOffset), + color: .white, + lineWidth: outerStrokeLineWidth) + drawInsetLine(NSPoint(x:pathRect.minX - outerStrokeOffset, + y:pathRect.maxY - outerStrokeOffset), + to: NSPoint(x: pathRect.maxX + outerStrokeOffset, + y: pathRect.maxY - outerStrokeOffset), + color: .white, + lineWidth: outerStrokeLineWidth) + drawInsetLine(NSPoint(x:pathRect.minX + outerStrokeOffset, + y:pathRect.minY + outerStrokeOffset), + to: NSPoint(x: pathRect.maxX - outerStrokeOffset, + y: pathRect.minY + outerStrokeOffset), + color: .gray, + lineWidth: outerStrokeLineWidth) + + + path.lineWidth = 1 + outerStrokeColor.setStroke() + path.stroke() + + + // Text + let paragraphStyle : NSMutableParagraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = NSTextAlignment.center + let font = NSFont(name: "Helvetica-Bold", size: fontSize*(pathRect.height/80.0)) ?? NSFont.systemFont(ofSize: fontSize*(pathRect.height/80.0)) + let shadow = NSShadow() + shadow.shadowColor = .white + shadow.shadowOffset = NSSize(width: -1, height: 1) + shadow.shadowBlurRadius = 1 + let attribs : [NSAttributedString.Key:Any] = + [NSAttributedString.Key.font:font, + NSAttributedString.Key.strokeColor: textColor, + NSAttributedString.Key.shadow: shadow, + NSAttributedString.Key.paragraphStyle: + paragraphStyle] + + + let ptop = NSPoint(x: pathRect.width*0.5 + pathRect.minX, + y: pathRect.height*0.80 + pathRect.minY) + let pbot = NSPoint(x: pathRect.width*0.5 + pathRect.minX, + y: pathRect.height*0.20 + pathRect.minY) + let pleft = NSPoint(x: pathRect.width*0.20 + pathRect.minX, + y: pathRect.height*0.5 + pathRect.minY) + let pright = NSPoint(x: pathRect.width*0.80 + pathRect.minX, + y: pathRect.height*0.5 + pathRect.minY) + + drawStringCenteredAt(ptop, str: "\(pieceModel.topValue)", attribs: attribs) + drawStringCenteredAt(pbot, str: "\(pieceModel.bottomValue)", attribs: attribs) + drawStringCenteredAt(pleft, str: "\(pieceModel.leftValue)", attribs: attribs) + drawStringCenteredAt(pright, str: "\(pieceModel.rightValue)", attribs: attribs) + image.unlockFocus() + return image + } + + func drawInsetLine(_ from: CGPoint, to: CGPoint, color: NSColor, lineWidth:CGFloat = 1) { + let innerPath = NSBezierPath() + innerPath.lineWidth = insetStrokeLineWidth + innerPath.move(to: from) + innerPath.line(to: to) + color.setStroke() + innerPath.stroke() + } + + override func prepareForInterfaceBuilder() { + pieceModel = TVPieceModel(top: 1, left: 2, bottom: 3, right: 4) + } } diff --git a/TetraVexKit/TVBoardModel.swift b/TetraVexKit/TVBoardModel.swift index 5d5b053..bf5acc1 100644 --- a/TetraVexKit/TVBoardModel.swift +++ b/TetraVexKit/TVBoardModel.swift @@ -9,90 +9,90 @@ import Foundation open class TVBoardModel { + + // MARK: - Properties + open var board : [[TVPieceModel?]] + open var boardWidth : Int = 0 + open var boardHeight : Int = 0 + open var startedPlaying : Bool = false + + // MARK: - Initializer + public init(width: Int, height: Int) { + board = Array(repeating: Array(repeating: nil, count: height), count: width) + boardWidth = width + boardHeight = height + } + + // MARK: - Board manipulation + open func addPieceToBoard(_ piece: TVPieceModel, x: Int, y: Int) -> Bool { + if !(0 ... board.count-1 ~= x && 0 ... board[0].count-1 ~= y) { + return false + } - // MARK: - Properties - open var board : [[TVPieceModel?]] - open var boardWidth : Int = 0 - open var boardHeight : Int = 0 - open var startedPlaying : Bool = false + if board[x][y] != nil { + return false + } - // MARK: - Initializer - public init(width: Int, height: Int) { - board = Array(repeating: Array(repeating: nil, count: height), count: width) - boardWidth = width - boardHeight = height + var leftP :TVPieceModel? = nil + if 0 ... board.count-1 ~= x-1 && 0 ... board[0].count-1 ~= y { + leftP = board[x-1][y] + } + var rightP :TVPieceModel? = nil + if 0 ... board.count-1 ~= x+1 && 0 ... board[0].count-1 ~= y { + rightP = board[x+1][y] + } + var topP :TVPieceModel? = nil + if 0 ... board.count-1 ~= x && 0 ... board[0].count-1 ~= y+1 { + topP = board[x][y+1] + } + var botP :TVPieceModel? = nil + if 0 ... board.count-1 ~= x && 0 ... board[0].count-1 ~= y-1 { + botP = board[x][y-1] } - // MARK: - Board manipulation - open func addPieceToBoard(_ piece: TVPieceModel, x: Int, y: Int) -> Bool { - if !(0 ... board.count-1 ~= x && 0 ... board[0].count-1 ~= y) { - return false - } - - if board[x][y] != nil { - return false - } - - var leftP :TVPieceModel? = nil - if 0 ... board.count-1 ~= x-1 && 0 ... board[0].count-1 ~= y { - leftP = board[x-1][y] - } - var rightP :TVPieceModel? = nil - if 0 ... board.count-1 ~= x+1 && 0 ... board[0].count-1 ~= y { - rightP = board[x+1][y] - } - var topP :TVPieceModel? = nil - if 0 ... board.count-1 ~= x && 0 ... board[0].count-1 ~= y+1 { - topP = board[x][y+1] - } - var botP :TVPieceModel? = nil - if 0 ... board.count-1 ~= x && 0 ... board[0].count-1 ~= y-1 { - botP = board[x][y-1] - } - - if leftP != nil && leftP?.rightValue != piece.leftValue { - return false - } - - if rightP != nil && rightP?.leftValue != piece.rightValue { - return false - } - - if topP != nil && topP?.bottomValue != piece.topValue { - return false - } - - if botP != nil && botP?.topValue != piece.bottomValue { - return false - } - - board[x][y] = piece - if startedPlaying == false { - startedPlaying = true - } - return true + if leftP != nil && leftP?.rightValue != piece.leftValue { + return false } - open func removePieceFromBoard(_ piece:TVPieceModel) -> Bool { - for i in 0.. Bool { - for a in board { - for p in a { - if p == nil { - return false - } - } + board[x][y] = piece + if startedPlaying == false { + startedPlaying = true + } + return true + } + + open func removePieceFromBoard(_ piece:TVPieceModel) -> Bool { + for i in 0.. Bool { + for a in board { + for p in a { + if p == nil { + return false } - return true + } } + return true + } } diff --git a/TetraVexKit/TVGameModel.swift b/TetraVexKit/TVGameModel.swift index 3696ef5..1f08927 100644 --- a/TetraVexKit/TVGameModel.swift +++ b/TetraVexKit/TVGameModel.swift @@ -9,13 +9,13 @@ import Foundation public struct TVGameModel { - public var currentTime = 0 - public var score = 0 - public var currentNumberDigits = 10 - public var boardWidth = 2 - public var boardHeight = 2 + public var currentTime = 0 + public var score = 0 + public var currentNumberDigits = 10 + public var boardWidth = 2 + public var boardHeight = 2 + + public init() { - public init() { - - } + } } diff --git a/TetraVexKit/TVPieceModel.swift b/TetraVexKit/TVPieceModel.swift index 966f5ba..fdee07c 100644 --- a/TetraVexKit/TVPieceModel.swift +++ b/TetraVexKit/TVPieceModel.swift @@ -9,41 +9,41 @@ import Foundation public struct TVPieceModel { - - // MARK: - Properties - public var topValue : Int - public var leftValue : Int - public var bottomValue : Int - public var rightValue : Int - public var boltedInPlace : Bool = false - public var isOnBoard : Bool = false - + + // MARK: - Properties + public var topValue : Int + public var leftValue : Int + public var bottomValue : Int + public var rightValue : Int + public var boltedInPlace : Bool = false + public var isOnBoard : Bool = false + // MARK: - Setting a different piece letter style public enum TextStyle { case digits case letters case greekSymbols } - + public var textStyle : TextStyle = .digits - - // MARK: - Initializer - public init(top: Int, left: Int, bottom: Int, right: Int) { - topValue = top - leftValue = left - bottomValue = bottom - rightValue = right - } - - // MARK: - Operations - public static func == (left: TVPieceModel, right: TVPieceModel) -> Bool { - return (left.topValue == right.topValue) && - (left.bottomValue == right.bottomValue) && - (left.leftValue == right.leftValue) && - (left.rightValue == right.rightValue) - } - - public static func != (left: TVPieceModel, right: TVPieceModel) -> Bool { - return !(left == right) - } + + // MARK: - Initializer + public init(top: Int, left: Int, bottom: Int, right: Int) { + topValue = top + leftValue = left + bottomValue = bottom + rightValue = right + } + + // MARK: - Operations + public static func == (left: TVPieceModel, right: TVPieceModel) -> Bool { + return (left.topValue == right.topValue) && + (left.bottomValue == right.bottomValue) && + (left.leftValue == right.leftValue) && + (left.rightValue == right.rightValue) + } + + public static func != (left: TVPieceModel, right: TVPieceModel) -> Bool { + return !(left == right) + } } diff --git a/TetraVexKit/TVPuzzleGenerator.swift b/TetraVexKit/TVPuzzleGenerator.swift index c8a6c28..f66afeb 100644 --- a/TetraVexKit/TVPuzzleGenerator.swift +++ b/TetraVexKit/TVPuzzleGenerator.swift @@ -11,53 +11,53 @@ import GameplayKit extension Int { - public static func random(_ range: ClosedRange, randomSource: GKRandomSource ) -> Int + public static func random(_ range: ClosedRange, randomSource: GKRandomSource ) -> Int + { + var offset = 0 + + if range.lowerBound < 0 // allow negative ranges { - var offset = 0 - - if range.lowerBound < 0 // allow negative ranges - { - offset = abs(range.lowerBound) - } - - let mini = Int(range.lowerBound + offset) - let maxi = Int(range.upperBound + offset) - - return Int(mini + randomSource.nextInt(upperBound: maxi - mini)) - offset + offset = abs(range.lowerBound) } + + let mini = Int(range.lowerBound + offset) + let maxi = Int(range.upperBound + offset) + + return Int(mini + randomSource.nextInt(upperBound: maxi - mini)) - offset + } } open class TVPuzzleGenerator { - - // MARK: - Properties - open var solvedBoard : [[TVPieceModel]] - var source : GKARC4RandomSource - - // MARK: - Initialization - public init(width: Int, height: Int, rangeOfNumbers: ClosedRange, seed: Data? = nil){ - if seed == nil { - self.source = GKARC4RandomSource() + + // MARK: - Properties + open var solvedBoard : [[TVPieceModel]] + var source : GKARC4RandomSource + + // MARK: - Initialization + public init(width: Int, height: Int, rangeOfNumbers: ClosedRange, seed: Data? = nil){ + if seed == nil { + self.source = GKARC4RandomSource() + } else { + self.source = GKARC4RandomSource(seed: seed!) + } + solvedBoard = Array(repeating: Array(repeating:TVPieceModel(top: 0, left: 0, bottom: 0, right: 0), count: width), count: width) + for i in 0.. 0 { + p.bottomValue = (solvedBoard[i][j-1]).topValue } else { - self.source = GKARC4RandomSource(seed: seed!) + p.bottomValue = Int.random(rangeOfNumbers, randomSource: source) } - solvedBoard = Array(repeating: Array(repeating:TVPieceModel(top: 0, left: 0, bottom: 0, right: 0), count: width), count: width) - for i in 0.. 0 { - p.bottomValue = (solvedBoard[i][j-1]).topValue - } else { - p.bottomValue = Int.random(rangeOfNumbers, randomSource: source) - } - if i > 0 { - p.leftValue = (solvedBoard[i-1][j]).rightValue - } else { - p.leftValue = Int.random(rangeOfNumbers, randomSource: source) - } - p.rightValue = Int.random(rangeOfNumbers, randomSource: source) - p.topValue = Int.random(rangeOfNumbers, randomSource: source) - solvedBoard[i][j] = p - } + if i > 0 { + p.leftValue = (solvedBoard[i-1][j]).rightValue + } else { + p.leftValue = Int.random(rangeOfNumbers, randomSource: source) } + p.rightValue = Int.random(rangeOfNumbers, randomSource: source) + p.topValue = Int.random(rangeOfNumbers, randomSource: source) + solvedBoard[i][j] = p + } } + } } diff --git a/TetraVexKitTests/TetraVexKitTests.swift b/TetraVexKitTests/TetraVexKitTests.swift index 345e090..a4baf98 100644 --- a/TetraVexKitTests/TetraVexKitTests.swift +++ b/TetraVexKitTests/TetraVexKitTests.swift @@ -10,101 +10,101 @@ import XCTest @testable import TetraVexKit class TetraVexKitTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testPuzzleGeneration() { + let puzzle = TVPuzzleGenerator(width: 2, height: 2, rangeOfNumbers: 0...9) + let board = TVBoardModel(width: 2, height: 2) - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } + let a = puzzle.solvedBoard[0][0] + let b = puzzle.solvedBoard[0][1] + let c = puzzle.solvedBoard[1][0] + let d = puzzle.solvedBoard[1][1] - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } + XCTAssert(board.startedPlaying == false) + XCTAssert(a != b && a != c && a != d && b != c && b != d && c != d) - func testPuzzleGeneration() { - let puzzle = TVPuzzleGenerator(width: 2, height: 2, rangeOfNumbers: 0...9) - let board = TVBoardModel(width: 2, height: 2) - - let a = puzzle.solvedBoard[0][0] - let b = puzzle.solvedBoard[0][1] - let c = puzzle.solvedBoard[1][0] - let d = puzzle.solvedBoard[1][1] - - XCTAssert(board.startedPlaying == false) - XCTAssert(a != b && a != c && a != d && b != c && b != d && c != d) - - var r = board.addPieceToBoard(a, x: 0, y: 0) - XCTAssert(r == true) - r = board.addPieceToBoard(b, x: 0, y: 1) - XCTAssert(r == true) - r = board.addPieceToBoard(c, x: 1, y: 0) - XCTAssert(r == true) - r = board.addPieceToBoard(d, x: 1, y: 1) - XCTAssert(r == true) - XCTAssert(board.isCompleted()) - XCTAssert(board.startedPlaying == true) - } + var r = board.addPieceToBoard(a, x: 0, y: 0) + XCTAssert(r == true) + r = board.addPieceToBoard(b, x: 0, y: 1) + XCTAssert(r == true) + r = board.addPieceToBoard(c, x: 1, y: 0) + XCTAssert(r == true) + r = board.addPieceToBoard(d, x: 1, y: 1) + XCTAssert(r == true) + XCTAssert(board.isCompleted()) + XCTAssert(board.startedPlaying == true) + } + + func testPiecePlacement() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. - func testPiecePlacement() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - - /* A | B - C | D */ - - let a = TVPieceModel(top: 1, left: 2, bottom: 3, right: 4) - let b = TVPieceModel(top: 3, left: 4, bottom: 2, right: 1) - let c = TVPieceModel(top: 3, left: 1, bottom: 4, right: 2) - let d = TVPieceModel(top: 2, left: 2, bottom: 1, right: 1) - - - let board = TVBoardModel(width: 2, height: 2) - var r = board.removePieceFromBoard(a) - XCTAssert(r == false) - r = board.addPieceToBoard(a, x: 1, y: 1) - XCTAssert(r == true) - r = board.removePieceFromBoard(a) - XCTAssert(r == true) - r = board.addPieceToBoard(a, x: 0, y: 1) - XCTAssert(r == true) - r = board.addPieceToBoard(b, x: 0, y: 1) - XCTAssert(r == false) - r = board.addPieceToBoard(b, x: 1, y: 1) - XCTAssert(r == true) - r = board.addPieceToBoard(c, x: 1, y: 0) - XCTAssert(r == false) - r = board.addPieceToBoard(d, x: 1, y: 0) - XCTAssert(r == true) - XCTAssert(!board.isCompleted()) - r = board.addPieceToBoard(c, x: 0, y: 0) - XCTAssert(r == true) - - XCTAssert(board.isCompleted()) - } + /* A | B + C | D */ + + let a = TVPieceModel(top: 1, left: 2, bottom: 3, right: 4) + let b = TVPieceModel(top: 3, left: 4, bottom: 2, right: 1) + let c = TVPieceModel(top: 3, left: 1, bottom: 4, right: 2) + let d = TVPieceModel(top: 2, left: 2, bottom: 1, right: 1) - func testPerformanceExample() { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - func testBoardSeed() { - let seed = Data(repeating: 1, count: 1) - let puzzle = TVPuzzleGenerator(width: 2, height: 2, rangeOfNumbers: 0...9, seed: seed) - - /* Check consistency */ - let expectedA = TVPieceModel(top: 6, left: 3, bottom: 6, right: 1) - let a = puzzle.solvedBoard[0][0] - XCTAssert(expectedA == a) - let expectedB = TVPieceModel(top: 5, left: 3, bottom: 6, right: 1) - let b = puzzle.solvedBoard[0][1] - XCTAssert(expectedB == b) - let expectedC = TVPieceModel(top: 1, left: 1, bottom: 8, right: 0) - let c = puzzle.solvedBoard[1][0] - XCTAssert(expectedC == c) - let expectedD = TVPieceModel(top: 3, left: 1, bottom: 1, right: 8) - let d = puzzle.solvedBoard[1][1] - XCTAssert(expectedD == d) + let board = TVBoardModel(width: 2, height: 2) + var r = board.removePieceFromBoard(a) + XCTAssert(r == false) + r = board.addPieceToBoard(a, x: 1, y: 1) + XCTAssert(r == true) + r = board.removePieceFromBoard(a) + XCTAssert(r == true) + r = board.addPieceToBoard(a, x: 0, y: 1) + XCTAssert(r == true) + r = board.addPieceToBoard(b, x: 0, y: 1) + XCTAssert(r == false) + r = board.addPieceToBoard(b, x: 1, y: 1) + XCTAssert(r == true) + r = board.addPieceToBoard(c, x: 1, y: 0) + XCTAssert(r == false) + r = board.addPieceToBoard(d, x: 1, y: 0) + XCTAssert(r == true) + XCTAssert(!board.isCompleted()) + r = board.addPieceToBoard(c, x: 0, y: 0) + XCTAssert(r == true) + + XCTAssert(board.isCompleted()) + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. } + } + + func testBoardSeed() { + let seed = Data(repeating: 1, count: 1) + let puzzle = TVPuzzleGenerator(width: 2, height: 2, rangeOfNumbers: 0...9, seed: seed) + + /* Check consistency */ + let expectedA = TVPieceModel(top: 6, left: 3, bottom: 6, right: 1) + let a = puzzle.solvedBoard[0][0] + XCTAssert(expectedA == a) + let expectedB = TVPieceModel(top: 5, left: 3, bottom: 6, right: 1) + let b = puzzle.solvedBoard[0][1] + XCTAssert(expectedB == b) + let expectedC = TVPieceModel(top: 1, left: 1, bottom: 8, right: 0) + let c = puzzle.solvedBoard[1][0] + XCTAssert(expectedC == c) + let expectedD = TVPieceModel(top: 3, left: 1, bottom: 1, right: 8) + let d = puzzle.solvedBoard[1][1] + XCTAssert(expectedD == d) + } } diff --git a/TetraVexUITests/TetraVexUITests.swift b/TetraVexUITests/TetraVexUITests.swift index 25fc513..d96bdba 100644 --- a/TetraVexUITests/TetraVexUITests.swift +++ b/TetraVexUITests/TetraVexUITests.swift @@ -9,43 +9,43 @@ import XCTest class TetraVexUITests: XCTestCase { - - override func setUp() { - super.setUp() - - // Put setup code here. This method is called before the invocation of each test method in the class. - - // In UI tests it is usually best to stop immediately when a failure occurs. - continueAfterFailure = false - // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. - if #available(OSX 10.11, *) { - XCUIApplication().launch() - } else { - // Fallback on earlier versions - } - - // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. - } + + override func setUp() { + super.setUp() + + // Put setup code here. This method is called before the invocation of each test method in the class. - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. + if #available(OSX 10.11, *) { + XCUIApplication().launch() + } else { + // Fallback on earlier versions } - /// Tests the proper creation of a board from scratch - func testNewGame() { - - let app = XCUIApplication() - let menuBarsQuery = app.menuBars - menuBarsQuery.menuBarItems["Game"].click() - menuBarsQuery/*@START_MENU_TOKEN@*/.menuItems["New Game"]/*[[".menuBarItems[\"Game\"]",".menus.menuItems[\"New Game\"]",".menuItems[\"New Game\"]"],[[[-1,2],[-1,1],[-1,0,1]],[[-1,2],[-1,1]]],[0]]@END_MENU_TOKEN@*/.click() - - let tetravexWindow = app.windows["TetraVex"] - let tetravexPieceButton = tetravexWindow.children(matching: .button).matching(identifier: "TetraVex Piece").element(boundBy: 0) - tetravexPieceButton.click() - tetravexWindow.children(matching: .button).matching(identifier: "TetraVex Piece").element(boundBy: 2).click() - tetravexWindow.children(matching: .button).matching(identifier: "TetraVex Piece").element(boundBy: 1).click() - tetravexWindow.children(matching: .button).matching(identifier: "TetraVex Piece").element(boundBy: 3).click() + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + /// Tests the proper creation of a board from scratch + func testNewGame() { - } + let app = XCUIApplication() + let menuBarsQuery = app.menuBars + menuBarsQuery.menuBarItems["Game"].click() + menuBarsQuery/*@START_MENU_TOKEN@*/.menuItems["New Game"]/*[[".menuBarItems[\"Game\"]",".menus.menuItems[\"New Game\"]",".menuItems[\"New Game\"]"],[[[-1,2],[-1,1],[-1,0,1]],[[-1,2],[-1,1]]],[0]]@END_MENU_TOKEN@*/.click() + + let tetravexWindow = app.windows["TetraVex"] + let tetravexPieceButton = tetravexWindow.children(matching: .button).matching(identifier: "TetraVex Piece").element(boundBy: 0) + tetravexPieceButton.click() + tetravexWindow.children(matching: .button).matching(identifier: "TetraVex Piece").element(boundBy: 2).click() + tetravexWindow.children(matching: .button).matching(identifier: "TetraVex Piece").element(boundBy: 1).click() + tetravexWindow.children(matching: .button).matching(identifier: "TetraVex Piece").element(boundBy: 3).click() + + } }