Hello I am a Java programmer trying to learn the ways of Swift. I coded a hangman game in Xcode. I was wondering what I could improve, specifically whether I used delegation correctly and if there is anything I can do more elegantly in Swift.
Here is my code:
Title View Controller
import UIKit
@IBDesignable
class TitleViewController: UIViewController {
//Mark - Properties
@IBOutlet weak var hangmanTitleLabel: UILabel! {
didSet {
let hangmanTitle: NSString = "Hangman"
let attributes = [NSFontAttributeName: UIFont(name: "MarkerFelt-Thin", size: 48.0)!, NSForegroundColorAttributeName: UIColor.redColor()]
let titleString = NSAttributedString(string: hangmanTitle as String, attributes: attributes)
hangmanTitleLabel.attributedText = titleString
}
}
//MARK - Lifecycle functions
override func shouldAutorotate() -> Bool {
return false
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.Portrait
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let destination = segue.destinationViewController as? GameViewController {
if let identifier = segue.identifier {
switch identifier {
case "Easy": destination.brain.level = "Easy"
case "Medium": destination.brain.level = "Medium"
case "Hard": destination.brain.level = "Hard"
default: break
}
}
}
}
}
TitleView
import UIKit
//MARK - Global Functions
func connectPoints(bottomLeftPoint: CGPoint, bottomRightPoint: CGPoint, topLeftPoint: CGPoint, topRightPoint: CGPoint, color: UIColor) {
color.set()
let path = UIBezierPath()
path.moveToPoint(bottomLeftPoint)
path.addLineToPoint(topLeftPoint)
path.addLineToPoint(topRightPoint)
path.addLineToPoint(bottomRightPoint)
path.closePath()
path.fill()
path.stroke()
}
func calculateMidPoint(point1: CGPoint, point2: CGPoint) -> CGPoint {
return CGPoint(x: (point1.x + point2.x) / 2, y: (point1.y + point2.y) / 2)
}
class TitleView: UIView {
//MARK - Drawing Scales and Constants
struct DrawingConstants {
static let gallowBaseStartScale: CGFloat = 0.15
static let gallowBaseEndScale: CGFloat = 0.85
static let gallowBaseHeight: CGFloat = 10
static let gallowHeight: CGFloat = 0.15
static let gallowHeightStart: CGFloat = 0.175
static let gallowHeightWidth: CGFloat = 10
static let gallowAcrossScale: CGFloat = 0.5
static let gallowTipHeight: CGFloat = 17.5
static let headRadius: CGFloat = 16
static let bodyLength: CGFloat = 25
static let bodyHeight: CGFloat = 25
static let legLength: CGFloat = 50
static let grassHeightScale: CGFloat = 0.68
static let armBack: CGFloat = 5
}
//MARK - Drawing Functions
override func drawRect(rect: CGRect) {
drawGrass()
drawSky()
drawGallow()
drawDude()
}
func drawGrass() {
let topStartPoint = CGPoint(x: CGFloat(0), y: CGFloat(bounds.size.height * DrawingConstants.grassHeightScale))
let topRightPoint = CGPoint(x: CGFloat(bounds.size.width), y: topStartPoint.y)
let bottomRightPoint = CGPoint(x: topRightPoint.x, y: CGFloat(bounds.size.height))
let bottomLeftPoint = CGPoint(x: CGFloat(0), y: bottomRightPoint.y)
connectPoints(bottomLeftPoint, bottomRightPoint: bottomRightPoint, topLeftPoint: topStartPoint, topRightPoint: topRightPoint, color: UIColor.greenColor())
}
func drawSky() {
let bottomLeftPoint = CGPoint(x: CGFloat(0), y: CGFloat(bounds.size.height * DrawingConstants.grassHeightScale))
let topLeftPoint = CGPoint(x: CGFloat(0), y: CGFloat(0))
let topRightPoint = CGPoint(x: CGFloat(bounds.size.width), y: CGFloat(0))
let bottomRightPoint = CGPoint(x: CGFloat(bounds.size.width), y: CGFloat(bounds.size.height * DrawingConstants.grassHeightScale))
connectPoints(bottomLeftPoint, bottomRightPoint: bottomRightPoint, topLeftPoint: topLeftPoint, topRightPoint: topRightPoint, color: UIColor.cyanColor())
}
func drawGallow() {
drawGallowBase()
drawGallowHeight()
drawGallowAcross()
drawGallowTip()
}
func drawGallowBase() {
let bottomLeftPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowBaseStartScale), y: CGFloat(bounds.size.height * DrawingConstants.grassHeightScale))
let topLeftPoint = CGPoint(x: bottomLeftPoint.x, y: bottomLeftPoint.y - DrawingConstants.gallowBaseHeight)
let topRightPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowBaseEndScale), y: topLeftPoint.y)
let bottomRightPoint = CGPoint(x: topRightPoint.x, y: bottomLeftPoint.y)
connectPoints(bottomLeftPoint, bottomRightPoint: bottomRightPoint, topLeftPoint: topLeftPoint, topRightPoint: topRightPoint, color: UIColor.brownColor())
}
func drawGallowHeight() {
let bottomLeftPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowHeightStart), y: CGFloat(bounds.size.height * DrawingConstants.grassHeightScale - DrawingConstants.gallowBaseHeight))
let bottomRightPoint = CGPoint(x: bottomLeftPoint.x + DrawingConstants.gallowHeightWidth, y: bottomLeftPoint.y)
let topLeftPoint = CGPoint(x: bottomLeftPoint.x, y: bounds.size.height * DrawingConstants.gallowHeight)
let topRightPoint = CGPoint(x: bottomRightPoint.x, y: topLeftPoint.y)
connectPoints(bottomLeftPoint, bottomRightPoint: bottomRightPoint, topLeftPoint: topLeftPoint, topRightPoint: topRightPoint, color: UIColor.brownColor())
}
func drawGallowAcross() {
let bottomLeftPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowHeightStart) + DrawingConstants.gallowHeightWidth, y: CGFloat(bounds.size.height * DrawingConstants.gallowHeight + DrawingConstants.gallowBaseHeight))
let bottomRightPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale), y: bottomLeftPoint.y)
let topLeftPoint = CGPoint(x: bottomLeftPoint.x, y: CGFloat(bounds.size.height * DrawingConstants.gallowHeight))
let topRightPoint = CGPoint(x: CGFloat(bottomRightPoint.x), y: topLeftPoint.y)
connectPoints(bottomLeftPoint, bottomRightPoint: bottomRightPoint, topLeftPoint: topLeftPoint, topRightPoint: topRightPoint, color: UIColor.brownColor())
}
func drawGallowTip() {
let topLeftPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale - DrawingConstants.gallowHeightWidth), y: CGFloat(bounds.size.height * DrawingConstants.gallowHeight + DrawingConstants.gallowBaseHeight))
let topRightPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale), y: topLeftPoint.y)
let bottomLeftPoint = CGPoint(x: topLeftPoint.x, y: topLeftPoint.y + DrawingConstants.gallowTipHeight)
let bottomRightPoint = CGPoint(x: topRightPoint.x, y: bottomLeftPoint.y)
connectPoints(bottomLeftPoint, bottomRightPoint: bottomRightPoint, topLeftPoint: topLeftPoint, topRightPoint: topRightPoint, color: UIColor.brownColor())
}
private func drawDude() {
drawHead()
drawBody()
}
func drawHead() {
let centerX = CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale - (DrawingConstants.gallowHeightWidth / 2))
let centerY = CGFloat(bounds.size.height * DrawingConstants.gallowHeight + DrawingConstants.gallowBaseHeight + DrawingConstants.gallowTipHeight + DrawingConstants.headRadius)
let center = CGPoint(x: centerX, y: centerY)
UIColor.blackColor().set()
let path = UIBezierPath(arcCenter: center, radius: DrawingConstants.headRadius, startAngle: CGFloat(0), endAngle: CGFloat(2 * M_PI), clockwise: true)
path.lineWidth = CGFloat(2)
path.stroke()
}
private func drawBody() {
let add = CGFloat(DrawingConstants.gallowBaseHeight + DrawingConstants.gallowTipHeight + 2 * DrawingConstants.headRadius)
let startPointY = CGFloat(bounds.size.height * DrawingConstants.gallowHeight + add)
let startPointX = CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale - (DrawingConstants.gallowHeightWidth / 2))
let startPoint = CGPoint(x: startPointX, y: startPointY)
let midPoint = CGPoint(x: startPoint.x + DrawingConstants.bodyLength, y: startPoint.y + DrawingConstants.bodyHeight)
let endPoint = CGPoint(x: midPoint.x + DrawingConstants.legLength, y: midPoint.y)
let bodyMid = calculateMidPoint(startPoint, point2: midPoint)
let armStartX = CGFloat(bodyMid.x - DrawingConstants.armBack)
let armStartY = CGFloat(bodyMid.y - DrawingConstants.armBack)
let armStart = CGPoint(x: armStartX, y: armStartY)
let armMid = CGPoint(x: armStart.x, y: midPoint.y)
let armEnd = CGPoint(x: bodyMid.x + DrawingConstants.armBack, y: armMid.y)
let legStart = calculateMidPoint(midPoint, point2: endPoint)
let legEndX = calculateMidPoint(legStart, point2: endPoint).x
let legEndY = endPoint.y
let legMidX = legStart.x
let legMidY = armStartY
let legMid = CGPoint(x: legMidX, y: legMidY)
let legEnd = CGPoint(x: legEndX, y: legEndY)
UIColor.blackColor().set()
let path = UIBezierPath()
path.lineWidth = CGFloat(2)
path.moveToPoint(startPoint)
path.addLineToPoint(midPoint)
path.addLineToPoint(endPoint)
path.stroke()
path.moveToPoint(armStart)
path.addLineToPoint(armMid)
path.addLineToPoint(armEnd)
path.moveToPoint(midPoint)
path.addLineToPoint(legMid)
path.addLineToPoint(legEnd)
path.stroke()
}
}
GameView Controller
import UIKit
class GameViewController: UIViewController, gameViewDataSource {
//MARK - Properties
@IBOutlet weak var gameView: GameView! {
didSet {
gameView.dataSource = self
}
}
@IBOutlet weak var youLose: UILabel! {
didSet {
youLose.textColor = UIColor.cyanColor()
youLose.font = UIFont(name: "MarkerFelt-Thin", size: CGFloat(48.0))
}
}
@IBOutlet weak var youWin: UILabel! {
didSet {
youWin.textColor = UIColor.cyanColor()
youWin.font = UIFont(name: "MarkerFelt-Thin", size: CGFloat(48.0))
}
}
@IBOutlet weak var numberOfGuessesLabel: UILabel! {
didSet {
if let guesses = brain.guesses {
numberOfGuessesLabel.text = "\(guesses)"
}
}
}
@IBOutlet weak var gameWordLabel: UILabel! {
didSet {
let gameWord = brain.gameWord
gameWordLabel.text = gameWord
}
}
@IBAction func guess(sender: UIButton) {
if running {
let guessedAlready = sender.currentTitleColor
if guessedAlready == UIColor.redColor() {
return
} else {
sender.setTitleColor(UIColor.redColor(), forState: .Normal)
let guess = sender.currentTitle!
brain.checkGuessAndUpdateGameWordAndGuesses(character: guess)
numberOfGuessesLabel.text = "\(brain.guesses!)"
gameWordLabel.text = brain.gameWord
gameView.setNeedsDisplay()
checkYouWin()
checkYouLose()
}
}
}
var running = true
var brain = HangmanBrain()
//MARK - Lifecycle Functions
override func shouldAutorotate() -> Bool {
return false
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.Portrait
}
//MARK - Gameplay Methods
func numberOfGuessesLeft() -> Int {
return brain.guesses!
}
func gameLevel() -> String {
return brain.level
}
func checkYouLose() {
if brain.theUserLost() {
running = false
youLose.textColor = UIColor.redColor()
brain.buildCorrectWord()
gameWordLabel.text = brain.gameWord
}
}
func checkYouWin() {
if brain.theUserWon() {
running = false
youWin.textColor = UIColor.redColor()
}
}
}
GameView
import UIKit
//MARK - GameView protocol
protocol gameViewDataSource: class {
func numberOfGuessesLeft() -> Int
func gameLevel() -> String
}
//MARK - Global Function
func drawLine(startPoint: CGPoint, endPoint: CGPoint) {
let path = UIBezierPath()
path.lineWidth = CGFloat(2)
path.moveToPoint(startPoint)
path.addLineToPoint(endPoint)
path.stroke()
}
class GameView: TitleView {
//MARK - Drawing Scales and Constants
struct ScaleConstants {
static let bodyLength: CGFloat = 50
static let limbLength: CGFloat = 25
static let handHeightScale: CGFloat = 0.4
static let headRadius: CGFloat = 20
static let eyeRadius = CGFloat(0.15 * ScaleConstants.headRadius)
static let eyeOffset = CGFloat(0.3 * ScaleConstants.headRadius)
static let mouthOffSet = CGFloat(0.3 * ScaleConstants.headRadius)
static let mouthRadius = CGFloat(0.25 * ScaleConstants.headRadius)
}
//MARK - Properties
weak var dataSource = gameViewDataSource?()
private var bodyStart: CGPoint = CGPointZero
private var bodyEnd: CGPoint = CGPointZero
private var headMiddle: CGPoint = CGPointZero
//MARK - Drawing functions
override func drawRect(rect: CGRect) {
drawSky()
drawGrass()
drawGallow()
let level = dataSource?.gameLevel()
let guesses = dataSource?.numberOfGuessesLeft()
var wrongGuessesSoFar = 0
var maxGeusses = 0
switch level! {
case "Hard":
maxGeusses = 6
case "Medium":
maxGeusses = 8
case "Easy":
maxGeusses = 10
default: break
}
wrongGuessesSoFar = maxGeusses - guesses!
startDrawChain(wrongGuessesSoFar)
}
func startDrawChain(numberOfGuesses: Int) {
drawHead(numberOfGuesses)
}
func drawHead(numberOfGuesses: Int) {
if numberOfGuesses == 0 {
return
} else {
let centerX = CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale - (DrawingConstants.gallowHeightWidth / 2))
let centerY = CGFloat(bounds.size.height * DrawingConstants.gallowHeight + DrawingConstants.gallowBaseHeight + DrawingConstants.gallowTipHeight + ScaleConstants.headRadius)
let center = CGPoint(x: centerX, y: centerY)
headMiddle = center
UIColor.blackColor().set()
let path = UIBezierPath(arcCenter: center, radius: ScaleConstants.headRadius, startAngle: CGFloat(0), endAngle: CGFloat(2 * M_PI), clockwise: true)
path.lineWidth = CGFloat(2)
path.stroke()
drawBody(numberOfGuesses - 1)
}
}
func drawBody(numberOfGuesses: Int) {
if numberOfGuesses == 0 {
return
} else {
UIColor.blackColor().set()
let add = CGFloat(DrawingConstants.gallowBaseHeight + DrawingConstants.gallowTipHeight + 2 * ScaleConstants.headRadius)
let startPointY = CGFloat(bounds.size.height * DrawingConstants.gallowHeight + add)
let startPointX = CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale - (DrawingConstants.gallowHeightWidth / 2))
let startPoint = CGPoint(x: startPointX, y: startPointY)
let endPoint = CGPoint(x: startPoint.x, y: startPoint.y + ScaleConstants.bodyLength)
bodyStart = startPoint
bodyEnd = endPoint
drawLine(startPoint, endPoint: endPoint)
drawLeftLeg(numberOfGuesses - 1)
}
}
func drawLeftLeg(numberOfGuesses: Int) {
if numberOfGuesses == 0 {
return
} else {
let startPoint = CGPoint(x: bodyEnd.x, y: bodyEnd.y)
let endPoint = CGPoint(x: startPoint.x - ScaleConstants.limbLength, y: startPoint.y + ScaleConstants.limbLength)
drawLine(startPoint, endPoint: endPoint)
drawRightLeg(numberOfGuesses - 1)
}
}
func drawRightLeg(numberOfGuesses: Int) {
if numberOfGuesses == 0 {
return
} else {
let startPoint = CGPoint(x: bodyEnd.x, y: bodyEnd.y)
let endPoint = CGPoint(x: startPoint.x + ScaleConstants.limbLength, y: startPoint.y + ScaleConstants.limbLength)
drawLine(startPoint, endPoint: endPoint)
drawLeftArm(numberOfGuesses - 1)
}
}
func drawLeftArm(numberOfGuesses: Int) {
if numberOfGuesses == 0 {
return
} else {
let startPoint = CGPoint(x: bodyStart.x, y: bodyStart.y + ScaleConstants.handHeightScale * ScaleConstants.bodyLength)
let endPoint = CGPoint(x: startPoint.x - ScaleConstants.limbLength, y: startPoint.y - ScaleConstants.limbLength * ScaleConstants.handHeightScale)
drawLine(startPoint, endPoint: endPoint)
drawRightArm(numberOfGuesses - 1)
}
}
func drawRightArm(numberOfGuesses: Int) {
if numberOfGuesses == 0 {
return
} else {
let startPoint = CGPoint(x: bodyStart.x, y: bodyStart.y + ScaleConstants.handHeightScale * ScaleConstants.bodyLength)
let endPoint = CGPoint(x: startPoint.x + ScaleConstants.limbLength, y: startPoint.y - ScaleConstants.limbLength * ScaleConstants.handHeightScale)
drawLine(startPoint, endPoint: endPoint)
drawLeftEye(numberOfGuesses - 1)
}
}
func drawLeftEye(numberOfGuesses: Int) {
if numberOfGuesses == 0 {
return
} else {
UIColor.blackColor().set()
let eyeMiddle = CGPoint(x: headMiddle.x - ScaleConstants.eyeOffset, y: headMiddle.y - ScaleConstants.eyeOffset)
let path = UIBezierPath(arcCenter: eyeMiddle, radius: ScaleConstants.eyeRadius, startAngle: 0, endAngle: CGFloat(2 * M_PI), clockwise: true)
path.lineWidth = CGFloat(1)
path.stroke()
drawRightEye(numberOfGuesses - 1)
}
}
func drawRightEye(numberOfGuesses: Int) {
if numberOfGuesses == 0 {
return
} else {
UIColor.blackColor().set()
let eyeMiddle = CGPoint(x: headMiddle.x + ScaleConstants.eyeOffset, y: headMiddle.y - ScaleConstants.eyeOffset)
let path = UIBezierPath(arcCenter: eyeMiddle, radius: ScaleConstants.eyeRadius, startAngle: 0, endAngle: CGFloat(2 * M_PI), clockwise: true)
path.lineWidth = CGFloat(1)
path.stroke()
drawMouth(numberOfGuesses - 1)
}
}
func drawMouth(numberOfGuesses: Int) {
if numberOfGuesses == 0 {
return
} else {
UIColor.blackColor().set()
let mouthMiddle = CGPoint(x: headMiddle.x, y: headMiddle.y + ScaleConstants.mouthOffSet)
let path = UIBezierPath(arcCenter: mouthMiddle, radius: ScaleConstants.mouthRadius, startAngle: 0, endAngle: CGFloat(2 * M_PI), clockwise: true)
path.lineWidth = CGFloat(1)
path.stroke()
}
}
}
Hangman Brain
import Foundation
import Darwin
class HangmanBrain {
//MARK - Database of Words
private struct Words {
static let Easy: [String] = ["fireplace","apple","january","tooth","cookies","mysterious","essential","magenta","darling","pterodactyl"]
static let Medium: [String] = ["palace","thumb","eleven","monkey","hunter","wounds","wright","egypt","slaves","zipper"]
static let Hard: [String] = ["jazz","puff","jiff","sphinx","vex","pox","hajj","jinx","vine","mom"]
static let numberOfWordsPerLevel = 10
}
//MARK - Properties
var level: String = "" {
didSet {
chooseWord(wordLevel: level)
setNumberOfGuesses(level: level)
}
}
var guesses: Int? = nil
var word: String? = nil
var gameWord = ""
private let wordsByLevel : [String: [String]] = ["Easy": Words.Easy, "Medium": Words.Medium, "Hard": Words.Hard]
//MARK - Gameplay Functions
private func chooseWord(wordLevel wordLevel: String) {
let UInt = UInt32(Words.numberOfWordsPerLevel - 1)
let wordNumber = Int(arc4random_uniform(UInt))
let wordChosen = wordsByLevel[wordLevel]![wordNumber]
word = wordChosen
gameWord = ""
for _ in wordChosen.characters {
createGameWord(character: "_")
}
}
private func setNumberOfGuesses(level level: String) {
switch level {
case "Easy": guesses = 10
case "Medium": guesses = 8
case "Hard": guesses = 6
default: break
}
}
func checkGuessAndUpdateGameWordAndGuesses(character character: String) {
var guessIsCorrect = false
let answer = word!
let currentWord = gameWord as String
gameWord = ""
let currentWordTrimmed = currentWord.stringByReplacingOccurrencesOfString(" ", withString: "")
let numberOfLetters = answer.characters.count as Int
for i in 0...numberOfLetters-1 {
let start = advance(currentWordTrimmed.startIndex, i)
let end = advance(currentWordTrimmed.startIndex, i+1)
let subCurrentWord = currentWordTrimmed.substringWithRange(Range<String.Index>(start: start, end: end))
if subCurrentWord != "_" {
createGameWord(character: subCurrentWord)
} else {
let subAnswer = answer.substringWithRange(Range<String.Index>(start: start, end: end))
if subAnswer == character.lowercaseString {
guessIsCorrect = true
createGameWord(character: subAnswer)
} else {
createGameWord(character: "_")
}
}
}
if(!guessIsCorrect) {
guesses = guesses! - 1
}
}
func buildCorrectWord() {
gameWord = ""
for c in word!.characters {
createGameWord(character: "\(c)")
}
}
func createGameWord(character character: String) {
gameWord += "\(character) "
}
func theUserWon() -> Bool {
for ch in gameWord.characters {
if "\(ch)" == "_" {
return false
}
}
return true
}
func theUserLost() -> Bool{
return guesses == 0
}
}
Thanks for any help!