Re: Factor

Factor: the language, the theory, and the practice.

Color Picker Game

Wednesday, May 3, 2023

#colors #ui

In the SwiftUI by Tutorials book by Ray Wenderlich, there is a tutorial on building RGBullsEye, which is a game for adjusting RGB Colors using sliders to match a provided random color and providing a “color score” to the user showing how well they matched it. Some users have even posted their solutions on GitHub.

I thought it would be fun to build a version of this example using Factor.

We could generate a color by using random-unit to make three random values for the red, green, and blue slots. Instead, we can pick randomly from the standard color database.

: random-color ( -- color )
    named-colors random named-color ;

Comparing two colors can use the rgba-distance word from the colors.distances vocabulary, returning an integer score out of 100 points:

: color-score ( color1 color2 -- n )
    rgba-distance 1.0 swap - 100.0 * round >integer ;

We can define a gadget type that can be used to find our object in a gadget hierarchy.

TUPLE: color-picker-game < track ;

Given a child of the color-picker-game instance, we can pull out the color-preview gadgets in a slightly fragile way by knowing where they are in the layout:

: find-color-previews ( gadget -- preview1 preview2 )
    [ color-picker-game? ] find-parent
    children>> first children>> first2 ;

Using that, we can make a button that, when clicked:

  1. finds the two color-preview objects
  2. grabs the latest color value from their models
  3. calculates the “color score”
  4. displays it by modifying the button text
: <match-button> ( -- button )
    "Match Color" [
        dup find-color-previews
        [ model>> compute-model ] bi@
        color-score "Your score: %d" sprintf
        over children>> first text<< relayout
    ] <border-button> ;

Another button can be used to reset the color we are trying to match against to a new random color, setting it on the model used by the left color-preview:

: <reset-button> ( -- button )
    "Random" [
        find-color-previews drop model>>
        random-color swap set-model
    ] <border-button> ;

Using these two buttons, and some gadgets from the color picker vocabulary, we can build up our interface, choosing a random color to start, and then laying out the other components we need:

:: <color-picker-game> ( -- gadget )
    vertical color-picker-game new-track { 5 5 } >>gap

    random-color <model>     :> left-model
    \ <rgba> <color-sliders> :> ( sliders right-model )

    horizontal <track>
        left-model <color-preview> 1/2 track-add
        right-model <color-preview> 1/2 track-add
    1 track-add

    sliders                     f track-add
    right-model <color-status>  f track-add
    <match-button>              f track-add
    <reset-button>              f track-add ;

We can make a main entry point, constructing the game and providing it as the main gadget:

MAIN-WINDOW: color-picker-game-window
    { { title "Color Picker Game" } }
    <color-picker-game> >>gadgets ;

This is available in the development version and includes some additional features such as support for additional color spaces along with some improvements to our tabbed gadgets. Give it a try!