#16 Enum/Switch/Struct (psychological test)

Foreword

So I made an app, called Trial of Tarnished, as a tribute to Elden Ring. The app generates corresponding runes based on various options and choices made by the user. Constructing this simple app requires a flexible understanding and usage of enum, switch, and struct. So let’s talk about them!

Gif demonstration
Overview
Storyboard layout

MVC rules

To keep my folder nice and tidy. I group them according to Model, Visual, and Controller.
Model folder:
It’s where I store all my database/ function/ and struct

Visual folder: It’s where I put my visual components. In this case — storyboard.

Controller folder: Pretty obvious, I put controllers in it. Let’s see there’s IntroViewController/ QuestionViewController/ FinalViewController

MVC rules

Enum within struct

The piece of code below is a model for me to define all kinds of properties of corresponding demigods.

This approach is used when you want to tightly couple the enum to the struct, making it clear that the enum is specifically related to that struct.

So in the following code, I grouped all kinds of properties with associated enum into a single struct.

Pros:

  • Encapsulation: The enum is being contained within the struct, meaning it cannot be accessed or used elsewhere in your code. This can prevent misuse or confusion. The enum is created specifically for the Demigod struct and is protected from outside.
  • Namespace Management: The enum's values are scoped to the struct, so they won't conflict with similar named values in other enums outside the struct.

Cons:

  • Limited Accessibility: Because the enum is within the struct, it's not accessible to other parts of the code. If I have another struct or class that might need to use the same enum, I’ll have to redefine it or call the struct first.
// Importing necessary frameworks
import Foundation
import UIKit

//----DEMIGOD STRUCT---------------------------------------------------------------------------------

// Defining a new structure named DemigodInfo
struct DemigodInfo {

// Defining an enumeration to represent the various nicknames of demigods
// CaseIterable allows us to get a collection of all the cases,
// Hashable allows us to use this enum in a Set or as a Dictionary key.
enum Nickname: String, CaseIterable, Hashable {
// Defining each case in the enumeration
case godrickTheGrafted = "Godrick the Grafted"
case rennalaQueenOfTheFullMoon = "Rennala Queen of the Full Moon"
case starscourgeRadahn = "Starscourge Radahn"
case morgottTheOmenKing = "Morgott, the Omen King"
case rykardLordOfBlasphemy = "Rykard, Lord of Blasphemy"
case maleniaBladeOfMiquella = "Malenia, Blade of Miquella"
case mohgLordOfBlood = "Mohg, Lord of Blood"
}

// Defining a computed property for bossImage.
// This switch statement will return different boss images values based on the value of resultDemigod.
var bossImage:String{
switch resultDemigod {
case .godrickTheGrafted:
return "Godrick"
case .rennalaQueenOfTheFullMoon:
return "Rennala"
case .starscourgeRadahn:
return "Starscourge"
case .morgottTheOmenKing:
return "Morgott"
case .rykardLordOfBlasphemy:
return "Rykard"
case .maleniaBladeOfMiquella:
return "Melania"
case .mohgLordOfBlood:
return "Mohg"
}
}

// Similar to bossImage, this computed property provides different titles based on the value of resultDemigod.
var titles:String { return "You possess the greate rune of \(resultDemigod.rawValue)"}



// Another computed property, this one provides different URL strings based on the value of resultDemigod.
var Urls:String{
switch resultDemigod {
case .godrickTheGrafted:
return "https://eldenring.wiki.fextralife.com/Godrick+the+Grafted"
case .rennalaQueenOfTheFullMoon:
return "https://eldenring.wiki.fextralife.com/Rennala+Queen+of+the+Full+Moon"
case .starscourgeRadahn:
return "https://eldenring.wiki.fextralife.com/Starscourge+Radahn"
case .morgottTheOmenKing:
return "https://eldenring.wiki.fextralife.com/Margit,+The+Fell+Omen"
case .rykardLordOfBlasphemy:
return "https://eldenring.wiki.fextralife.com/Rykard,+Lord+of+Blasphemy"
case .maleniaBladeOfMiquella:
return "https://eldenring.wiki.fextralife.com/Malenia+Blade+of+Miquella"
case .mohgLordOfBlood:
return "https://eldenring.wiki.fextralife.com/Mohg,+Lord+of+Blood"
}
}

// A computed property that provides different rune pictures based on the value of resultDemigod.
var runePictures:String{
switch resultDemigod {
case .godrickTheGrafted:
return "greatRune_01"
case .rennalaQueenOfTheFullMoon:
return "greatRune_02"
case .starscourgeRadahn:
return "greatRune_03"
case .morgottTheOmenKing:
return "greatRune_04"
case .rykardLordOfBlasphemy:
return "greatRune_05"
case .maleniaBladeOfMiquella:
return "greatRune_06"
case .mohgLordOfBlood:
return "greatRune_07"
}
}

// This computed property provides different descriptions based on the value of resultDemigod.
var description:String {
switch resultDemigod{
case .godrickTheGrafted :
return "Godrick, Stormveil's monarch bold, of lineage golden and ancient told. Branded weak by kin of spite, his strength in grafted limbs takes flight. A dragon's head his arm adorns, in battle's second phase reborn. His knights in golden emblems clad, of war-axe twain, and Serosh, the Regent lad."
case .rennalaQueenOfTheFullMoon :
return "Beneath a gilded sky, young Rennala sought stars, bewitching Raya Lucaria's academy with lunar charms. From humble beginnings to queenly might, she led Glintstone knights, to challenge gold-clad champions in starlit fights. Marika's champion, Radagon, crossed her path in strife, till love supplanted war, and bound them man and wife. When Radagon departed, Rennala's heart did rend, her academy rebelled, her rule came to an end. Clutching Radagon's gift, the amber egg, she delved into rebirth's grim art, her sanity dissolved."
case .starscourgeRadahn :
return "Born of Radagon and Rennala, Radahn, lion-armored, idolized Elden Lord Godfrey. Heir to fiery hair and demigod stature, he outgrew Leonard, his faithful steed, and sought gravitation's magic in Sellia's realm. Radahn, Starscourge shattered celestial cycles, his blades marked with gravity's crest. His Redmanes, fearless knights, stood with him, loyal in every quest."
case .morgottTheOmenKing :
return "Born of golden lineage, Morgott, fiend confined, renounced his blood, his curse, in blade and staff enshrined. Amidst the shattered Ring, claimed he Leyndell's throne, swearing to protect the Erdtree, alone. As Margit, Fell Omen, he laid champions low, yet as the Veiled Monarch, his reign cast a shadow. Warned he of the Erdtree's wrath, fading on the throne, a warning left unheeded, his fate, by Godfrey known."
case .rykardLordOfBlasphemy :
return "In aftermath of the Shattered Ring, Rykard, aggrieved, waged blasphemous war against the Erdtree, deceived. Fed himself to Gelmir's serpent, his ambition turned depraved, once noble lord to gluttonous beast, his honour waived. Even in death, he boasted, A serpent never dies, while Tanith, heartbroken, devoured his corpse beneath the skies."
case .maleniaBladeOfMiquella :
return "Born of Marika and Radagon, Malenia and Miquella were the Empyreans afflicted. From Scarlet Rot emerged Malenia, her limbs lost, but in swordsmanship, she was gifted. Wars waged, battles won, until the Rot unleashed, her victory sunken. Coma-ridden, in Haligtree, she awaits her brother, while Rot's infection swelled greater."
case .mohgLordOfBlood :
return "In lineage golden, Mohg and Morgott birthed, Omen-born twins 'neath Leyndell's earth. Royal blood, uncut horns, Mohg embraced his curse, found fire in blood from a Mother, diverse. Lord of Blood, he claimed, while wars raged above, and from Haligtree's heart, his brother's form he stole, in twisted love. His dream - a god-ruler, his brother to be, while he'd be consort, in dark monarchy."
}
}

// This is the nickname (of the enum type Nickname) for the current instance of the struct.
var resultDemigod: Nickname

// This is the initializer for the DemigodInfo struct. It takes a Nickname as argument and assigns it to resultDemigod.
init(_ resultDemigod:Nickname) {
self.resultDemigod = resultDemigod
}
}

Switch return struct or dictionary

In the code below, I want o return the corresponding demigod identifier for each option. So I decided to use switch to return an array of struct. Switching over an enum and returning a struct has the advantage of type safety and the convenience of accessing data. But… I was also told that I can actually use dictionary instead of struct. Let’s break them down and see what kind of situation to use them accordingly.

Pros:

  • Struct : Switch statements with structs provide type safety.
  • Dictionary : Dictionaries offer more flexibility in terms of the kinds of data they can hold.

Cons:

  • Struct : Switching over a struct is more readable and easier to maintain, as the structure of data is defined in my code(in this case OptionInfo)
  • Dictionary : there’s a risk of errors if you try to access a key that doesn’t exist.
//
// QuestionModel.swift
// Trial of tarnished
//
// Created by JimmyChao on 2023/8/3.
//

import Foundation
import UIKit



struct OptionInfo {
let optionContent: String
let optionTag: String
}



// Enum Quiz is used to represent different aspects of a character, such as their home landscape, combat method, leadership style, etc.
// It conforms to String, allowing the cases to be easily transformed to and from a string representation.
// It also conforms to CaseIterable, enabling the enumeration of all the cases.
enum Quiz: String, CaseIterable, Hashable {
// These cases represent different character aspects that are part of the quiz.
case HomeLandscape
case CombatMethod
case LeadershipStyle
case ChallengeApproach
case GreatestStrength
case RulerType
case GreatestWeakness
case CherishedValue



// Below, each case is mapped to a specific question that will be asked in the quiz.
// The questions relate to different facets of a character's personality and preferences.
var description: String {
switch self {
case .HomeLandscape:
return "Which of these landscapes would you prefer to call home?"
case .CombatMethod:
return "What is your preferred method of combat?"
case .LeadershipStyle:
return "How would you describe your leadership style?"
case .ChallengeApproach:
return "How do you approach a challenge?"
case .GreatestStrength:
return "What is your greatest strength?"
case .RulerType:
return "What type of ruler are you?"
case .GreatestWeakness:
return "What would be your greatest weakness?"
case .CherishedValue:
return "What's your most cherished value?"
}
}


//Each option has it's corresponding character stored in tags in order for me to keep track the score for corresponding character.
var optionInfo: [OptionInfo] {
switch self {
case .HomeLandscape:
return [
OptionInfo(optionContent: "A grand castle, teeming with knowledge and wealth.", optionTag: "Godrick The Grafted"),
OptionInfo(optionContent: "A tranquil forest, bathed in the glow of the full moon.", optionTag: "Rennala Queen of the Full Moon"),
OptionInfo(optionContent: "A desolate wasteland, scarred by falling stars.", optionTag: "Starscourge Radahn"),
OptionInfo(optionContent: "An imposing fortress, steeped in dark omens.", optionTag: "Morgott, the Omen King")
]
case .CombatMethod:
return [
OptionInfo(optionContent: "Cunning trickery and powerful spells.", optionTag: "Rykard, Lord of Blasphemy"),
OptionInfo(optionContent: "Strength and brute force, unleashing the fury of a thousand suns.", optionTag: "Starscourge Radahn"),
OptionInfo(optionContent: "Swift, precise swordsmanship.", optionTag: "Malenia, Blade of Miquella"),
OptionInfo(optionContent: "Commanding legions of loyal followers.", optionTag: "Morgott, the Omen King")
]
case .LeadershipStyle:
return [
OptionInfo(optionContent: "Wise and innovative, always seeking progress.", optionTag: "Godrick The Grafted"),
OptionInfo(optionContent: "Mysterious and enigmatic, ruling from the shadows.", optionTag: "Rennala Queen of the Full Moon"),
OptionInfo(optionContent: "Aggressive and dominant, seizing power through force.", optionTag: "Mohg, Lord of Blood"),
OptionInfo(optionContent: "Inspirational and revered, leading with charisma.", optionTag: "Rykard, Lord of Blasphemy")
]
case .ChallengeApproach:
return [
OptionInfo(optionContent: "With careful planning and strategy.", optionTag: "Godrick the Grafted"),
OptionInfo(optionContent: "By overpowering it with brute strength.", optionTag: "Mohg, Lord of Blood"),
OptionInfo(optionContent: "By adapting and finding creative solutions.", optionTag: "Malenia, Blade of Miquella"),
OptionInfo(optionContent: "By confronting it head on, fearlessly.", optionTag: "Starscourge Radahn")
]
case .GreatestStrength:
return [
OptionInfo(optionContent: "My unyielding thirst for knowledge.", optionTag: "Godrick the Grafted"),
OptionInfo(optionContent: "My mastery over the forces of nature.", optionTag: "Rennala Queen of the Full Moon"),
OptionInfo(optionContent: "My unparalleled combat skills.", optionTag: "Malenia, Blade of Miquella"),
OptionInfo(optionContent: "My unstoppable willpower and determination.", optionTag: "Rykard, Lord of Blasphemy")
]
case .RulerType:
return [
OptionInfo(optionContent: "A feared despot, ruling with an iron fist.", optionTag: "Morgott, the Omen King"),
OptionInfo(optionContent: "A beloved sovereign, respected by all.", optionTag: "Rennala Queen of the Full Moon"),
OptionInfo(optionContent: "A warlord, leading through strength and victory.", optionTag: "Mohg, Lord of Blood"),
OptionInfo(optionContent: "An innovator, pushing the boundaries of what is possible.", optionTag: "Godrick the Grafted")
]
case .GreatestWeakness:
return [
OptionInfo(optionContent: "My ruthless ambition.", optionTag: "Morgott, the Omen King"),
OptionInfo(optionContent: "My volatile temper.", optionTag: "Mohg, Lord of Blood"),
OptionInfo(optionContent: "My relentless pursuit of knowledge, at any cost.", optionTag: "Rykard, Lord of Blasphemy"),
OptionInfo(optionContent: "My inability to resist the allure of power.", optionTag: "Starscourge Radahn")
]
case .CherishedValue:
return [
OptionInfo(optionContent: "Loyalty, the cornerstone of every great kingdom.", optionTag: "Morgott, the Omen King"),
OptionInfo(optionContent: "Courage, the heart of every warrior.", optionTag: "Starscourge Radahn"),
OptionInfo(optionContent: "Wisdom, the foundation of every decision.", optionTag: "Godrick the Grafted"),
OptionInfo(optionContent: "Harmony, the essence of a balanced life.", optionTag: "Rennala Queen of the Full Moon")
]
}

}


}



Max method and closure

I’ve always wanted to explore closure, I’ll probably write an article just about it. But for now, let’s have a glimpse through the Max method!

Ignore the @escaping for now, we can see the argument of the max method has (Self.Element, Self.Element) -> Bool) . This means the max(by:) method is asking for a function or closure. And closure is just a simplified version of a function, let’s keep that in mind for now.

How closure works

In Swift, you have a lot of flexibility with how you define closures. When you pass closure as a method’s argument, Swift allows us to write it in a very shortened form. But for clarity, here’s the code using the full closure syntax:

var score: [String: Int] = ["Zeus": 10, "Hercules": 12, "Ares": 8]

if let result = score.max(by: { (demigod1: (key: String, value: Int), demigod2: (key: String, value: Int)) -> Bool in
return demigod1.value < demigod2.value
}) {
let (demigod, _) = result
print("Demigod with max score is: \(demigod)")
}
  • (demigod1: (key: String, value: Int), demigod2: (key: String, value: Int)) -> Bool: This part declares the closure's parameters and return type. The closure takes two tuples as parameters (each containing a key and a value from the dictionary) and returns a Bool.
  • return demigod1.value < demigod2.value: This is the body of the closure. It returns true if the value of the first demigod's score is less than the second demigod's score. This will cause the max(by:) method to return the demigod with the maximum score.
  • So, the shortened form { $0.value < $1.value } is a shorthand way of writing the exact same closure. It omits the parameter and return type declaration and uses $0 and $1 as shorthand for the first and second parameters.

In the following code, I use the max method with shortened closure to find the highest value within score dictionary.

import Foundation
import UIKit

// ScoreTracker struct tracks the score for each demigod in Elden Ring.
struct ScoreTracker{


// score dictionary uses the Nickname enumeration from DemigodInfo as the key and an integer as the value.
var score:[DemigodInfo.Nickname:Int] = [
.godrickTheGrafted: 0,
.rennalaQueenOfTheFullMoon:0,
.starscourgeRadahn:0,
.morgottTheOmenKing:0,
.rykardLordOfBlasphemy:0,
.maleniaBladeOfMiquella:0,
.mohgLordOfBlood:0
]



//Hold the final result message after all scores are calculated.
var resultText = ""



//Hold the Nickname of the demigod who has the highest score.
var resultDemigod:DemigodInfo.Nickname = .godrickTheGrafted



// It assigns the nickname of the demigod with the highest score to resultDemigod.
mutating func getHighestScore(){
if let (demigod, _) = score.max(by: {$0.value < $1.value}){
resultDemigod = demigod
}else{
resultDemigod = DemigodInfo.Nickname.godrickTheGrafted
}
}


// Increases the score of the demigod associated with the selected tag by 1 each time it's called.
mutating func trackingScore(_ tags: String){
guard let selectedTag = DemigodInfo.Nickname(rawValue: tags) else { return }

//Increment the corresponding score
score[selectedTag] = (score[selectedTag] ?? 0) + 1

//Print out the result for debugging purposes
for demigod in DemigodInfo.Nickname.allCases {
print("\(demigod.rawValue) \(score[demigod] ?? 0)")
}
}
}

Sending data with segue action

I once asked Peter a very naive question. Why do we need a segue action when we can just create files for all our data a share them with all the controllers?

And he said that transmitting data by using segue action allows data to be stored inside the ram memory. And it’s faster for other controllers to reach and access the data. While storing data in a file means the data is stored in the hard drive. And accessing data from a hard drive is relatively slower than from ram memory.

    //---SEGUE FUNCTIONS----
@IBSegueAction func sendResult(_ coder: NSCoder) -> FinalViewController? {
let controller = FinalViewController(coder: coder)
//Track the score of selected option
scoreTracker.trackingScore(tags)
//Identify the highest score
scoreTracker.getHighestScore()

// Assign the result to 'resultDemigod' of 'FinalViewController'.
controller?.resultDemigod = scoreTracker.resultDemigod

return controller
}
Use segue action to send data from one controller to another.

Git/Conclusion

There is still much to learn. At this point, I’m pretty glad that I can use switch and enum or dictionary to structure and organize my code. But I still don’t understand some of the mechanisms of closure and how to properly read a document. I guess I’ll write articles about them in the future as well.

--

--