Saturday, December 16, 2023

Swasteroids 2.0



I’ve released Swashteroids 2.0 to the App Store! A fun retro open-source game built on my open-source ECS framework Swash. 

All new user-interface and art. Play with or without buttons! Plasma torpedoes and hyperspace are now both power-ups, and they're moving! Game play mechanics have been tweaked, ie asteroids' base speeds increase per level. The game starts easier as well so you can warm up.

https://apps.apple.com/us/app/swashteroids/id6472061502

Monday, November 20, 2023



 I’ve released Swashteroids to the App Store! A fun retro open-source game built on my open-source ECS framework Swash.

https://apps.apple.com/us/app/swashteroids/id6472061502

Friday, November 17, 2023




Version 2.0 of my Near Mint iOS app is now live! This release has many new features and other improvements. This is an app that helps you organize your comic book collection. It is fast and native since 2017! 🤠

#comicbooks #ios



Thursday, April 14, 2022

Swift Arrays to Dictionaries with indices

The extension below is to facilitate the creation of dictionaries from arrays. In this case, the dictionary has the array element as the key and the index of that element as the value. 

Since I was only working with arrays that held unique values I added a guard statement to enforce that. If you remove it, then your the last duplicated element would have the index as its value.

extension Array where Element: Hashable {

    func toDictionary() -> [Element: Int] {

        guard Set(self).count == self.count else {

            fatalError("\(#function) requires arrays with unique values!")

        }

        return self

            .enumerated()

            .reduce(into: [Element: Int]()) { dict, tup in

                dict[tup.1] = tup.0

            }

    }


["hello", "world"].toDictionary() // ["world": 1, "hello": 0]


Saturday, June 01, 2019

100 Days of Swift

3. For a tougher challenge, take the image generation code out of cellForRowAt: generate all images when the app first launches, and use those smaller versions instead. For bonus points, combine the getDocumentsDirectory() method I introduced in project 10 so that you save the resulting cache to make sure it never happens again.

As a reminder, here’s the code for getDocumentsDirectory():
func getDocumentsDirectory() -> URL {
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    return paths[0]
}

The above is from Day 98 of Paul Hudson's excellent course: 100 Days of Swift. It is Challenge #3 and there is even a "bonus" challenge inside the challenge! In approaching the solution--before coding--I wrote down my thoughts and then gathered snippets of code that I thought would help. My notes are below. I thought it might be helpful for others to share them without giving away the solution.

First, I jotted down the four main things I was concerned with:
  1. Read big images from bundle, see project 1.
  2. Render a image into a rect using CoreGraphics like in project 30. 
  3. Read the thumbnails in like project 10
  4. If a thumbnail does not exist then create it. Also like in project 10.
Next, I gathered the corresponding snippets of the code:

From project 1:
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
    let fm = FileManager.default
    let path = Bundle.main.resourcePath!
    let items = try! fm.contentsOfDirectory(atPath: path)
    
    let pictureNames = items
        .filter { $0.hasPrefix("nssl") }
        .sorted { $0 < $1 }
    
    self?.storms = pictureNames.map {
        Storm(name: $0, imageName: $0) }
    
    DispatchQueue.main.async { [weak self] in
        self?.collectionView.reloadData()
    }
}

From project 30:
        let path = Bundle.main.path(forResource: imageRootName, ofType: nil)!
        let original = UIImage(contentsOfFile: path)!

        let renderRect = CGRect(origin: .zero, size: CGSize(width: 90, height: 90))
        let renderer = UIGraphicsImageRenderer(size: renderRect.size)
        
        let rounded = renderer.image { ctx in
            ctx.cgContext.addEllipse(in: renderRect)
            ctx.cgContext.clip()
            
            original.draw(in: renderRect)
        }

From project 10:

Write:
        let imageName = UUID().uuidString
        let imagePath = getDocumentsDirectory().appendingPathComponent(imageName)
        if let jpegData = image.jpegData(compressionQuality: 0.8) {
            try? jpegData.write(to: imagePath)
        }

Read:
        let path = getDocumentsDirectory().appendingPathComponent(person.imageName)
        cell.imageView.image = UIImage(contentsOfFile: path.path)

Wednesday, December 31, 2014

The Ash Entity Framework


I’ve been doing object-oriented programming for over 20 years. I’ve worked with many languages in that time. One can improve their ability to abstract the more they work with different languages. A more challenging exercise is to change your programming paradigm. 

Lately, the paradigm I’ve been experimenting with is entity component systems (ECS). ECS are touted for game programming. A common scenario in games is for things in the game to changes powers and abilities. To solve this issue in the past, I’ve relied heavily on composition over inheritance. In ECS, that idea is taken to the extreme.

The objects (entities) are made up of components (that are mostly value objects), the logic resides in the systems that operate on the entities. After reading an excellent post by Richard Lord, I decided to give his ECS micro framework a try, it’s call Ash.  

The Ash Entity Framework

The Ash Entity Framework uses entity, components, systems, and nodes. Ash facilities the work of mapping the systems to the components they work on. Let me define these:
  • Components are (mostly) simple value objects. They contain little or no behavior.
  • Entities hold components. Entities only care about components. By changing what components an entity has affects which systems affect the entity. This changing of entity composition is important as it impacts node creation as well as which systems execute on it. Entity names must be unique! 
  • Systems operate on lists of nodes. Systems only care about nodes. Systems contain the logic. 
  • Nodes are combinations of one or more components. Node classes are defined and at runtime, an entity that has components matching the definition gets a node created by the Ash engine. Ash also removes and destroys nodes when they are no longer applicable to the entity composition. Node lists use signals--not events--for adding and removing.
The Engine is part of the framework. It knows about entities and systems, both of which you add to the engine. It in turn watches entities and--as they change--adds and removes the components to node collections used by systems. The engine creates the node collections and passes them to the appropriate systems.


Wednesday, February 12, 2014

ABGC On Sale

My course A Blender Game Character is on sale at CartoonSmart thru Saturday, $25 (usually $39) #b3d