Glimmr Canvas-Based Drawing

version 2/101030 by Erik Temple

  • Home page
  • Beginning
  • Previous



  • Example: *** Deal Me In - This example illustrates a number of different techniques. We show how to change the contents of a graphics window at a stroke by changing canvases. We also construct a custom graphic element with rather complex behavior: rather than simply drawing a single entity, the element acts as a manager, interpreting and drawing game information using varied techniques, as needed.

    Specifically, the manager renders the cards that the player has drawn in a sort of card game (we borrow the relevant code from the Tilt 3 example in the Inform documentation). The game is poker, so the player can hold up to five cards. The manager centers and spaces the card outlines as needed (based on the width of the graphics window). For each card the player holds, it provides a white background, paints text to the window to represent the card's value (e.g., 6, 7, 10, J, Q, K), and draws a bitmap graphic in the proper color to represent the suit. If the player holds fewer than five cards, the remaining cards will be depicted as empty placeholders.

    The manager g-element also implements simple mouse input: clicking on a placeholder draws a card, while clicking on a card discards it.

    We begin by defining the graphics window. We give it a fixed height dimension so that the look doesn't vary overmuch, and make it black so that--in most interpreters--it will blend visually with the status line. We also indicate that the window should be refreshed at the end of every turn. We also declare a canvas for card-display. Later, we will declare a second canvas.

        "Deal Me In" by Erik Temple

        Include Glimmr Canvas-Based Drawing by Erik Temple.
        Include Glimmr Graphic Hyperlinks by Erik Temple.
        Include Glimmr Bitmap Font by Erik Temple.
        Include Basic Screen Effects by Emily Short.

        The graphics-window is a graphlink g-window spawned by the main-window. The position of the graphics-window is g-placeabove. The scale method of the graphics-window is g-fixed-size. The measurement of the graphics-window is 44.

        The card-display canvas is a g-canvas. The associated canvas of the graphics-window is the card-display canvas.

        The graphics-canvas is a g-canvas.

        When play begins:
            open up the graphics-window.

        Every turn:
            refresh windows.

    We want our canvases to be the same size as the window for the purposes of this example, so before scaling the canvas to the window at each refresh, we resize the canvas to match the window. (This isn't strictly necessary. We could get very nice output with fixed-size canvases, but making the canvas variable allows us to show how to make the manager element a bit more powerful.)

    Note the use of the "associated canvas of the graphics-window"--this allows this rule to resize whatever canvas we assign to the window, and will come in handy when we swap out canvases.

        Before scaling the graphics-window:
            change the canvas-width of the associated canvas of the graphics-window to the width of the graphics-window;
            change the canvas-height of the associated canvas of the graphics-window to the height of the graphics-window;
            continue the action.

    Now we define the manager g-element. We provide an origin point, but it is really a placeholder. The x-coordinate will be changed by the scaling rule for the element. Because our graphics window is of a fixed height, the y-coordinate will not change (though in principle it could, of course).

    We also define the dimensions for each card as global variables. We refer only to the variables in the remainder of the source code, so that if we want to adjust things, we need only return to this paragraph and change these variables.

        Section 1 - The manager

        The card-manager is a g-element. The associated canvas is the card-display canvas. The origin is { 0, 4 }. The graphlink status is g-active.

        The card-x is a number variable. The card-x is 28.
        The card-y is a number variable. The card-y is 36.

    Each element (or type of element) has both a scaling rule and a display rule. In the scaling rule for the card-manager element, we don't actually do any scaling. (We don't have to, because the height of our graphics window is fixed, and we simply center the cards in the available space horizontally.) Instead, we use the scaling rule as a hook for setting the x-coordinate of the origin properly in order to display the cards as centered in the window. We do this by first changing the x-coordinate (entry 1 in the origin, which is stored as a list of numbers) to the center-point of the window. We then calculate 2.5 card-widths (half of the five card-widths needed to display the five cards) plus 2/3 of a card-width for the total spacing between cards, and subtract this from the center point. This sets the x-coordinate to the leftmost point of the card assemblage.

    Next, we move on to the element display rule, where most of the real work is done. After sorting the cards into a reasonable order (by calling a rulebook from the Tilt 3 example), we repeat first through the cards held by the player. Starting at the origin point of the card-manager element, we draw two overlapping white filled rectangles, one slightly wider and shorter than the other; this is a computationally inexpensive way of depicting "rounded" corners for the cards. We then draw a bitmap text in the upper left corner of the card in the appropriate color that indicates its rank (1 to 10, J, Q, K, or A), followed by a monochrome bitmap in the lower right corner to indicate the suit. We then set a graphic hyperlink area in the same screen position as the card with the command "Discard." This command is just a placeholder; the more important thing we do here is identify the graphlink zone using the name of the card. (For the phrases used to draw the various entitites, see the Glimmr Drawing Commands extension.) Finally, we increase the drawing coordinate by the width of one card plus 1/3 of one card for spacing and start the process over with the next card.

    If the player is holding fewer than 5 cards, we repeat through the number of places remaining. We draw a lavender box (outline) for each empty spot in the player's hand, and hyperlink this with a "Draw" command.

        Element scaling rule for the card-manager:
            change entry 1 of the origin of the card-manager to entry 1 of the center-point of the card-display canvas;
            let delta-x be (card-x * 2) + (card-x / 2) + (card-x / 3) + (card-x / 3);
            change entry 1 of the origin of the card-manager to (entry 1 of the origin of the card-manager minus delta-x);

        Element display rule for the card-manager:
            let N be 5 minus the number of cards carried by the player;
            if N < 5:
                follow the hand-ranking rules;
            let x be entry 1 of the origin of the card-manager;
            let y be entry 2 of the origin of the card-manager;
            repeat with flat running through the cards carried by the player:
                draw a rectangle (color g-white) in (current window) from (x + 1) by (y) to (x + card-x - 1) by (y + card-y);
                draw a rectangle (color g-white) in (current window) from (x) by (y + 1) to (x + card-x) by (y + card-y - 1);
                paint bitmap text (color appropriate to the flat) of "[rank of flat as abbreviated value]" in (current window) at (x + 7) by (y + 2) using Glimmr C&C with dot size 1, center-aligned;
                display monochrome bitmap (color appropriate to the flat) in (current window) at (x + card-x) - 12 by (y + card-y) - 12 using (the bitmap-dataset of the flat) with dot size 1 px;
                if the card-manager is graphlinked:
                    set a graphlink in the current window identified as the flat from (x) by (y) to (x + card-x) by (y + card-y) as "DISCARD";
                increase x by card-x + (card-x / 3);
            if N > 0:
                repeat with blank running from 1 to N:
                    draw a box (color g-lavender) in (current window) from (x) by (y) to (x + card-x) by (y + card-y) with 1 px line-weight, inset;
                    if the card-manager is graphlinked:
                        set a graphlink in the current window identified as the card-manager from (x) by (y) to (x + card-x) by (y + card-y) as "DRAW", ignoring redundant links;
                    increase x by card-x + (card-x / 3).

    When we set the graphic hyperlink zone for cards in the element display rule for a card, we identified that graphlink zone using the name of the card (card placeholders were identified as the card-manager rather than any particular card). When we click on a card, then, we can provide a special graphlink processing rule to handle the mouse input (see the Glimmr Graphic Hyperlinks extension for more on the graphlink processing rules).

    In this rule, we change the replacement command to refer to the name of the card, e.g. "discard the ten of hearts".

    In the next section, we provide phrases to assign the color and bitmap shape appropriate to a given card's suit.

        Section 2 - Graphic hyperlink rule for clicking on a card (for use with Glimmr Graphic Hyperlinks by Erik Temple)

        A graphlink processing rule for a card (called the flat):
            if the flat is carried by the player:
                let T be indexed text;
                let T be the "[flat]";
                change the candidate replacement command to "[T in upper case]";
                change the glulx replacement command to "DISCARD THE [candidate replacement command]";
                change the candidate replacement command to "";
                rule succeeds.

        Section 3 - Graphic representation of suits

        To decide which glulx color value is appropriate to (flat - a card):
            if the suit of the flat is hearts or the suit of the flat is diamonds:
                decide on g-Red;
            decide on g-Black;

        To decide which list of lists of numbers is the bitmap-dataset of (flat - a card):
            if the suit of the flat is hearts:
                decide on {
        { 0, 0, 1, 0, 0, 0, 1, 0 },
        { 0, 1, 1, 1, 0, 1, 1, 1 },
        { 0, 1, 1, 1, 1, 1, 1, 1 },
        { 0, 0, 1, 1, 1, 1, 1, 0 },
        { 0, 0, 0, 1, 1, 1, 0, 0 },
        { 0, 0, 0, 0, 1, 0, 0, 0 }
        };
            if the suit of the flat is diamonds:
                decide on {
        { 0, 0, 0, 0, 1, 0, 0, 0 },
        { 0, 0, 0, 1, 1, 1, 0, 0 },
        { 0, 0, 1, 1, 1, 1, 1, 0 },
        { 0, 1, 1, 1, 1, 1, 1, 1 },
        { 0, 0, 1, 1, 1, 1, 1, 0 },
        { 0, 0, 0, 1, 1, 1, 0, 0 },
        { 0, 0, 0, 0, 1, 0, 0, 0 }
        };
            if the suit of the flat is clubs:
                decide on {
        { 0, 0, 0, 1, 1, 0, 0, 0 },
        { 0, 0, 1, 1, 1, 1, 0, 0 },
        { 0, 0, 1, 1, 1, 1, 0, 0 },
        { 1, 1, 1, 1, 1, 1, 1, 1 },
        { 1, 1, 1, 0, 0, 1, 1, 1 },
        { 1, 1, 1, 1, 1, 1, 1, 1 },
        { 0, 0, 0, 1, 1, 0, 0, 0 },
        { 0, 0, 1, 1, 1, 1, 0, 0 }
        };
            if the suit of the flat is spades:
                decide on {
        { 0, 0, 0, 1, 1, 0, 0, 0 },
        { 0, 0, 1, 1, 1, 1, 0, 0 },
        { 0, 1, 1, 1, 1, 1, 1, 0 },
        { 1, 1, 1, 1, 1, 1, 1, 1 },
        { 1, 1, 1, 1, 1, 1, 1, 1 },
        { 0, 1, 1, 1, 1, 1, 1, 0 },
        { 0, 0, 0, 1, 1, 0, 0, 0 },
        { 0, 0, 1, 1, 1, 1, 0, 0 }
        };

    The player may not be sure what to do in this example, so we offer some help text. For kicks, we'll do this in the graphics window, using a bitmap-rendered string g-element. When the player types "HELP", we change the associated canvas of the graphics-window temporarily to the graphics-canvas (defined in Glimmr Simple Graphics Window); the graphics-canvas has only one element, the bitmap-rendered string that provides our instructions. While this is displayed, we pause the game using the "wait for any key" phrase. After the keypress, we switch the canvas back to the card-display canvas and refresh the window again.


        Section 4 - Changing the canvas

        Asking for help is an action out of world. Understand "help" as asking for help.

        After printing the banner text:
            [say "[line break]This example for the Glimmr Canvas-Based Drawing extension illustrates a couple of different of techniques:[paragraph break](1) Changing the contents of a window at a stroke by swapping out the canvas (type HELP to see this).[line break](2) The construction of a complex graphic element from a number of parts: the card graphics are composed of two rectangles, a bitmap-rendered text, and a bitmap graphic.[paragraph break]The example is built on top of the Tilt 3 example from the Inform Recipe Book, essentially unchanged but with the graphic display layered on top.[paragraph break]";]
            say "Type HELP for instructions."

        Carry out asking for help:
            change the associated canvas of the graphics-window to the graphics-canvas;
            say "Instructions above. Press any key to continue.";
            refresh windows;
            wait for any key;
            change the associated canvas of the graphics-window to the card-display canvas;
            refresh windows.

        Help text is a bitmap-rendered string. The associated canvas is the graphics-canvas. The origin is { 5, 10 }. The text-string is "Click on a blank to draw a card. Click on a card to discard it." The tint is g-CornflowerBlue. The bit-size is 2.

        Test me with "help / draw / draw / draw / draw / draw"


    From this point on, the code does not differ from the Inform documentation's "Tilt 3" example.

        Section 5 - Colored Output in Two Forms

        [For the suit symbols, we'll want the Unicode extension included with Inform:]

        Include Unicode Character Names by Graham Nelson.

        Rule for printing the name of a card (called target) while grouping together:
            say "[rank of the target as abbreviated value][suit of the target as symbol]".

        To say (current suit - a suit) as symbol:
            if current suit is diamonds, say "[red letters][unicode black diamond suit][default letters]";
            if current suit is spades, say "[unicode black spade suit]";
            if current suit is clubs, say "[unicode black club suit]";
            if current suit is hearts, say "[red letters][unicode black heart suit][default letters]".


        Table of User Styles (continued)
      style name  glulx color  
      special-style-1  g-red  

        To say red letters: say first custom style.

        To say default letters: say roman type.


        Section 6 - Cards

        Suit is a kind of value. The suits are hearts, clubs, diamonds, and spades. Understand "heart" as hearts. Understand "club" as clubs. Understand "diamond" as diamonds. Understand "spade" as spades.

        A card is a kind of thing. A card has a suit. A card has a number called rank. Understand the suit property as describing a card. Understand the rank property as describing a card.

        52 cards are in the card repository.

        To say (count - a number) as a card value:
            choose row count in the Table of Value Names;
            say "[term entry]".

        Rule for printing the name of a card (called target):
            say "[rank of the target as a card value] of [suit of the target]"

        To say (count - a number) as abbreviated value:
            choose row count in the Table of Value Names;
            say "[abbrev entry]".

        Table of Value Names
      term  value  abbrev  topic  
      "ace"  "1"  "A"  "ace/A"  
      "deuce"  "2"  "2"  "deuce/two"  
      "three"  "3"  "3"  "three"  
      "four"  "4"  "4"  "four"  
      "five"  "5"  "5"  "five"  
      "six"  "6"  "6"  "six"  
      "seven"  "7"  "7"  "seven"  
      "eight"  "8"  "8"  "eight"  
      "nine"  "9"  "9"  "nine"  
      "ten"  "10"  "10"  "ten"  
      "jack"  "11"  "J"  "jack/knave/J"  
      "queen"  "12"  "Q"  "queen/Q"  
      "king"  "13"  "K"  "king/K"  

        After reading a command:
            if the player's command includes "of [suit]":
                while the player's command includes "of", cut the matched text;
            repeat through the Table of Value Names:
                while the player's command includes topic entry, replace the matched text with value entry.

        When play begins:
            reconstitute deck.

        To reconstitute deck:
            let current suit be hearts;
            now every card is in the card repository;
            while a card is in the card repository:
                repeat with current rank running from 1 to 13:
                    let item be a random card in card repository;
                    change rank of item to current rank;
                    change suit of item to current suit;
                    now item is in the deck of cards;
                change current suit to the suit after the current suit.
            

        Section 7 - The Deck and the Discard Pile

        The Casino is a room. "Nothing to see here."

        The deck of cards is in the Casino. It is a closed unopenable container. The description is "A standard poker deck."

        The discard pile is a closed unopenable container. The description is "Cards in this game are discarded face-down, so the discard pile is not very interesting to see. All you can observe is that it currently contains [if the number of cards which are in the discard pile is less than ten][the number of cards which are in the discard pile in words][otherwise]about [the rounded number of cards which are in the discard pile in words][end if] card[s]."

        To decide what number is the rounded number of (described set - a description of objects):
            let N be the number of members of the described set;
            let R be N divided by 5;
            let total be R times 5;
            decide on total.

        Rule for printing room description details of something: do nothing instead.


        Section 8 - Drawing and Discarding Actions

        Understand the commands "take" and "carry" and "hold" and "get" and "drop" and "throw" and "discard" as something new.

        Understand "take [text]" or "get [text]" or "drop [text]" as a mistake ("Here, you only draw and discard. Nothing else matters at the moment.").

        Understand "draw" or "draw card" or "draw a card" as drawing. Drawing is an action applying to nothing. The drawing action has an object called the card drawn.

        Setting action variables for drawing:
            change the card drawn to a random card which is in the deck of cards.

        Check drawing:
            if the card drawn is nothing, say "The deck is completely depleted." instead.

        Check drawing:
            if the number of cards carried by the player is greater than four,
                say "This is a five-card game; you must discard something before drawing anything further." instead.

        Carry out drawing:
            move the card drawn to the player.

        Report drawing:
            say "You draw [a card drawn]."


        Understand "discard [card]" as discarding. Discarding is an action applying to one thing.

        Check discarding:
            if the player does not carry the noun, say "You can only discard cards from your own hand." instead.

        Carry out discarding:
            now the noun is in the discard pile;
            if the discard pile is not visible, move the discard pile to the location.

        Report discarding:
            say "You toss [the noun] nonchalantly onto the discard pile."

        Section 9 - Assessing Hands

        Before listing contents while taking inventory: group cards together.

        Before grouping together cards:
            if the number of cards carried by the player is 5:
                say "[run paragraph on]";
                follow the hand-ranking rules;
                if the rule succeeded, say "[the outcome of the rulebook]";
                otherwise say "some random cards";
                if the outcome of the rulebook is pair outcome, say " of [rank of the first thing held by the player as a card value]s";
            otherwise:
                say "[number of cards carried by the player in words] assorted cards";
            say " (".

        Rule for grouping together cards:
            say "[list hand]".

        To say list hand:
            let chosen card be the first thing held by the player;
            while chosen card is a card:
                say "[chosen card]";
                now chosen card is the next thing held after chosen card;
                if chosen card is a card, say "-".

        After grouping together cards:
            say ")".

        The hand-ranking rules is a rulebook. The hand-ranking rules have outcomes royal flush, straight flush, four of a kind, full house, flush, straight, three of a kind, two pairs, pair, high card.

        The hand-ranking rulebook has a truth state called the flushness.
        The hand-ranking rulebook has a truth state called the straightness.

        The hand-ranking rulebook has a number called the pair count.
        The hand-ranking rulebook has a number called the triple count.
        The hand-ranking rulebook has a number called the quadruple count.

        A card can be sorted or unsorted. A card is usually unsorted.

        Definition: a card is high if its rank is 11 or more.
        Definition: a card is low if its rank is 4 or less.

        A hand-ranking rule (this is the initial sort rule):
            now every card is unsorted;
            while the player carries an unsorted card:
                let item be the lowest unsorted card held by the player;
                move item to the player;
                now the item is sorted;
            if sort-debugging is true, say "-- after initial sort: [list hand]".

        A hand-ranking rule (this is the finding flushness rule):
            let called suit be the suit of a random card carried by the player;
            if every card carried by the player is called suit, change flushness to true.

        A hand-ranking rule (this is the finding straightness rule):
            change straightness to true;
            let N be the rank of the highest card which is carried by the player;
            repeat with current rank running from N - 4 to N:
                change the test rank to the current rank;
                unless the player carries a matching card:
                    if the current rank is N - 4 and the current rank is 9 and the player carries an ace card, do nothing;
                    otherwise change straightness to false.

        A card can be quadrupled, tripled, paired or uncombined.

        Test rank is a number that varies. Definition: a card is matching if its rank is the test rank.

        A hand-ranking rule (this is the counting multiples rule):
            now every card is uncombined;
            repeat with current rank running from 1 to 13:
                change test rank to current rank;
                let N be the number of matching cards held by the player;
                if N is 4:
                    increase the quadruple count by 1;
                    now every matching card held by the player is quadrupled;
                if N is 3:
                    increase the triple count by 1;
                    now every matching card held by the player is tripled;
                if N is 2:
                    increase the pair count by 1;
                    now every matching card held by the player is paired.

        A hand-ranking rule (this is the move aces up unless there's a low straight rule):
            unless the straightness is true and the lowest card carried by the player is an ace card and the rank of the highest card carried by the player is 5,
                now every ace card which is carried by the player is carried by the player;
            if sort-debugging is true, say "-- after ace movement rule: [list hand]".

        A hand-ranking rule (this is the move pairs forward rule):
            while the player carries a paired card:
                let selection be the lowest paired card which is carried by the player;
                move the selection to the player;
                now the selection is uncombined;
            if sort-debugging is true, say "-- after pairs movement: [list hand]".

        A hand-ranking rule (this is the raise ace pairs rule):
            if the player carries exactly two ace cards:
                repeat with item running through ace cards which are carried by the player:
                    move item to the player;
            if sort-debugging is true, say "-- after paired-ace movement: [list hand]".

        A hand-ranking rule (this is the move multiples forward rule):
            while the player carries a tripled card:
                let selection be the lowest tripled card which is carried by the player;
                move the selection to the player;
                now the selection is uncombined;
            while the player carries a quadrupled card:
                let selection be the lowest quadrupled card which is carried by the player;
                move the selection to the player;
                now the selection is uncombined;
            if sort-debugging is true, say "-- after multiples movement rule: [list hand]".

        Definition: a card is ace if its rank is 1.
        Definition: a card is king if its rank is 13.

        A hand-ranking rule (this is the royal-flush rule):
            if flushness is true and straightness is true and the highest card carried by the player is king and the lowest card carried by the player is ace, royal flush.

        A hand-ranking rule (this is the straight-flushes rule):
            if flushness is true and straightness is true, straight flush.

        A hand-ranking rule (this is the four-of-a-kind rule):
            if the quadruple count is 1, four of a kind.

        A hand-ranking rule (this is the full-house rule):
            if the pair count is 1 and the triple count is 1, full house.

        A hand-ranking rule (this is the flushes rule):
            if flushness is true, flush.

        A hand-ranking rule (this is the straights rule):
            if straightness is true, straight.

        A hand-ranking rule (this is the three-of-a-kind rule):
            if triple count is 1, three of a kind.

        A hand-ranking rule (this is the two-pair rule):
            if the pair count is 2, two pairs.

        A hand-ranking rule (this is the pair rule):
            if the pair count is 1, pair.

        A hand-ranking rule (this is the default rule):
            high card.

        Sort-debugging is a truth state that varies.