jvarela jvarela - 2 months ago 17
Swift Question

How to prevent a failable initializer of a Swift enum from returning nil within a range of raw numbers?

I implemented an enum to represent 256 colors of a color map like this:

enum MapColor:UInt8
{
case black = 0
case white = 1
case red = 2
case lightRed = 3
case orange = 7
...
case rampBlue = 78
...
case rampRed = 255
}


However, I have NOT given a case for each color in the color map. I only added specific cases for the most important colors that I want to specify by name, like green, orange, red, yellow, gray, lightYellow, etc. This allows me to specify a color like this:

let color = MapColor.orange


without worrying about what is the underlying raw value. At the same time it would allow me to have 256 colors that could be used in gradients in my game. This is why between 78 and 255 I specified in my color map a gradient of colors that goes from blue (rampBlue) and ends in red (rampRed).

However, I wanted to implement a random color generator that returned a raw value between 0 and 255, the possible color gamut for my color map, like this:

func fillColor() -> MapColor
{
return MapColor(rawValue: UInt8(abs(Utilities.random(maximum: 255))))!
}


However, obviously, that causes a segmentation fault whenever it tries to initialize the MapColor object with a raw value with a non-existent case.

So my question is: is there any way to avoid this without tediously add all possible cases between 0 and 255 by hand and still keep some explicit cases that I want to refer to by name, but at the same time have the possibility of generating a random color with a value between 0 and 255?

Answer

After all, it was Code Different that threw me in the right direction. Indeed, there is no other way than turning MapColor into a struct but with a twist: I kept the former enum and renamed it RawColor, so that I can still use explicit color cases and I do not have to remember the exact raw values for a given color in the color map. Thus, for those of you who may have a similar problem to mine now or in the future, I post here the code that was tested in a playground (Xcode 8 GM using Swift 3.0):

import simd

enum RawColor:UInt8
{
    case black = 0
    case white = 1
    case red = 2
    case orange = 7
    case yellow = 16
    case gray = 30
    case darkBrown = 40
    case lightBrown = 58
    case blue = 59
    case green = 67
    case lightGreen = 77
    case rampBlue = 78
    case rampGreenBlue = 123
    case rampBlueGreen = 128
    case rampDarkGreen = 134
    case rampGreen = 142
    case rampLightGreen = 166
    case rampYellow = 185
    case rampLightOrange = 202
    case rampOrange = 220
    case rampDarkOrange = 231
    case rampRedOrange = 238
    case rampRed = 255
}

struct MapColor
{
    var rawValue:UInt8

    init(value:UInt8)
    {
        self.rawValue = value
    }

    init(color:RawColor)
    {
        self.rawValue = color.rawValue
    }

    static func random() -> MapColor
    {
        return MapColor(value: UInt8(abs(random(maximum: 255))))
    }

    private static func random(maximum:Double) -> Double
    {
        let r = Int32(Int64(arc4random()) - Int64(RAND_MAX))
        let v = (Double(r) / Double(RAND_MAX)) * maximum
        return v
    }
}

// specify a map color with a specific value
let red = RawColor.red
let mapRed = MapColor(color: red)
let value = mapRed.rawValue // value = 2

// random colors
for i in 0..<256
{
    let randomColor = MapColor.random()
    let rawValue = randomColor.rawValue
}
Comments