100 Days of Swift – Consolidation IV

I completely agree with Paul: we are almost halfway with the 100 days, the difficulty is ramping up and, yes, every day more time is demanded of us. I am sincere, this is the end of Day 42 and I am now beginning to face Day 41. I am pretty sure I will go much beyond Day 100 to finish the assignments because I really struggle to have one completely free hour per day to concentrate on this plus writing what I learn not to get lost. Let’s see how it goes!

What we learned?

A whole lot of things!

By now my main struggle is: when should I use what? That is, when to use Data(contentsOf:) when to load content from disk, when FileManager when Bundle… I guess only experience will tell.

We learned about the tab bar controller and that each item on it is represented by a title and an icon, we used Datato load a URL and, therefore, to parse a JSON.

We managed not to get WKWebView rusty to show some content from the web in a table-view, took a glance into the world of HTML (not that I liked it!) and learned how to design our interfaces in code.

We added property observers to our arsenal and how to program multi-threaded workflows on our devices.

And much, much more…

Key take-aways

enumerated()

When we want to loop over an array and work not only with its elements but also with their index we can use the enumerated() method on the array itself. Let’s give a look at the method’s description:

Let’s give a look at what that return type is: EnumeratedSequence<[String]>. In the Documentation this type is nested inside Collections > Supporting Types and is described as an enumeration of the elements of a sequence or collection. To call this on an array we have to use a tuple and the return will be, in any case, a String. Look at this example from the Documentation:

var s = ["foo", "bar"].enumerated()
for (n, x) in s {
    print("\(n): \(x)")
}
// Prints "0: foo"
// Prints "1: bar"

Property Observers

These clever pieces of code let us run some code when a property changes. We can think of them as of code sensors. Of course they are not almighty but they are for sure useful and, in Project 8 we used them in this way:

var score: Int = 0 {
    didSet {
        scoreLabel.text = "Score: \(score)"
    }
}

Grand Central Dispatch

To harness the power of our devices’ hardware we need to write clever software that can take advantage of their multitasking capabilities. This is done via the access to Grand Central Dispatch (Global Cooldown, for me, or I’ll never remember it!), allowing us to perform long and slow activities in the background and important (and UI updates!!!) on the main thread.

This is how the code looks like:

DispatchQueue.global().async { [weak self] in
    // do background work

    DispatchQueue.main.async {
        // do main thread work
    }
}

Challenge

Fine! This time we have to build a hangman game… all by ourselves! Let’s get started and see where we get!

After a good 20 minutes of blank-screen crisis I took a piece of paper and drew my interface there so that I could see what I needed…

I plan to have a big label on the top to show a report of how the player is doing and, most of all, how many errors he can still make.

Right below I will put a “NEW GAME” and a “RETRY” button (maybe also a SCORE button to get along with correct and wrong answers but one piece of a time, ok?)

Below that I need the “current answer” text field then, below that, one label and two buttons: a label that will show the “chosen letter” and two buttons to “confirm” the choice and another to “clear” it and choose another letter.

Now for the bad part… I have to organise the letter buttons below but if I use the 26-letter English alphabet I will not be able to use a nested loop to draw it… How to do that?

Little comment… how can it be expected from us that we can make this in an hour or little more, this I don’t know… What can I say? I will just lag behind in the progress of the days…

Planning what will be needed…

I started to write some properties that I thought I may be needing…

var reportLabel: UILabel!
var scoreLabel: UILabel!
var nextLevel: UIButton!
var retry: UIButton!
    
var currentAnswer: UITextField!
var chosenLetter: UILabel!
var submitButton: UIButton!
var clearButton: UIButton!
    
var letterButtons = [UIButton]()
var activatedButtons = [UIButton]()
    
var attemptsLeft = 0
var score = 0

Of course this list may very well be changing in the nearest future but by now it looks like a safe one. Now, on to designing the UI.

UI Design

I managed to design the whole UI and it is starting to look good, just now Xcode is refusing to “compile in reasonable time” an instance of NSLayoutConstraint.activate… so right now I am completely stuck here even if the whole instance is written and it kind of makes sense…

Maybe I will just go and attempt to write the logic.


A little story…

Between the last time I wrote on this project and today 5 days passed. It is actually Day 46 of the 100 Days Of Swift initiative but I have been stuck on the Consolidation Day IV until now. I confess, I was really an inch away from giving up because even with all the hints, the research, the trials I had done for three days, I guess at least 10hrs in total, nothing was coming out of it. The logic was working, basically, but if I had a word with any double letter it would have just taken the first one of it. I really need to thank user anngmt from the Hacking with Swift Slack channel who helped me understand what the problem was. I will now just report how the app was built and the extra little things I put inside, as I really want to get back on learning. I know, struggle is learning but frustration is not so… Let’s move on or, well, let’s start back from the beginning…


The Properties

Starting from the UI, I made four labels, one of which will end up unused and will possibly be replaced in the future with something animatable which is now far beyond my avid grasp: a title label, a report label, a score label and a wrong answer label.

Based on how I had conceived the UI (much more complex than needed, as an afterthought) I created a text field for the current answer and an extra label for the chosen letter (I wanted to give the user the possibility to think about their choice and go back before committing).

I created two global arrays of buttons, one for the letter buttons and one for those letter-buttons which would end up tapped (in this way I could control which one to show and to show them back all at once in a new game).

Finally, four variables to keep track of the state of the game and to govern the next steps of the game! So a guessedLetters variable would let the game know when a user would win and show an appropriate alert, an attemptsLeft variable would determine the destiny of the bad players, a score variable would keep track of the score of the player and an errorScore one, finally, would keep track of each mistake and made work with the attemptsLeft one.

Now for the tricky part, the one that let me stuck for days… I created a word empty string variable (so far, so good…) and a usedLetters string array… WRONG! And this was what made me get crazy! It had to be a [Character] and the promptWord (the one which would have then ended in the text field), a [String] one… I am not really sure this is the best solution, I do not know why my solution did not work, I just care that this is over and that I can move on… If someone will then explain me why it was wrong fine, otherwise well, time will tell.

UI Building

As I said above, I decided to build my UI in code. I will not bore you will something Paul has already shown so just go to the GitHub repository to see what I made up and how. The only important parts are these:

  • the titleLabel will have a setCompressionResistancePriority of 1 to be sure it will be as little as possible on the top.
  • the reportLabel will have a setContentHuggingPriority of 1 instead so that it will end up the biggest view on the screen.
  • the nextWord button (actually all our buttons) will be created directly in the loadView method and it will have a target called nextWordTapped for the .touchUpInside UI control event.
  • I have added a RETRY button but, after all, in the end, it will not be because I tied its logic to the Game Over alert controller. Well, to be sincere, one could be seeing the incoming doom and decide to start over and just press the RETRY button.
  • the submit button will have a submitTapped method and the clear button will have a clearTapped method.

I activated all the constraints fighting with this Xcode 10.1 bug that made my MacBook Pro’s fans howl like the wind in Norway’s fjords and obtained a layout that looks like this:

As you can see I created two rows of 13 buttons which make the layout a big clunky in portrait. I had other ideas but no time to implement them. I saw some people implementing a 6 x 5 grid with the last row half-empty, something that I really didn’t want to do.

THE logic!

Now for the fun part!

Load the words

I called loadWords in viewDidLoad and started writing it. I retrieved a URL from our app’s bundle, then extracted a String from it and created an array out of its components and shuffled it. Something interesting happened: I had a .rtf file and every word had an extra \n attached to its end. Converting it to .txt removed it. What is this sorcery?

I called a random element of this words’ array and stored it into the word property, printing it to the console for debugging purposes.

I then ran a loop that for every letter (of goddamnit type Character) of said word would append said letter to the usedLetters array. It would then append an equally long amount of ? to the promptWord array. I also printed these two properties to the console to be sure of what was happening in the background.

Finally I put the text of the currentAnswer label to be equal the joined elements of the promptWord array (I didn’t do this, I really could not figure it out …) and set up the attemptsLeft property to be 3/4 of the length of the word. I know that normally one has only two legs, a torso and two arms in this game but I wanted to be a big more kind!

Letter tapped

Now, when a user taps a letter I wanted a few things to happen: first of all I needed to check that the tapped button had indeed something inside. I would then put that same letter in the chosenLetter label. I would then append the sender to the tappedButtons array, hide it and make it non-user interact-able.

Clear tapped

I also wanted to provide a way for the user to change letter in case of an accidental tap so I wrote a clearTapped method. I stored the last tapped button in a constant, made it visible, interact-able and reset the chosenLetter label to its original underscore.

The beast… well, no… just the submit method!

Now for the big part: first I checked that lowercased version of the chosen letter would be there and in that case stored it into a string constant using guard let. I created a big if statement for whether the letter would have been inside the word or not. Let’s now take two roads for extra clarity:

  1. Assuming the letter (in may-it-drown Character type!) was there…
    • I looped red the enumerated version of the usedLetters array with a tuple made of (index, character) so that, for every indexed-character of that array, IF said character would be equal to the (may-it-burn-in-Hell Character type) version of my letter, I would change the element at [index] of promptWord with that element and update the current answer’s label accordingly. You just cannot imagine how much I got lost here because the logic was all there but the way strings and characters interlocked just would not make it work…
    • increased guessedLetters and score by 1.
    • if guessedLetters == word.count (that is, if the user wins) a victoryAC would be presented with a funny message and it would call the nextWord method which would basically reset the game! No else needed here.
  2. Assuming the letter (in may-it-sit-on-the-WC-for-days Character type) would not be there I would increase the errorScore by 1 and, if said score would be less than the attempts left I would present an errorAC to ask the user to choose another letter, otherwise I would announce him his premature in-game death and present a gameOverAC, offering, though—because yes, I am merciful— a RETRY button!

The rest

What remains is really little thing compared to what was done just above. The retry action-method would reset everything, the nextWord one would allow us to get the next word in the array and… well… that’s it.


Thank you for following me thus far, I really hope I will not get stuck so much on something so trivial but well, only time will tell.

You can find the repository for this project here.


If you like what I’m doing here please consider liking this article and sharing it with some of your peers. If you are feeling like being really awesome, please consider making a small donation to support my studies and my writing (please appreciate that I am not using advertisement on my articles).

If you are interested in my music engraving and my publications don’t forget visit my Facebook page and the pages where I publish my scores (Gumroad, SheetMusicPlus, ScoreExchange and on Apple Books).

You can also support me by buying Paul Hudson’s books from this Affiliate Link.

Anyways, thank you so much for reading!

Till the next one!

Published by Michele Galvagno

Professional Musical Scores Designer and Engraver Graduated Classical Musician (cello) and Teacher Tech Enthusiast and Apprentice iOS / macOS Developer Grafico di Partiture Musicali Professionista Musicista classico diplomato (violoncello) ed insegnante Appassionato di tecnologia ed apprendista Sviluppatore iOS / macOS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: