1

I am working on a very simple swift game. The main layout of the game is a UICollectionView with 16 cells (doors.) 1 of the cells (doors) is a game over cell. You need to tap (open) all doors besides the game over cell. if a game over cell gets tapped, the cells will all randomize, and you lose a life. If you open all doors besides the game over cell, you win.

My code is below.

What works?

  • On initial load, the doors get randomized properly with an array of 15 DoorIdentifier and 1 GameOverIdentifier. This is how I am detecting the game over cells.

What doesn't work?

  • If I tap on some normal doors (to disable them), then tap on the Game Over door, it shuffles the doors around (as it should), but then other random doors turned disabled and look enabled (by 1.0 alpha). Not intended.

  • If I tap on some normal doors (to disable them), then tap on the Game Over door, all doors become 1.0 alpha, and only some have interaction enabled. Additionally, after pressing the Game Over door, sometimes only 1 door will maintain the 0.2 alpha.

What I Need? When a normal door gets tapped, that cell will now always become disabled. If a game over door gets tapped, it will disregard the cells that were already tapped, as they should be "out of the game play."

import UIKit

class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {

@IBOutlet weak var mainCollectionView: UICollectionView!


var gameOverDoorArray = ["GameOverIdentifier"]
var normalDoors = ["DoorIdentifier", "DoorIdentifier", "DoorIdentifier", "DoorIdentifier", "DoorIdentifier", "DoorIdentifier", "DoorIdentifier", "DoorIdentifier", "DoorIdentifier", "DoorIdentifier", "DoorIdentifier", "DoorIdentifier", "DoorIdentifier", "DoorIdentifier", "DoorIdentifier"]
var reuseIdentifiers = [String]()
var livesLeftCounter = 3


@IBAction func randomizeButton(sender: UIButton) {
    randomizeReuseIdentifiers()
}

override func viewDidLoad() {
    super.viewDidLoad()
    mainCollectionView.dataSource = self
    mainCollectionView.delegate = self
    mainCollectionView.backgroundColor = UIColor.clearColor()

    reuseIdentifiers = gameOverDoorArray + normalDoors

    randomizeReuseIdentifiers()
}


func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return reuseIdentifiers.count
}


func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let identifier = reuseIdentifiers[indexPath.item]
    let cell: DoorCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier(identifier, forIndexPath: indexPath) as! DoorCollectionViewCell

    cell.backgroundColor = UIColor(patternImage: UIImage(named: "doorClosedImage")!)

    return cell
}

func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {

    let currentCell = mainCollectionView.cellForItemAtIndexPath(indexPath) as UICollectionViewCell!



    // Game Over door was pressed!
    if reuseIdentifiers[indexPath.item] == "GameOverIdentifier" {
        gameOverDetection()
        randomizeReuseIdentifiers()
        mainCollectionView.reloadData()
    }

    // Normal door was pressed!
    else if reuseIdentifiers[indexPath.item] == "DoorIdentifier"{
        if currentCell.userInteractionEnabled && (normalDoors.count > 0){
            currentCell.alpha = 0.2
            print("reuse ID's array - \(reuseIdentifiers)")
            // when all the above is done, the button gets disabled.
            currentCell.userInteractionEnabled = false
            // Remove last item in normalDoors Array.
            normalDoors.removeLast()
        }

        print("Array count of allTheDoors - \(reuseIdentifiers.count).")
    }



}


func randomizeReuseIdentifiers() {
    var randomized = [String]()

    for _ in reuseIdentifiers {
        let random = reuseIdentifiers.removeAtIndex(Int(arc4random_uniform(UInt32(reuseIdentifiers.count))))
        randomized.append(random)
    }

    reuseIdentifiers = randomized

}

func gameOverDetection() {
    livesLeftCounter -= 1
    if livesLeftCounter < 0 {
        print("GAME OVER!")
    }
    print(livesLeftCounter)
}

}

Screenshot of layout

Joe
  • 3,772
  • 3
  • 33
  • 64
  • I don't understand all details of your game but I looked at the code and found some flaws which can lead to these problems. Notice that you use cells itself to store information about whether this door is enabled or not. It's not gonna work. You should create some property (array, maybe) and store this information in it, not in the cells. And inside a `cellForItemAtIndexPath` you need to update state of cell based on information from this array. – Alexander Doloz Apr 25 '16 at 16:26
  • Also storing information of game over cell by means of keeping array of reuse identifiers looks strange. You just need 1 number for that. – Alexander Doloz Apr 25 '16 at 16:29

1 Answers1

2

When working with UICollectionView, (and when using MVC in general) it's best to store all of the data you'll be working with separate from the view. Each door has a few properties for game state that you are currently trying to use properties on UICollectionViewCell to store. This is not correct. You should instead create a model (let's call it Door), and use properties on this model to store your game state.

First, create a class with a nested enum to store the type of door.

class Door {
    enum Type {
        case Normal
        case GameOver
    }

    var type: Type

    init(type: Type) {
        self.type = type
    }
}

Then, you'll need to store the open/closed status for each Door:

class Door {
    enum Type {
        case Normal
        case GameOver
    }

    var type: Type

    var closed = true

    init(type: Type) {
        self.type = type
    }
}

Lastly, you should write a method or computed property to return the appropriate reuse identifier for each kind of Door.

class Door {
    enum Type {
        case Normal
        case GameOver
    }

    var type: Type

    var closed = true

    var reuseIdentifier: String {
        switch type {
        case .Normal:
            return "DoorIdentifier"
        case .GameOver:
            return "GameOverIdentifier"
        default:
            return ""
    }

    init(type: Type) {
        self.type = type
    }
}

You'll then initialize your array of model objects instead of initializing an array of reuseIdentifiers.

var doors = [Door]()

override func viewDidLoad() {
    super.viewDidLoad()

    // Your other initialization in viewDidLoad here...

    doors = [Door(.Normal), Door(.Normal), /* etc... */ Door(.GameOver)]
}

You'll then need to rewrite randomizeReuseIdentifiers to randomize doors instead.

func randomizeDoors() {
    var randomized = [Door]()

    for _ in doors {
        let random = doors.removeAtIndex(Int(arc4random_uniform(UInt32(doors.count))))
        randomized.append(random)
    }

    doors = randomized
}

Finally, you can use this new data model in your UICollectionViewDataSource and UICollectionViewDelegate methods:

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return reuseIdentifiers.count
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let door = doors[indexPath.item]
    let identifier = door.reuseIdentifier
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(identifier, forIndexPath: indexPath) as! DoorCollectionViewCell

    // Here, use the closed property to set interaction enabled and alpha.
    if door.closed {
        cell.userInteractionEnabled = true
        cell.alpha = 1.0
    } else {
        cell.userInteractionEnabled = false
        cell.alpha = 0.2    
    }

    cell.backgroundColor = UIColor(patternImage: UIImage(named: "doorClosedImage")!)
    return cell
}     

func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {

    let currentCell = mainCollectionView.cellForItemAtIndexPath(indexPath) as UICollectionViewCell!
    let door = doors[indexPath.item]

    switch door.type {
    case .Normal:
        // Set the door to opened, so it is disabled on the next reload.
        door.closed = false
    case .GameOver:
        gameOverDetection()
        randomizeDoors()            
    }

    mainCollectionView.reloadData()
}

In general, I think it would benefit you to read a bit more about the Model View Controller (MVC) pattern as you continue learning. Apple has some documentation that might be helpful, and you might find these resources illustrative as well.

Community
  • 1
  • 1
Brendon Roberto
  • 428
  • 3
  • 12
  • Thanks for your help. Getting some errors in your code, though. var closed = true "enums may not contain stored properties" / var reuseIdentifier "computed property must have an explicit type" / randomized.append(random) "Cannot convert value of type 'ViewController.Door" to expected argument type 'string' / doors = randomized "Cannot assign value of type '[String]' to type '[ViewController.Door]'" – Joe Apr 25 '16 at 20:34
  • Fixed the issues. Sorry - didn't run this code before posting it. – Brendon Roberto Apr 25 '16 at 20:43
  • YOU ROCK! i got this to work. there were a couple of small issues in your code, but i ironed them out. reuseIdentifier in the Door class was missing a close bracket / door array needed type: - Door(type: .Normal) / currentCell didn't need to be declared in didSelectItemAtIndexPath. One more question though.. If the "game over" door gets tapped, it shuffles the entire collection view. Is it possible to just shuffle all closed doors, so that the open (alpha 1.0 / enabled) doors don't shift around? – Joe Apr 25 '16 at 22:20
  • Check out the [`filter` method on `SequenceType`](https://developer.apple.com/library/watchos/documentation/Swift/Reference/Swift_SequenceType_Protocol/index.html#//apple_ref/swift/intfm/SequenceType/s:FEsPs12SequenceType6filterFzFzWx9Generator7Element_SbGSaWxS0_S1___) (which Array implements). As a hint: `doors.filter({ $0.closed })` – Brendon Roberto Apr 26 '16 at 13:00
  • Thanks, but that syntax is too advanced for my skill set at the moment. Will save for future reference. – Joe Apr 26 '16 at 16:00