Challenges Unlocking New Game Types


It's surprising how many changes came along with a seemingly simple concept like unlocking protected games. To start playing around with new games I wanted to make sure people don't stumble on them too early - but for an application that doesn't actually store anything or have accounts, protecting content not ready for the public is somewhat difficult. 

The first challenge - storing the codes so you don't have to put 'em in every time. There's a library to handle that (https://react-native-async-storage.github.io/) but it's asynchronous and most of the Tenkiwi is built around re-frame, which has abstractions to pretend everything is synchronous. The library also deals only in strings, which is a far cry from full-on Clojure data structures. After dealing with the strings problem (thanks to a few easy calls to edn), I mostly decided to ignore the asynchronous problem and hope for the best right now. At some point I imagine that's going to come back and bite me. 

(defn- stringify [val] (clj->js (prn-str val)))
(defn- unstring [str] (edn/read-string str))
(defn load-storage-items [keys callback]
  (let [key-arr (mapv stringify keys)
        cb (fn [errs vals]
             (let [mapped (reduce #(assoc %1
                                          (unstring (first %2))
                                          (unstring (last %2)))
                                  {}
                                  (js->clj vals))]
               (callback mapped)))]
    (.multiGet AsyncStorage (clj->js key-arr) cb)))
(defn set-storage-items [map]
  (let [keystrs (mapv stringify (keys map))
        valstrs (mapv stringify (vals map))
        tuples  (mapv #(array %1 %2) keystrs valstrs)]
    (.multiSet AsyncStorage (clj->js tuples))))
(def supported-storage-keys #{:unlock-codes})
(re-frame/reg-fx  :set-storage  
  (fn [map]    
    (let [bad-keys (remove supported-storage-keys (keys map))]
      (doseq [bad-key bad-keys]
        (println "Attempting set on unsupported key" bad-key (get map bad-key)))
      (set-storage-items map))))
(re-frame/reg-fx  :load-storage
  (fn [_]
    (let [keys supported-storage-keys
          handle-vals (fn [pairs] (re-frame/dispatch [:sync-storage pairs]))]
      (load-storage-items keys handle-vals))))

Making a space to put the input UI also required me to start putting thought into the experience outside of the games. So far this has just been the bare minimum to get games started. So in addition to creating a settings page, why not a quick tutorial on the UI? This gives users a chance to (hopefully) figure out how games work by working out how to join the game. Hopefully it's not too much of a puzzle. 


Unlocking Games

Adding an unlock code for a game in playtesting

Putting a giant card over the join panel definitely gives the user experience in an actual game but was kinda a pain to imagine dealing with as a first time user though. How do you get this thing to go away? Even saying "swipe me up or down" feels like it's not necessarily intuitive enough. Maybe it's time to figure out how to make it hide itself when you try to scroll what's behind it (like happens in other apps). 

Turns out the library I'm using to bring the card in and out https://github.com/osdnk/react-native-reanimated-bottom-sheet does support the ability to hide away from other parts of the app, but because it's rendered through a portal it's actually really hard to get the reference to call the "snapTo" function. Another hack that's probably going to cause issues - add an atom that gets passed through everywhere that can give us access to "collapse" function from anywhere. 

;; collapse! is an atom that we end up passing around everywhere
;; and hope gets set here with an actual function. Maybe I can replace
;; it with react context providers? 
;; https://github.com/lilactown/reagent-context/blob/master/src/reagent_context/core.cljs
(defn bottom-sheet-card [{:as props :keys [start-collapsed?
                                           collapse!]}]
  (let [collapsed?   (r/atom (or start-collapsed? false))
        on-open      #(reset! collapsed? false)
        on-close     #(reset! collapsed? true)
        ref          (r/atom nil)
        do-collapse! (fn collapse-it
                       ([]
                        (do
                          (swap! collapsed? not)
                          ((aget @ref "snapTo") (if @collapsed? 2 0))))
                       ([e]
                        (if (boolean? e)
                          (collapse-it e e)
                          (collapse-it)))
                       ([maybe e]
                        (do
                          (reset! collapsed? maybe)
                          ((aget @ref "snapTo") (if @collapsed? 2 0)))))
        collapse! (doto (or collapse! (r/atom nil))
                    (reset! do-collapse!))]

So yeah, turns out a simple feature that adds nothing to the game ended up taking an entire weekend to complete, but hopefully also added a bit more usability for the next playtest. 

Leave a comment

Log in with itch.io to leave a comment.