Open source game built in SwiftUI and the Composable Architecture.

Overview

isowords

CI

This repo contains the full source code for isowords, an iOS word search game played on a vanishing cube. Connect touching letters to form words, the longer the better, and the third time a letter is used its cube is removed, revealing more letters inside!

Available on the App Store now!

Download isowords on the App Store

isowords screenshots


About

isowords is a large, complex application built entirely in Swift. The iOS client's logic is built in the Composable Architecture and the UI is built mostly in SwiftUI with a little bit in SceneKit. The server is also built in Swift using our experimental web server libraries.

We published a 4-part series of videos covering these topics and more on Point-Free, a video series exploring functional programming and the Swift language, hosted by Brandon Williams and Stephen Celis.

video poster image



Some things you might find interesting:

The Composable Architecture

The whole application is powered by the Composable Architecture, a library we built from scratch on Point-Free that provides tools for building applications with a focus on composability, modularity, and testability. This means:

  • The entire app's state is held in a single source of truth, called a Store.
  • The entire app's behavior is implemented by a single unit, called a Reducer, which is composed out of many other reducers.
  • All effectful operations are made explicit as values returned from reducers.
  • Dependencies are made explicit as simple data types wrapping their live implementations, along with various mock instances.

There are a ton of benefits to designing applications in this manner:

  • Large, complex features can be broken down into smaller child domains, and those domains can communicate via simple state mutations. Typically this is done in SwiftUI by accessing singletons inside ObservableObject instances, but this is not necessary in the Composable Architecture.
  • We take control of dependencies rather than allow them to take control of us. Just because you are using StoreKit, GameCenter, UserNotifications, or any other 3rd party APIs in your code, it doesn't mean you should sacrifice your ability to run your app in the simulator, SwiftUI previews, or write concise tests.
  • Exhaustive tests can be written very quickly. We test very detailed user flows, capture subtle edge cases, and assert on how effects execute and how their outputs feed back into the application.
  • It is straightforward to write integration tests that exercise multiple independent parts of the application.

Hyper-modularization

The application is built in a hyper-modularized style. At the time of writing this README the client and server are split into 86 modules. This allows us to work on features without building the entire application, which improves compile times and SwiftUI preview stability. It also made it easy for us to ship an App Clip, whose size must be less than 10 MB uncompressed, by choosing the bare minimum of code and resources to build.

Client/Server monorepo

The code for both the iOS client and server are included in this single repository. This makes it easy to run both the client and server at the same time, and we can even debug them at the same time, e.g. set breakpoints in the server that are triggered when the simulator makes API requests.

We also share a lot of code between client and server:

  • The core types that describe players, puzzles, moves, etc.
  • Game logic, such as the random puzzle generator, puzzle verification, dictionaries, and more.
  • The router used for handling requests on the server is the exact same code the iOS client uses to make API requests to the server. New routes only have to be specified a single time and it is immediately available to both client and server.
  • We write integration tests that simultaneously test the server and iOS client. During a test, API requests made by the client are actually running real server code under the hood.
  • And more...

Automated App Store screenshots and previews

The screenshots and preview video that we upload to the App Store for this app are automatically generated.

  • The screenshots are generated by a test suite using our SnapshotTesting library, and do the work of constructing a very specific piece of state that we load into a screen, as well as framing the UI and providing the surrounding graphics.

  • The preview video is generated as a screen recording of running a slimmed-down version of the app that embeds specific letters onto a cube and runs a sequence of actions to emulate a user playing the game. The app can be run locally by selecting the TrailerPreview target in Xcode and running it in the simulator.

Preview apps

There are times that we want to test a feature in isolation without building the entire app. SwiftUI previews are great for this but also have their limitations, such as if you need to use APIs unavailable to previews, or if you need to debug more complex flows, etc.

So, we create mini-applications that build a small subset of the 86+ modules that comprise the entire application. Setting up these applications requires minimal work. You just specify what dependencies you need in the Xcode project and then create an entry point to launch the feature.

For example, here is all the code necessary to create a preview app for running the onboarding flow in isolation. If we were at the whims of the full application to test this feature we would need to constantly delete and reinstall the app since this screen is only shown on first launch.

Getting Started

This repo contains both the client and server code for running the entire isowords application, as well as an extensive test suite. To get things running:

  1. Make sure git-lfs is installed so that app assets (images, etc.) can be fetched.
  2. Grab the code:
    git clone https://github.com/pointfreeco/isowords
    cd isowords
  3. Bootstrap the application:
    1. If you are only interested in building the iOS client, then run the following bootstrap command:
      make bootstrap-client
    2. If you want to build the client and server make sure PostgreSQL is installed and running, and then run the following bootstrap command:
      make bootstrap
  4. Open the Xcode workspace isowords.xcworkspace.
  5. To run the client locally, select the isowords target in Xcode and run (⌘R).
  6. To run the server locally, select the server target in Xcode and run (⌘R).

Learn More

Most of the concepts discussed in this README are covered in-depth on Point-Free, a video series exploring functional programming and the Swift language, hosted by Brandon Williams and Stephen Celis.

Point-Free

Related Projects

This application makes use of a number of open source projects built by us and discussed on Point-Free, including:

License

The source code in this repository may be run and altered for education purposes only and not for commercial purposes. For more information see our full license.

Comments
  • make bootstrap-client does not fetch image assets

    make bootstrap-client does not fetch image assets

    Describe the bug After cloning the repository, running: make bootstrap-client and then opening the workspace file and attempting to run the main isowords target produces the following error: The stickers icon set or app icon set named "AppIcon" did not have any applicable content.

    To reproduce

    • Clone the repository
    • Run make bootstrap-client in terminal
    • Open workspace
    • Run isowords target
    • Build fails

    Expected behavior The app runs in the iOS simulator

    Screenshots/video Screen Shot 2021-06-11 at 4 05 40 PM

    Environment

    • 2016 MacBook Pro 15":
    • OS: macOS 11.4
    opened by romero-ios 10
  • Remove workspace, just use project.

    Remove workspace, just use project.

    Thanks to a tip from @krzysztofzablocki we found out that we can get rid of the workspace if we hold the Xcode project in a sub-directory instead of the root, which we already do.

    opened by mbrandonw 6
  • unexpected service error: “multiple configured targets of \’CubeCore\’ are being created for iOS Simulator”

    unexpected service error: “multiple configured targets of \’CubeCore\’ are being created for iOS Simulator”

    --- Solved --- But feel free to comment...

    In the spirit of issue #49 I'll ask this question here - but please let me know if you'd prefer I ask it elsewhere (Twitter, StackOverflow, forums.swift.org - where is best?)

    I'm interested in using Previews to view some of the available screens. I’m running into an issue I’m sure you had to solve early on in your development:

    “unexpected service error: build aborted due to an internal error: planning Failed(“multiple configured targets of \’CubeCore\’ are being created for iOS Simulator”)

    I see multiple proposed solutions, from setting the DISABLE_DIAMOND_PROBLEM_DIAGNOSTIC build setting to YES in the app target to converting static libraries to dynamic. What worked best for you?

    opened by Mozahler 6
  • Fix Settings from Home

    Fix Settings from Home

    This reinstalls the Setting reducer in Home reducer. It was most likely left behind during the "protocol" conversion.

    As Reduce and the other ifLet reducers are not effectively handling .settings actions, they all commute with Settings, so this reducer can be installed arbitrarily at the end of the reducer's chain with the other child reducers.

    This should fix #170

    I also took the liberty to modernize homogenous EmptyReducer().ifCaseLet… into Scope(state: /CasePath…, but I can revert if you prefer.

    opened by tgrapperon 3
  • [Feature] - Time limit for multiplayer mode

    [Feature] - Time limit for multiplayer mode

    As described in the title, I think it will be a good feature if we add a time limit for each turn in multiplayer mode. For example, there can be an adjustable time limit of say 1 minute before the person's turn is forfeited.

    I will add more design details because I would actually like to work on this, once I get a better understanding of the codebase (just discovered this amazing project today).

    opened by jevonmao 3
  • make bootstrap-client produces multiple fail messages

    make bootstrap-client produces multiple fail messages

    I'm new to git-lfs and the version bootstrap tried to use caused a segmentation fault. I removed it and reinstalled using brew. Then I was asked to issue 'git lfs install' when I issued 'git-lfs' with no args. That worked fine. At this point I am cd-ed into isowords, and issue 'make bootstrap-client':

    ⚠️ Checking for Git LFS... ✅ Git LFS is good to go! touch Sources/AppAudioLibrary/Resources/empty.mp3 touch Sources/AppClipAudioLibrary/Resources/empty.mp3 /dev/stdin:173: INSERT failed: UNIQUE constraint failed: words.word /dev/stdin:236: INSERT failed: UNIQUE constraint failed: words.word --- for a total of 1468 similar lines

    are these normal error messages that can be safely ignored?

    opened by Mozahler 3
  • Visually improves AppStorePreview

    Visually improves AppStorePreview

    Hello folks!

    In the wake of the Part 2 episode of the isowords tour (and after collecting all the pieces of my brain from the floor after it has been 🤯-ed), I've decided to poke around the snapshot tests and noticed the AppStore images could be improved.

    Here's a diff with my changes: image

    • Device and screen corners are now continuous
    • Device and screen corner radii are now concentric
    • Added cellular bars
    • Status bar views are now horizontally centred in their corresponding ear
    • Status bar bottom edge is visually aligned to the notch

    In detail: image

    I hope this is useful!

    P.S. I'm still salty that I'm #11 in on of the screenshots and it shows only the top 10 results...

    opened by myurieff 2
  • Explicit error message for missing 'git-lfs'

    Explicit error message for missing 'git-lfs'

    Hi everyone 👋🏻

    I am absoluty not proud of this one, because I needed 50 minutes to figure out what actually happened since I'm on a new device and didn't think twice about my tools installed. A more explicit error message will maybe help other lost souls like me 😅

    If the language or format used in the message needs correction, I'm happy about every kind of feedback. Since then .. happy easter 🥚

    opened by mflknr 2
  • Mimicking the modular structure of this app

    Mimicking the modular structure of this app

    Hi. Love the project and the way it is structured. I have tried to move one of my own apps over to a similar sort of scheme by following the setup of your package file

    I am seeing a few flaky CI runs, sometimes testing passes and sometimes it fails.

    the failure seems to be related to the resolution of XCTestDynamicOverlay

    error: Missing package product 'XCTestDynamicOverlay' 
    

    when I see this locally I can get rid of the issue by clearing my derived data which then prompts Xcode to do its thing and pull the dependencies again.

    I know it may be difficult to diagnose without having a copy of my project, but I figure it was worth an ask. Maybe you guys have seen something similar? I am using an almost identical copy of your ci workflow and a paired down set of package dependencies. Hopefully I am missing something small.

    opened by j-j-m 2
  • Daily Challenge Unlimited button doesn't fade when ending a game immediately

    Daily Challenge Unlimited button doesn't fade when ending a game immediately

    I've started the Daily Unlimited challenge and then ended it without doing anything. Now the game doesn't let me play the challenge again which is fine, but the button isn't faded out.

    opened by kuglee 2
  • Change my user name?

    Change my user name?

    I tried asking via Twitter, but I think my privacy settings were messed up. I was using the test version of isowords until you released it on the store, and then bought that version. I notice a lot of people have normal user names, whereas mine is a randomly generated string. Can I change my user name? If not, it's not a big deal... Alas, I'm afraid my days in the top ten are forever gone!

    opened by Mozahler 2
  • Settings.Action.binding actions are not being handled as expected

    Settings.Action.binding actions are not being handled as expected

    Describe the bug It seems like many of the viewStore actions sent within views of the Settings module (e.g. SoundsSettingsView) do not actually do anything within the Settings reducer as I'd expect. Instead, I see in the debug console a message like this:

    2022-12-09 17:31:27.590658-0600 isowords[11473:1931144] [ComposableArchitecture] A binding action sent from a view store 
    at "SettingsFeature/SoundsSettingsView.swift:22" was not handled. …
    
      Action:
        Settings.Action.binding(.set(_, 0.6017382))
    
    To fix this, invoke "BindingReducer()" from your feature reducer's "body".
    

    This is very strange because BindingReducer() is indeed within the Settings reducer body.

    To reproduce If I compile isowords in Xcode, run the app, click on the settings button, select the sound button and try and change the value of any of the sound sliders, the sliders actually do not change value.

    Expected behavior I'd expect that the Settings reducer would actually stop at any of the breakpoints I set within it.

    Screenshots/video https://user-images.githubusercontent.com/67525430/206812842-45fda284-78ec-4908-8177-1ab66a1a5015.mov

    Environment

    • Device: iPhone 14 Simulator
    • OS: iOS 16.1
    • Xcode: 14.1

    Additional context What is strange is that if I run "SettingsPreview", I do not see this problem.

    opened by acosmicflamingo 2
  • Replace FileClient functionality with PersistenceClient (WIP)

    Replace FileClient functionality with PersistenceClient (WIP)

    After trying to figure out how to handle saving UserSettings in my own app (https://github.com/pointfreeco/swift-composable-architecture/discussions/1384) and reading the discussion about mutable environment states, I thought I'd give it a try to replace FileClient with a new PersistenceClient, which will use UserDefaults in the backend for saving user data.

    opened by acosmicflamingo 2
  • isowords is running audio and using battery even when the isowords audio settings have turned audio off.

    isowords is running audio and using battery even when the isowords audio settings have turned audio off.

    Describe the bug Audio is turned off in isowords settings. Battery in System Settings is showing significant percentage of battery drain and Audio as the load.

    To reproduce Turn audio off, run isowords for a couple of games, go to battery (an hour later) and look at the description.

    Expected behavior If Audio is turned off isowords should be spending zero cpu cycles playing audio. Quick look at the code makes it seem like there aren't any checks for sound off and it just plays the audio with zero volume. You'd hope iOS would be smart about that, but it seems it may not be.

    Screenshots/video IMG_4851

    IMG_4850

    Environment

    • Device: iPhone 12 Pro
    • OS: iOS 15.5
    • isowords build 102 (fe7eb2c)
    bug 
    opened by GeekAndDad 2
  • use iPhone metrics when the iPad app is running in compact width (closes #128)

    use iPhone metrics when the iPad app is running in compact width (closes #128)

    This PR changes the majority of the code that is checking for iPad UI idiom to code that checks for "effectively using iPad metrics", which I've defined as "iPad idiom and horizontal size class not compact". With this change in place, the app will select UI element sizes and layout values (e.g. padding) that are intended for iPhone when running on iPad in compact width.

    This fix contains a shortcut that is probably fine for an app such as isowords, but is incorrect in the general case. Namely, the horizontal size class is tracked in the DeviceState struct, which captures the horizontal size class high up in the view hierarchy, whereas it is OK for intermediate views to manually override this value. Thus, the horizontalSizeClass value in the SwiftUI Environment on the view where you want to apply the metrics is the real "truth" and could diverge from what is in DeviceState. However, that makes the code a lot messier since it introduces another environment property into each of these views, plus each test about what metrics to apply becomes idiom == pad && horizontalSizeClass != compact.

    This change affects metrics and layout in the broader app, it's possible I've introduced some visual regressions since I've not tested all the flows extensively.

    I have noticed some visual glitching when doing some rotation and the app is in compact width. I'm not certain if this is a regression or some artifact of running in the simulator. I was not successful getting the code signing to be generic enough to run a local build of the app on my devices.

    Simulator Screen Shot - iPad Pro (11-inch) (2nd generation) - 2021-08-17 at 13 12 21

    If this problem is present when running on device, I don't mind working on it a little further if you have hints about where I should look.

    An alternative "fix" for this problem would be to enable "requires full screen" in info.plist, which would stop supporting the multitasking scenarios completely.

    opened by chucks 1
  • Various layout issues when iPad app is in compact width

    Various layout issues when iPad app is in compact width

    Describe the bug iPad multitasking is enabled in info.plist, but the app has various layout issues when running side by side in compact width.

    This is true even with the cube worldScale fix from #126

    To reproduce

    • Launch the app on iPad and use multitasking to put the app into compact width.
    • rotate the iPad etc etc

    Expected behavior Nothing is clipped and screen width is used effectively. It would probably be most effective if the iPad app in compact width used layout metrics similar to the phone app since compact width sizes are fairly similar.

    Screenshots/video

    some representative examples.

    Screen Shot 2021-08-16 at 4 52 41 PM Screen Shot 2021-08-16 at 4 52 23 PM Screen Shot 2021-08-16 at 4 51 41 PM

    Environment

    • iPad, iOS 14.7.1

    I'm working on a PR to use the iPhone layout values when running on iPad in compact width.

    opened by chucks 0
Owner
Point-Free
A video series exploring Swift and functional programming.
Point-Free
Thomas Grapperon 32 Dec 12, 2022
A library to derive and compose Environment's in The Composable Architecture.

ComposableEnvironment This library brings an API similar to SwiftUI's Environment to derive and compose Environment's in The Composable Architecture.

Thomas Grapperon 129 Dec 14, 2022
A library that provides undo semantics for the Composable Architecture with optional bridging tofUndoManager.

Swift Composable Undo A library that provides undo semantics for the Composable Architecture with optional bridging with UndoManager. Motivation It is

Aacapella Holdings Pty. Ltd. 17 Nov 18, 2022
Learn how to structure your iOS App with declarative state changes using Point-Free's The Composable Architecture (TCA) library.

Learn how to structure your iOS App with declarative state changes using Point-Free's The Composable Architecture (TCA) library.

Tiago Henriques 0 Oct 2, 2022
Pointfree's Composable Architecture Relay

RelayStore Pointfree's Composable Architecture Relay to hook into Actions sent to the Store from outside. Read more at Observe actions in The Composab

Alejandro Martínez 3 May 29, 2022
ScrumdingerTCA - Apple’s tutorial app recreated using The Composable Architecture

ScrumdingerTCA Apple’s tutorial app recreated using The Composable Architecture

Pat Brown 9 Nov 29, 2022
Tanukis-Stash - Open source e621 client for iOS built with SwiftUI

The Tanuki's Stash The worlds first e621 client for iOS, iPadOS, and macOS built

Jay 3 Jul 29, 2022
RippleQueries is an iOS application built as assessment task at Ripple Egypt. Built Using MVVM (Model-View-ViewModel) and Clean Architecture concepts

RippleRepositories RippleRepositories is an iOS application built as an assessment task at Ripple Egypt. Built Using RxSwift & MVVM (Model-View-ViewMo

Muhammad Ewaily 3 Sep 16, 2021
Best architecture for SwiftUI + CombineBest architecture for SwiftUI + Combine

Best architecture for SwiftUI + Combine The content of the presentation: First of the proposed architectures - MVP + C Second of the proposed architec

Kyrylo Triskalo 3 Sep 1, 2022
Mvi Architecture for SwiftUI Apps. MVI is a unidirectional data flow architecture.

Mvi-SwiftUI If you like to read this on Medium , you can find it here MVI Architecture for SwiftUI Apps MVI Architecture Model-View-Intent (MVI) is a

null 12 Dec 7, 2022
News App 📱 built to demonstrate the use of SwiftUI 3 features, Async/Await, CoreData and MVVM architecture pattern.

Box Feed News App ?? built to demonstrate the use of following features, SwiftUI 3 Async/Await AsyncImage List Refreshable Swipe Actions Separator Cor

Sameer Nawaz 113 Dec 20, 2022
E-commerce app built in SwiftUI. Built in the course SwiftUI Masterclass in Udemy.

Touchdown-SwiftUI E-commerce app built in SwiftUI. Built in the course SwiftUI Masterclass in Udemy. Main components and concepts used: @EnvironmentOb

Jorge Martinez 5 Aug 18, 2022
An open-source SwiftUI Stack Overflow client

StackOv A SwiftUI Stackoverflow client We are currently in the developing process of the next version of StackOv app. The demo version of StackOv is a

Surf 235 Dec 17, 2022
Pro Counter, SwiftUI WatchOS, Open Source Project

Countio | SwiftUI WatchOS App App Screenshots || What is Countio Countio is simple WatchOS App made with Swiftly SwiftUI. You can count anything quick

Dc7 4 Feb 7, 2022
A open source Swift app for iOS 13 that allows you to check your NFC transit card information. Written with SwiftUI.

ABANDONED Metrodroid, which TransitPal was heavily inspired by, now has its own fully functional iOS app! It should be in the App Store "soon". I stro

Robbie Trencheny 132 Jun 30, 2022
Riddler is a riddle game built as a native iOS app in Swift using SwiftUI

Riddler is a riddle game built as a native iOS app in Swift using SwiftUI. It includes 50 challenging riddles with hints for when you get stuck. The game tracks your stats so you can compare your performance against your friends, and see who can answer all 50 riddles the quickest.

Oliver Stenning 3 Nov 23, 2022
KHabit an open source, pure and minimalistic app which helps you maintain productive habits, and nothing more.

an open source, pure and minimalistic app which helps you maintain productive habits, and nothing more. The app is completely open source, it does not contain in-app or ads, and does not track the user in any way.

Stefano Bertoli 42 Dec 17, 2022
an open source, pure and minimalistic app which helps you maintain productive habits, and nothing more.

KHabit an open source, pure and minimalistic app which helps you maintain productive habits, and nothing more. The app is completely open source, it d

Stefano Bertoli 17 May 5, 2021
OONI Probe is free and open source software designed to measure internet censorship and other forms of network interference.

OONI Probe iOS OONI Probe is free and open source software designed to measure internet censorship and other forms of network interference. Click here

Open Observatory of Network Interference (OONI) 59 Nov 2, 2022